Skip to content

Conversation

@kingston
Copy link
Collaborator

@kingston kingston commented Dec 11, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a slot-based context system for schema definitions, enabling more flexible parent-child relationship management through typed slot provisioning.
  • Refactor

    • Restructured reference extraction from context-driven to a functional pipeline approach for improved clarity and maintainability.
    • Migrated schema definition patterns to accept and propagate context slots throughout schema builders.
    • Updated example project configuration for enhanced plugin setup.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 11, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
baseplate-project-builder-web Ready Ready Preview Comment Dec 11, 2025 5:18pm

@changeset-bot
Copy link

changeset-bot bot commented Dec 11, 2025

🦋 Changeset detected

Latest commit: 17f9245

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 18 packages
Name Type
@baseplate-dev/project-builder-lib Patch
@baseplate-dev/project-builder-cli Patch
@baseplate-dev/project-builder-common Patch
@baseplate-dev/project-builder-server Patch
@baseplate-dev/project-builder-test Patch
@baseplate-dev/project-builder-web Patch
@baseplate-dev/plugin-auth Patch
@baseplate-dev/plugin-queue Patch
@baseplate-dev/plugin-storage Patch
@baseplate-dev/create-project Patch
@baseplate-dev/code-morph Patch
@baseplate-dev/core-generators Patch
@baseplate-dev/fastify-generators Patch
@baseplate-dev/react-generators Patch
@baseplate-dev/sync Patch
@baseplate-dev/tools Patch
@baseplate-dev/ui-components Patch
@baseplate-dev/utils Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Dec 11, 2025

Walkthrough

This PR refactors the reference extraction and schema-building system from a recursive, context-driven approach to a functional, pipeline-based approach using typed context slots. Core changes include introducing RefContextSlot for context provisioning, replacing marker types with structured annotations, adding definitionSchemaWithSlots for slot-aware schemas, and migrating all schema definitions across the codebase to use slot-based context propagation.

Changes

Cohort / File(s) Summary
Core Reference System
packages/project-builder-lib/src/references/collect-refs.ts, definition-ref-builder.ts, ref-context-slot.ts, markers.ts, extract-definition-refs.ts, extend-parser-context-with-refs.ts
Introduces functional reference collection pipeline; replaces path-centric generics with TIdPath and slot-based provides/parentSlot fields; refactors marker system to use structured annotations (DefinitionEntityAnnotation, DefinitionReferenceAnnotation, DefinitionSlotAnnotation); removes legacy withRefBuilder and adds refContext API; replaces recursive extraction with pipeline: collectRefsresolveSlotsstripRefMarkers.
Reference Utilities
packages/project-builder-lib/src/references/resolve-slots.ts, resolve-zod-ref-payload-names.ts, deserialize-schema.unit.test.ts, extract-definition-refs.unit.test.ts, resolve-zod-ref-payload-names.unit.test.ts
Introduces slot resolution and ancestor matching; updates extractDefinitionRefsPayload type with nameResolver support; removes extractDefinitionRefsRecursive and adds collectRefs public export; adapts tests to new ExtractDefinitionRefsPayload structure.
Reference Types & Exports
packages/project-builder-lib/src/references/types.ts, index.ts
Exposes idPath and ReferenceOnDeleteAction on DefinitionEntity; re-exports ref-context-slot module.
Schema Creator Infrastructure
packages/project-builder-lib/src/schema/creator/schema-creator.ts, creator/types.ts, infer-types.ts
Adds definitionSchemaWithSlots factory for slot-aware schemas; introduces DefinitionSchemaCreatorWithSlots type and withPlaceholderSlots wrapper; updates context to expose refContext instead of withRefBuilder; generalizes type inference to support both slot and non-slot creators.
App Schema Migrations
packages/project-builder-lib/src/schema/apps/backend/index.ts, apps/web/admin/admin.ts, apps/web/web-app.ts
Converts app schemas to slot-based: introduces appSlot and threads it through admin/backend/web schemas via definitionSchemaWithSlots; adds backendAppEntryType and webAppEntryType exports.
Admin CRUD Column Schemas
packages/project-builder-lib/src/schema/apps/web/admin/sections/crud-columns/types.ts, admin-column-spec.ts, admin-crud-column.ts, built-in-columns.ts
Introduces AdminCrudColumnSlots and AdminCrudColumnSchemaCreator; updates column schemas to accept modelSlot via definitionSchemaWithSlots; replaces parentPath with parentSlot for field/relation references.
Admin CRUD Form Schemas
packages/project-builder-lib/src/schema/apps/web/admin/sections/crud-form/types.ts, admin-input-spec.ts, admin-crud-input.ts, built-in-input.ts
Introduces AdminCrudInputSlots and AdminCrudInputSchemaCreator; converts input schemas to accept modelSlot and adminSectionSlot; replaces parentPath with slot-based parent references.
Admin CRUD Section Schema
packages/project-builder-lib/src/schema/apps/web/admin/sections/crud.ts
Refactors to slot-aware variant: introduces modelSlot and adminSectionSlot via refContext; updates embedded object/list/form schemas to use slots; removes non-exported schema variants.
Model Schema Migrations
packages/project-builder-lib/src/schema/models/index.ts, enums.ts, graphql.ts
Converts all model-related schemas to definitionSchemaWithSlots: scalar fields, relations, constraints, services, and base schema now accept and propagate modelSlot; enum and GraphQL schemas use slot-based contexts instead of inline builders.
Model Transformer Schemas
packages/project-builder-lib/src/schema/models/transformers/types.ts, model-transformer-spec.ts, transformers.ts, built-in-transformers.ts
Introduces ModelTransformerSlots and ModelTransformerSchemaCreator; converts transformer schemas to accept modelSlot and propagate it via parentSlot; updates embedded relation transformer to use slot-based references with provides.
Project-level Schemas
packages/project-builder-lib/src/schema/project-definition.ts, web/hooks/use-definition-schema.ts
Migrates app schema to ctx.refContext({ appSlot }); adds overloaded useDefinitionSchema signatures supporting both slot and non-slot schemas with withPlaceholderSlots wrapper.
Web Frontend Import Consolidation
packages/project-builder-web/src/hooks/use-definition-schema.ts, routes/admin-sections.$appKey/-components/new-admin-section-dialog.tsx, routes/admin-sections.$appKey/edit.$sectionKey.tsx, routes/apps/edit.$key/backend.tsx, routes/apps/edit.$key/web/admin.tsx
Removes local useDefinitionSchema hook; imports now sourced from @baseplate-dev/project-builder-lib/web.
Utils & Type System
packages/utils/src/index.ts, types/index.ts, types/tuple-paths.ts, types/tuple-paths.unit.test.ts
Introduces TuplePaths utility type for recursive path enumeration; adds re-exports and comprehensive unit tests for type-level path inference.
Plugin Schema Updates
plugins/plugin-storage/src/storage/admin-crud/types.ts, storage/core/schema/plugin-definition.ts, storage/transformers/schema/file-transformer.schema.ts
Converts storage admin/transformer schemas to definitionSchemaWithSlots with modelSlot; removes withRefBuilder wrapper; updates file transformer with type literal and slot-based references.
Server Configuration
packages/project-builder-server/src/dev-server/server.ts, server/server.ts
Relocates maxParamLength into Fastify routerOptions object.
Example & Metadata
examples/blog-with-auth/baseplate/project-definition.json, .changeset/swift-foxes-dance.md, packages/project-builder-lib/src/tools/model-merger/model-merger.unit.test.ts
Updates example config CLI version and plugin structure; documents refactor via changeset; updates test reference to use model name string instead of ID.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

This refactor is substantial and intricate, spanning the entire reference extraction pipeline, schema-builder infrastructure, and propagating across ~90 files with heterogeneous changes:

  • Complexity density: Type system overhaul (path-centric → slot-centric), marker annotation restructuring, functional pipeline introduction, and multi-pattern schema migrations across diverse domains (app, admin CRUD, model, transformer, plugin schemas).
  • Heterogeneity: Changes range from new module introductions (ref-context-slot.ts, collect-refs.ts, resolve-slots.ts, tuple-paths.ts), API refactors (extend-parser-context-with-refs.ts, definition-ref-builder.ts), widespread schema factory conversions (definitionSchemaWithSlots), and strategic removals (withRefBuilder, extractDefinitionRefsRecursive).
  • Scope: Affects core reference system, schema creator pattern, all nested domain schemas (models, enums, transformers, admin CRUD, apps), type inference, and frontend integration.

Areas requiring extra attention:

  • Type system integrity: Validate that TuplePaths correctly generates exhaustive path tuples and that TIdPath constraint changes do not break existing schema usage.
  • Slot resolution logic: Verify resolveSlots and findNearestAncestorSlot correctly match ancestor slots, especially for deeply nested schemas and edge cases.
  • Reference pipeline: Ensure the new functional pipeline (collectRefsresolveSlotsstripRefMarkers) produces equivalent outputs to the previous recursive approach, particularly around nested entity/reference aggregation and parent path resolution.
  • Schema factory propagation: Audit that definitionSchemaWithSlots correctly threads slots through all nested schema creators and that withPlaceholderSlots adequately mocks slots for validation-only contexts.
  • Marker annotation conversions: Check that structured DefinitionEntityAnnotation/DefinitionReferenceAnnotation payloads are correctly assembled and deserialized across serialize-schema and deserialize-schema flows.
  • Admin CRUD & model transformer slots: Validate that new slot-based wiring of modelSlot, adminSectionSlot, enumSlot across deeply nested field/column/input/transformer definitions preserves parent–child relationships and name resolution.
  • Frontend hook overloads: Confirm that useDefinitionSchema correctly detects and wraps slot-requiring creators with withPlaceholderSlots.

Possibly related PRs

  • PR #587: Both PRs make tightly related, code-level changes to the reference system (extract-definition-refs, definition-ref-builder, markers, serialization/deserialization pipeline), migrating from old ZodRef/ref-builder to a transform/slot-based refContext approach.
  • PR #578: Continues the schema creator refactor introduced there by adding slot-based refContext APIs and converting schema creators to definitionSchemaWithSlots (same schema-creator files updated).
  • PR #424: Introduces utilities TuplePaths and related type exports that this PR depends on for compile-time path inference, with coordinated updates to packages/utils module structure.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main architectural refactor from the changeset: replacing the withRefBuilder pattern with a functional refContext and provides-based approach for reference extraction.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kingston/eng-929-ref-context-slots-reference-system-redesign

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (10)
packages/project-builder-lib/src/tools/model-merger/model-merger.unit.test.ts (1)

777-790: GraphQL relation now using model name instead of ID is consistent with ref refactor

Switching modelRef to the literal 'Author' here aligns the desired GraphQL/Post relation with the existing config (which already uses authorModel.name) and with the broader move to stable, name-based references in the new ref/slot system. Functionally this looks correct.

If you want to avoid duplicating the 'Author' string, you could optionally pull the model name into a shared const and reuse it in both authorModel and this relation, but that's a nice‑to‑have, not required.

packages/project-builder-lib/src/schema/creator/schema-creator.ts (1)

56-72: Add explicit return type annotation.

Per coding guidelines, top-level functions should include return types. While TypeScript can infer the return type, an explicit annotation improves readability and catches potential type mismatches earlier.

 export function definitionSchemaWithSlots<
   TSlotDef extends RefContextSlotDefinition,
   T extends z.ZodType,
 >(
   slotDefinition: TSlotDef,
   creator: (
     ctx: DefinitionSchemaParserContext,
     slots: RefContextSlotMap<TSlotDef>,
   ) => T,
-): DefinitionSchemaCreatorWithSlots<T, TSlotDef> {
+): DefinitionSchemaCreatorWithSlots<T, TSlotDef> {
   const creatorWithSlots: DefinitionSchemaCreatorWithSlots<T, TSlotDef> = (
     context,
     slots,
   ) => creator(context, slots);
   creatorWithSlots.slotDefinition = slotDefinition;
   return creatorWithSlots;
 }

The return type is already present, so this is actually correct. The documentation example is clear and demonstrates the slot-based pattern well.

packages/project-builder-lib/src/schema/apps/backend/index.ts (1)

10-22: The creator function ignores ctx and slots parameters.

Based on definitionSchemaWithSlots signature in schema-creator.ts, the creator receives (ctx: DefinitionSchemaParserContext, slots: RefContextSlotMap<TSlotDef>). The current implementation uses () => which discards both parameters. If ctx and slots are intentionally unused, consider using (_ctx, _slots) => to make this explicit and avoid potential lint warnings about unused parameters.

 export const createBackendAppSchema = definitionSchemaWithSlots(
   { appSlot: appEntityType },
-  () =>
+  (_ctx, _slots) =>
     z.object({
       ...baseAppValidators,
       type: z.literal('backend'),
       enableStripe: z.boolean().optional(),
       enableBullQueue: z.boolean().optional(),
       enablePostmark: z.boolean().optional(),
       enableSubscriptions: z.boolean().optional(),
       enableAxios: z.boolean().optional(),
     }),
 );
packages/project-builder-lib/src/references/collect-refs.ts (2)

32-50: Consider using explicit type annotation instead of type assertion.

The type assertion as CollectedRefs on line 50 could be replaced with an explicit type annotation on the variable declaration for better type safety.

-  const collected = {
+  const collected: CollectedRefs = {
     entities: [],
     references: [],
     slots: [],
-  } as CollectedRefs;
+  };

89-100: Redundant type check for object keys.

Object.entries() always returns string keys in JavaScript, so the check typeof key !== 'string' on line 90 will never be true. This can be safely removed.

     for (const [key, childValue] of Object.entries(value)) {
-      if (typeof key !== 'string') continue;
       const childCollected = collectRefAnnotationsRecursive(
         [...pathPrefix, key],
         childValue,
       );
packages/project-builder-lib/src/references/resolve-slots.ts (1)

100-105: Consider using push() for minor performance improvement.

The current spread pattern creates a new array on each registration. For schemas with many slots of the same type, using push() would be more efficient.

-    const existingSlots = resolvedSlotsByType.get(slotId) ?? [];
-    resolvedSlotsByType.set(slotId, [
-      ...existingSlots,
-      { resolvedPath, path: nearestAncestorSlot.path },
-    ]);
+    let existingSlots = resolvedSlotsByType.get(slotId);
+    if (!existingSlots) {
+      existingSlots = [];
+      resolvedSlotsByType.set(slotId, existingSlots);
+    }
+    existingSlots.push({ resolvedPath, path: nearestAncestorSlot.path });
packages/project-builder-lib/src/schema/apps/web/admin/sections/crud-columns/types.ts (1)

3-5: Consider extracting common slot interface pattern.

Both this file and transformers/types.ts define nearly identical interfaces and types:

  • ModelTransformerSlots / AdminCrudColumnSlots
  • ModelTransformerSchemaCreator / AdminCrudColumnSchemaCreator

If more slot-based schema creators are added, consider extracting a generic base pattern to reduce duplication.

Example of a potential shared pattern:

// In a shared types file
export interface ModelSlotContainer {
  modelSlot: RefContextSlot<typeof modelEntityType>;
}

export type SlotAwareSchemaCreator<
  TSlots,
  TSchema extends z.ZodType = z.ZodType,
> = (ctx: DefinitionSchemaParserContext, slots: TSlots) => TSchema;

Also applies to: 31-41

packages/project-builder-lib/src/references/extract-definition-refs.ts (1)

84-91: Minor: Comment says "Step 4" but this is actually Step 5.

The JSDoc flow documentation mentions 4 steps, but the duplicate ID validation is effectively a 5th step. Consider updating the comment for consistency.

-  // Step 4: Validate no duplicate IDs
+  // Step 5: Validate no duplicate IDs
packages/project-builder-lib/src/schema/apps/web/admin/sections/crud-form/built-in-input.ts (1)

25-38: Consider whether unused adminSectionSlot is intentional.

The slot definition includes adminSectionSlot, but only modelSlot is destructured and used. The same pattern appears in createAdminCrudForeignInputSchema and createAdminCrudEnumInputSchema. This may be intentional for API consistency or future extensibility, but if these schemas will never need adminSectionSlot, consider removing it to keep the API minimal.

If the unused slot is intentional for consistency, please confirm. Otherwise:

-export const createAdminCrudTextInputSchema = definitionSchemaWithSlots(
-  { modelSlot: modelEntityType, adminSectionSlot: adminSectionEntityType },
-  (ctx, { modelSlot }) =>
+export const createAdminCrudTextInputSchema = definitionSchemaWithSlots(
+  { modelSlot: modelEntityType },
+  (ctx, { modelSlot }) =>
packages/project-builder-lib/src/references/extend-parser-context-with-refs.ts (1)

150-163: Consider improving error handling for missing name resolver.

When getNameResolver is not provided and the name field is missing, the function returns 'invalid' as the nameResolver while still adding a validation issue. This means downstream code receives an entity annotation with an 'invalid' nameResolver, which could cause confusing errors later.

Consider either:

  1. Returning early after adding the issue (like the id validation block above)
  2. Using a more descriptive sentinel value or structured error
          if (!('name' in value) || typeof value.name !== 'string') {
            ctx.addIssue({
              code: 'custom',
              message: `Unable to find string name field in entity ${entity.type.name}`,
              input: value,
            });
-           return 'invalid';
+           return value; // Return early, similar to id validation
          }
          return value.name;

Note: This would require adjusting the subsequent logic to handle the early return case.

@kingston kingston merged commit 18c7cf1 into main Dec 11, 2025
17 checks passed
@kingston kingston deleted the kingston/eng-929-ref-context-slots-reference-system-redesign branch December 11, 2025 17:22
@github-actions github-actions bot mentioned this pull request Dec 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants