Skip to content

fix: Fix usage of register entity type URL#786

Merged
kingston merged 5 commits into
mainfrom
kingston/eng-937-centralize-registerentitytypeurl-to-single-file
Feb 26, 2026
Merged

fix: Fix usage of register entity type URL#786
kingston merged 5 commits into
mainfrom
kingston/eng-937-centralize-registerentitytypeurl-to-single-file

Conversation

@kingston

@kingston kingston commented Feb 26, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced plugin-based entity type navigation registration for improved extensibility and type safety.
    • Added core module for centralized entity type URL mappings across models, enums, plugins, apps, libraries, and admin sections.
  • Refactoring

    • Refactored entity navigation system from direct URL generation to structured navigation options for enhanced flexibility.
    • Updated reference parent selection logic to prioritize deeper nested entity relationships.

@changeset-bot

changeset-bot Bot commented Feb 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f190c1d

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

This PR includes changesets to release 21 packages
Name Type
@baseplate-dev/project-builder-lib Patch
@baseplate-dev/project-builder-web Patch
@baseplate-dev/create-project Patch
@baseplate-dev/project-builder-cli Patch
@baseplate-dev/project-builder-common Patch
@baseplate-dev/project-builder-dev Patch
@baseplate-dev/project-builder-server Patch
@baseplate-dev/project-builder-test Patch
@baseplate-dev/plugin-auth Patch
@baseplate-dev/plugin-email Patch
@baseplate-dev/plugin-queue Patch
@baseplate-dev/plugin-rate-limit Patch
@baseplate-dev/plugin-storage 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

coderabbitai Bot commented Feb 26, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR refactors entity-type navigation and schema handling by introducing a plugin-spec-based URL registration flow with typed discriminated unions, centralizing schema instantiation through a parser-context pattern, and updating reference-handling APIs to accept concrete Zod schemas instead of creator functions.

Changes

Cohort / File(s) Summary
Entity-type navigation plugin spec
packages/project-builder-lib/src/web/specs/entity-type-url-web-spec.ts, packages/project-builder-lib/src/web/specs/index.ts
New plugin spec for registering and retrieving entity-type navigation builders with typed discriminated unions (model, enum, plugin, app, lib, admin-section) and conditional parent parameters based on entity hierarchy.
Schema context and parser centralization
packages/project-builder-lib/src/definition/project-definition-container.ts, packages/project-builder-lib/src/definition/project-definition-container.test-utils.ts, packages/project-builder-lib/src/parser/parser.ts
ProjectDefinitionContainer now accepts and stores an explicit schema instance; adds entityFromId, nameFromId, and safeNameFromId helpers; centralizes schema creation via createDefinitionSchemaParserContext before instantiation rather than globally.
Reference handling API refactoring
packages/project-builder-lib/src/references/deserialize-schema.ts, packages/project-builder-lib/src/references/parse-schema-with-references.ts, packages/project-builder-lib/src/references/fix-ref-deletions.ts, packages/project-builder-lib/src/references/serialize-schema.ts
Updates function signatures to accept concrete Zod schemas directly (parameter schema: T) instead of schema creator functions; removes schemaCreatorOptions parameter; aligns generic constraints from DefinitionSchemaCreator to z.ZodType and return types from def.InferOutput<T> to z.output<T>.
Reference/serialization test updates
packages/project-builder-lib/src/references/deserialize-schema.unit.test.ts, packages/project-builder-lib/src/references/extract-definition-refs.unit.test.ts, packages/project-builder-lib/src/references/fix-ref-deletions.unit.test.ts, packages/project-builder-lib/src/references/serialize-schema.unit.test.ts
Tests updated to use createDefinitionSchemaParserContext pattern; schemas now instantiated via schemaCreator(parserContext) instead of inline plugin passing; fixRefDeletions and serialize/parse calls simplified to accept pre-built schema objects.
Entity-type navigation service
packages/project-builder-web/src/services/entity-type.ts
Replaces getEntityTypeUrl with getEntityNavOptions; introduces internal helpers getEntityNavTarget (plugin-spec-based navigation target resolution), resolveParentEntity (parent traversal with validation), and resolveEntityNavTarget (target-to-options conversion); adds logging for missing parent/grandparent entities.
Entity-type navigation core module
packages/project-builder-web/src/core-modules/entity-type-urls-core-module.ts, packages/project-builder-web/src/core-modules/index.ts
New core module that centralizes entity-type URL registration; registers model, enum, plugin, app, lib, and admin-section entity types with their navigation targets via the plugin spec; includes adminSectionChildBuilder helper for conditional admin-section child mappings.
Navigation usage updates
packages/project-builder-web/src/app/project-definition-provider/project-definition-provider.tsx, packages/project-builder-web/src/components/ref-issue-dialog/ref-issue-dialog.tsx
ProjectDefinitionProvider now passes pre-built schema to ProjectDefinitionContainer; RefIssueDialog uses getEntityNavOptions for navigation link construction instead of getEntityTypeUrl, applies spread options to link rendering.
Inline registration removal
packages/project-builder-web/src/routes/data/models/edit.$key/index.tsx, packages/project-builder-web/src/routes/data/models/edit.$key/service.tsx
Removes route-level imports and registerEntityTypeUrl calls; entity-type registrations now centralized in the core module.

Sequence Diagram(s)

sequenceDiagram
    participant Component as UI Component
    participant DefContainer as ProjectDefinitionContainer
    participant PluginContainer as PluginSpecStore
    participant WebSpec as entityTypeUrlWebSpec
    participant NavService as entity-type service
    participant NavTarget as EntityNavTarget

    Component->>DefContainer: get entity
    Component->>NavService: getEntityNavOptions(defContainer, entity, pluginContainer)
    NavService->>DefContainer: resolveParentEntity(parentId)
    DefContainer-->>NavService: parent entity (validated)
    NavService->>PluginContainer: get entityTypeUrlWebSpec
    PluginContainer-->>NavService: web spec instance
    NavService->>WebSpec: getNavBuilder(entityType)
    WebSpec-->>NavService: builder function
    NavService->>NavService: builder(params) → EntityNavTarget
    NavService->>NavTarget: resolveEntityNavTarget(target)
    NavTarget-->>NavService: ValidateNavigateOptions
    NavService-->>Component: navigation options
    Component->>Component: apply spread options to link
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #737 — Introduces the plugin-spec system (createPluginSpec/createPluginModule, PluginSpecStore) that this PR uses for the new entity-type URL registration; the entity-type URL web spec depends directly on those APIs.
  • PR #587 — Modifies the same reference/schema transformation code paths (deserialize, parse, fixRefDeletions) and introduces the parser-context pattern that this PR extends with ProjectDefinitionContainer schema plumbing.
  • PR #779 — Touches the same schema/parser infrastructure and ProjectDefinitionContainer schema-driven transformations used in project-definition-provider for schema-aware fix operations.
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'fix: Fix usage of register entity type URL' is generic and repetitive. It vaguely references the entity type URL registration without clearly describing the main architectural change: centralizing entity type URL registration through a plugin spec. Consider a more descriptive title like 'refactor: Centralize entity type URL registration through plugin spec' to better convey the primary architectural change of this PR.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kingston/eng-937-centralize-registerentitytypeurl-to-single-file

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/project-builder-web/src/components/ref-issue-dialog/ref-issue-dialog.tsx (1)

65-74: ⚠️ Potential issue | 🟠 Major

Logic bug: Path length comparison is inverted.

The condition on line 70 should select the entity with the longest matching path (deepest ancestor), but the current logic keeps referenceParent when its path is longer than the candidate's path. This means it will select the shortest matching path instead.

🐛 Proposed fix
               if (
                 issuePath.startsWith(entityPath) &&
                 (!referenceParent ||
-                    referenceParent.path.length > entityPath.length)
+                    referenceParent.path.length < entityPath.length)
               ) {
                 referenceParent = e;
               }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/project-builder-web/src/components/ref-issue-dialog/ref-issue-dialog.tsx`
around lines 65 - 74, The current loop that finds the reference parent
(variables: entities, e, entityPath, issuePath, referenceParent) accidentally
keeps the shorter match because it checks referenceParent.path.length >
entityPath.length; change the comparison so it chooses the longest matching path
(deepest ancestor) — e.g., when a candidate e matches
(issuePath.startsWith(entityPath)) update referenceParent if referenceParent is
null OR e.path.length > referenceParent.path.length (or compare
entityPath.split('.').length > referenceParent.path.length) so the
deepest/longest matching entity is selected.
🧹 Nitpick comments (2)
packages/project-builder-lib/src/references/extract-definition-refs.unit.test.ts (1)

729-733: Consider extracting parser-context setup helper in expression tests.

Line 729 and similar blocks repeat the same parser-context boilerplate. A tiny local helper would reduce duplication and make each test’s intent clearer.

Also applies to: 764-768, 861-865, 946-950, 1027-1031

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/project-builder-lib/src/references/extract-definition-refs.unit.test.ts`
around lines 729 - 733, Several tests repeatedly construct the same
parser-context boilerplate (calls to createDefinitionSchemaParserContext with
new PluginSpecStore and then passing to schemaCreator); extract a small local
helper function (e.g., makeParserContext or parserCtx) at the top of the test
file that returns createDefinitionSchemaParserContext({ plugins: new
PluginSpecStore() }) and replace each repeated block (places invoking
schemaCreator(createDefinitionSchemaParserContext(...)) such as the occurrences
around schemaCreator, createDefinitionSchemaParserContext, and PluginSpecStore)
with a single call to that helper to remove duplication and clarify intent.
packages/project-builder-lib/src/definition/project-definition-container.ts (1)

67-70: Optional: reuse entityFromId in name helpers to remove duplicate lookups.

entityFromId, nameFromId, and safeNameFromId repeat the same linear search pattern. Reusing the helper reduces drift and keeps behavior centralized.

Also applies to: 80-102

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/project-builder-lib/src/definition/project-definition-container.ts`
around lines 67 - 70, The name helper methods duplicate the linear search
performed in entityFromId; refactor nameFromId and safeNameFromId to call
entityFromId(id) instead of repeating this.entities.find(...) so lookups are
centralized and behavior stays consistent; update nameFromId to return
entityFromId(id)?.name and safeNameFromId to return entityFromId(id)?.name ?? ''
(or the existing fallback) while preserving their current return semantics and
references to DefinitionEntity and entity.id.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/project-builder-lib/src/definition/project-definition-container.ts`:
- Around line 143-147: The constructor call creating ProjectDefinitionContainer
should receive a parserContext whose pluginStore matches the updated pluginStore
being passed into the container; currently the code passes the original context
and the new pluginStore separately, leaving this.parserContext.pluginStore out
of sync. Update the factory calls that invoke new
ProjectDefinitionContainer(...) (the ones building
parsedDefinition/context/pluginStore/schema around ProjectDefinitionContainer)
to clone or replace the incoming context so that parserContext.pluginStore is
set to the same pluginStore object you pass as the pluginStore argument (i.e.,
construct a new context or assign contextWithUpdatedPlugins = { ...context,
pluginStore } and pass that instead). Apply the same change to the other factory
invocation referenced (the second occurrence around the other constructor call)
so both places keep parserContext.pluginStore consistent with pluginStore.

In `@packages/project-builder-lib/src/web/specs/entity-type-url-web-spec.ts`:
- Around line 66-71: The register function currently overwrites existing nav
builders in the builders Map silently; update the register implementation (the
register closure that accepts DefinitionEntityType and EntityTypeNavBuilder) to
first check builders.has(entityType.name) and if true throw an Error (or
otherwise fail fast) indicating a duplicate registration for that entity type
name; this ensures duplicate registrations for
DefinitionEntityType/EntityTypeNavBuilder are detected and prevented rather than
replaced silently.

---

Outside diff comments:
In
`@packages/project-builder-web/src/components/ref-issue-dialog/ref-issue-dialog.tsx`:
- Around line 65-74: The current loop that finds the reference parent
(variables: entities, e, entityPath, issuePath, referenceParent) accidentally
keeps the shorter match because it checks referenceParent.path.length >
entityPath.length; change the comparison so it chooses the longest matching path
(deepest ancestor) — e.g., when a candidate e matches
(issuePath.startsWith(entityPath)) update referenceParent if referenceParent is
null OR e.path.length > referenceParent.path.length (or compare
entityPath.split('.').length > referenceParent.path.length) so the
deepest/longest matching entity is selected.

---

Nitpick comments:
In `@packages/project-builder-lib/src/definition/project-definition-container.ts`:
- Around line 67-70: The name helper methods duplicate the linear search
performed in entityFromId; refactor nameFromId and safeNameFromId to call
entityFromId(id) instead of repeating this.entities.find(...) so lookups are
centralized and behavior stays consistent; update nameFromId to return
entityFromId(id)?.name and safeNameFromId to return entityFromId(id)?.name ?? ''
(or the existing fallback) while preserving their current return semantics and
references to DefinitionEntity and entity.id.

In
`@packages/project-builder-lib/src/references/extract-definition-refs.unit.test.ts`:
- Around line 729-733: Several tests repeatedly construct the same
parser-context boilerplate (calls to createDefinitionSchemaParserContext with
new PluginSpecStore and then passing to schemaCreator); extract a small local
helper function (e.g., makeParserContext or parserCtx) at the top of the test
file that returns createDefinitionSchemaParserContext({ plugins: new
PluginSpecStore() }) and replace each repeated block (places invoking
schemaCreator(createDefinitionSchemaParserContext(...)) such as the occurrences
around schemaCreator, createDefinitionSchemaParserContext, and PluginSpecStore)
with a single call to that helper to remove duplication and clarify intent.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bd25ff0 and f190c1d.

📒 Files selected for processing (21)
  • .changeset/entity-type-nav-spec.md
  • packages/project-builder-lib/src/definition/project-definition-container.test-utils.ts
  • packages/project-builder-lib/src/definition/project-definition-container.ts
  • packages/project-builder-lib/src/parser/parser.ts
  • packages/project-builder-lib/src/references/deserialize-schema.ts
  • packages/project-builder-lib/src/references/deserialize-schema.unit.test.ts
  • packages/project-builder-lib/src/references/extract-definition-refs.unit.test.ts
  • packages/project-builder-lib/src/references/fix-ref-deletions.ts
  • packages/project-builder-lib/src/references/fix-ref-deletions.unit.test.ts
  • packages/project-builder-lib/src/references/parse-schema-with-references.ts
  • packages/project-builder-lib/src/references/serialize-schema.ts
  • packages/project-builder-lib/src/references/serialize-schema.unit.test.ts
  • packages/project-builder-lib/src/web/specs/entity-type-url-web-spec.ts
  • packages/project-builder-lib/src/web/specs/index.ts
  • packages/project-builder-web/src/app/project-definition-provider/project-definition-provider.tsx
  • packages/project-builder-web/src/components/ref-issue-dialog/ref-issue-dialog.tsx
  • packages/project-builder-web/src/core-modules/entity-type-urls-core-module.ts
  • packages/project-builder-web/src/core-modules/index.ts
  • packages/project-builder-web/src/routes/data/models/edit.$key/index.tsx
  • packages/project-builder-web/src/routes/data/models/edit.$key/service.tsx
  • packages/project-builder-web/src/services/entity-type.ts
💤 Files with no reviewable changes (2)
  • packages/project-builder-web/src/routes/data/models/edit.$key/service.tsx
  • packages/project-builder-web/src/routes/data/models/edit.$key/index.tsx

Comment on lines 143 to +147
return new ProjectDefinitionContainer(
parsedDefinition,
context,
pluginStore,
schema,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep parserContext.pluginStore consistent with pluginStore inside the container.

Both factories pass an updated pluginStore (Lines 147/169) but still pass the original context (Lines 145/168). This can leave this.parserContext.pluginStore out of sync with this.pluginStore.

Suggested fix
   static fromDefinition(
     definition: ProjectDefinition,
     context: SchemaParserContext,
   ): ProjectDefinitionContainer {
     const { definition: parsedDefinition, pluginStore } =
       parseProjectDefinitionWithReferences(definition, context);
     const schema = createProjectDefinitionSchema(
       createDefinitionSchemaParserContext({ plugins: pluginStore }),
     );
+    const parserContext: SchemaParserContext = {
+      ...context,
+      pluginStore,
+    };
     return new ProjectDefinitionContainer(
       parsedDefinition,
-      context,
+      parserContext,
       pluginStore,
       schema,
     );
   }
@@
   static fromSerializedConfig(
     config: unknown,
     context: SchemaParserContext,
   ): ProjectDefinitionContainer {
     const plugins = createPluginSpecStore(context.pluginStore, config);
     const schema = createProjectDefinitionSchema(
       createDefinitionSchemaParserContext({ plugins }),
     );
+    const parserContext: SchemaParserContext = {
+      ...context,
+      pluginStore: plugins,
+    };
     return new ProjectDefinitionContainer(
       deserializeSchemaWithTransformedReferences(schema, config),
-      context,
+      parserContext,
       plugins,
       schema,
     );
   }

Also applies to: 166-170

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/project-builder-lib/src/definition/project-definition-container.ts`
around lines 143 - 147, The constructor call creating ProjectDefinitionContainer
should receive a parserContext whose pluginStore matches the updated pluginStore
being passed into the container; currently the code passes the original context
and the new pluginStore separately, leaving this.parserContext.pluginStore out
of sync. Update the factory calls that invoke new
ProjectDefinitionContainer(...) (the ones building
parsedDefinition/context/pluginStore/schema around ProjectDefinitionContainer)
to clone or replace the incoming context so that parserContext.pluginStore is
set to the same pluginStore object you pass as the pluginStore argument (i.e.,
construct a new context or assign contextWithUpdatedPlugins = { ...context,
pluginStore } and pass that instead). Apply the same change to the other factory
invocation referenced (the second occurrence around the other constructor call)
so both places keep parserContext.pluginStore consistent with pluginStore.

Comment on lines +66 to +71
register: <THasParent extends boolean>(
entityType: DefinitionEntityType<THasParent>,
builder: EntityTypeNavBuilder<THasParent>,
): void => {
builders.set(entityType.name, builder as EntityTypeNavBuilder);
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prevent silent nav builder overrides on duplicate entity type registration.

At Line 70, Map#set replaces existing builders without warning. In a plugin system, duplicate registration should fail fast to avoid route behavior changing by load order.

Suggested fix
           register: <THasParent extends boolean>(
             entityType: DefinitionEntityType<THasParent>,
             builder: EntityTypeNavBuilder<THasParent>,
           ): void => {
+            if (builders.has(entityType.name)) {
+              throw new Error(
+                `Duplicate entity nav builder registration for "${entityType.name}"`,
+              );
+            }
             builders.set(entityType.name, builder as EntityTypeNavBuilder);
           },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/project-builder-lib/src/web/specs/entity-type-url-web-spec.ts`
around lines 66 - 71, The register function currently overwrites existing nav
builders in the builders Map silently; update the register implementation (the
register closure that accepts DefinitionEntityType and EntityTypeNavBuilder) to
first check builders.has(entityType.name) and if true throw an Error (or
otherwise fail fast) indicating a duplicate registration for that entity type
name; this ensures duplicate registrations for
DefinitionEntityType/EntityTypeNavBuilder are detected and prevented rather than
replaced silently.

@kingston kingston merged commit 3029d42 into main Feb 26, 2026
10 checks passed
@kingston kingston deleted the kingston/eng-937-centralize-registerentitytypeurl-to-single-file branch February 26, 2026 16:56
@github-actions github-actions Bot mentioned this pull request Feb 26, 2026
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.

1 participant