Skip to content

fix(nextly): create the table on every field-group create transport - #596

Merged
mobeenabdullah merged 8 commits into
mainfrom
feat/field-group-metadata-service
Aug 10, 2026
Merged

fix(nextly): create the table on every field-group create transport#596
mobeenabdullah merged 8 commits into
mainfrom
feat/field-group-metadata-service

Conversation

@mobeenabdullah

@mobeenabdullah mobeenabdullah commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

The defect (P1, live, reachable through the public API)

A field group is creatable through three transports. Only one made the table.

transport wrote the registry row created the table
component-dispatcher.ts yes yes
api/field-groups.ts POST yes no
direct-api/namespaces/field-groups.ts create yes no

Both of the last two answered success. The route returned 201 Field group created.; the Direct API resolved normally. The registry then described a comp_<slug> table that did not exist, and every read and write to that field group failed against the database.

This is not a private path: nextly.fieldGroups.create() is the public programmatic surface, and api/field-groups.ts is exported for user apps to mount. Both routes already call requireBuilderEnabled("create-component"), so they were understood to BE schema operations. They just did not perform the schema half.

Why it happened, and why the fix is a service

The code that provisions the table was private to the dispatcherregisterComponentRuntimeSchema and reconcileComponentCompanion were file-local functions. Nothing else could reach them, so the other transports could not have done the right thing even by trying. Copying the DDL block into them would have entrenched exactly the split this closes.

Three commits, each independently checkable:

  1. fe6dda5a6 — the two helpers move beside the field-group schema service. Proven a pure move: both bodies diffed byte-identical against main, the only difference being the depth of three dynamic-import paths.
  2. 0604a56fdFieldGroupMetadataService: plan the DDL, apply it without throwing, write the row carrying the outcome. Registered in DI rather than built per request, so one wrapper governs every caller — which is what lets the migration lock (B1-9b) enclose both halves.
  3. be3117eda — all three transports routed through it, plus the tests.

The input type is Omit<DynamicFieldGroupInsert, "migrationStatus"> — the registry's own insert type, never a hand-listed subset. That is not stylistic: the singles equivalent listed its fields by hand and silently dropped three of them, and nothing in the types or the tests caught it.

The coverage that was missing

Each transport was individually green and the product was broken, because the coverage was per transport rather than per outcome: the dispatcher had tests, and the other two were tested for their response shape and their table-name derivation — neither of which touches a database.

field-group-create-transports.integration.test.ts asserts the outcome against a real database: the physical table exists, per transport, per dialect.

🔴 The REST route is proved a level up, and the test says so rather than leaving it implicit. It is permission-gated and the harness cannot authenticate a request, so proving it there would mean faking a session and testing the fake. Instead the split is explicit: the integration suite proves the shared service provisions the table on live databases through the two transports the harness can reach, and field-group-route-delegates.test.ts proves the route reaches that service rather than writing the row itself. Neither half stands alone, and the file comment says so.

That delegation test also asserts the negative: registerComponent must not be called. The defect was never that the row went unwritten — it was that the row was written alone, so a route calling both would satisfy a positive-only assertion.

Verification

  • pnpm build · pnpm check-types --force · pnpm lint (exit code) · node scripts/check-drizzle-v1-legacy.cjs — clean
  • Unit: 361 failed / 7489 against a freshly measured baseline of 361 / 7478 at 6ca3f4506. Zero new.
  • Integration, full suite, both dialects: PostgreSQL 1364 passed / 0 failed, MySQL 1285 passed / 0 failed
  • Proven load-bearing: reverting the route to write the row directly fails both delegation tests.

Four existing suites needed rewiring. Each got the real service over the same doubles rather than a stub, so they keep describing the code instead of describing the stub.

One deliberate omission: the dispatcher keeps its original toast wording rather than adopting the routes' new message. Changing admin copy is not part of closing this P1.

Summary by CodeRabbit

  • New Features

    • Field-group creation now provisions the associated database tables and supporting localized storage automatically.
    • Creation results now indicate whether provisioning is complete, pending, or unsuccessful.
    • Component creation uses the same table-provisioning process for more consistent metadata and runtime behavior.
  • Bug Fixes

    • Added clearer warnings when field-group metadata is created but table provisioning cannot be completed.
    • Improved handling of localized field-group table updates and cleanup.

…ma service

Runtime registration and companion provisioning were private functions inside
the components dispatcher, which is why the two other create transports could
not reach the schema half and shipped a registry row describing a table that
was never created.

Both bodies move unchanged. Verified byte-identical against main; the only
difference is the depth of three dynamic-import paths, which the new location
changes.
… row

The REST route wrote a registry row and made no table, so it answered 201 for a
field group whose comp_<slug> did not exist and every later read and write to it
failed against the database.

FieldGroupMetadataService plans the DDL, applies it without throwing, and writes
the row carrying the outcome the apply reached. It is registered in DI rather
than built per request so one wrapper governs every caller, which is what lets a
migration lock enclose both halves. The route now reports the status instead of
answering an unqualified success for a create whose table was never made.
nextly.fieldGroups.create() and the mounted POST /api/field-groups route wrote a
registry row and no table, then answered success. The registry described a
comp_<slug> that did not exist and every read and write to that field group
failed against the database.

All three transports now go through FieldGroupMetadataService, so the table
change and the registry write are one operation wherever a create comes from,
and a create whose DDL failed says so instead of answering an unqualified
success.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex please review this PR

@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@greptileai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 59 minutes.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@mobeenabdullah, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 36 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c4de8738-de8f-415e-adc6-06b1f02be9c5

📥 Commits

Reviewing files that changed from the base of the PR and between be3117e and 4896d8b.

⛔ Files ignored due to path filters (1)
  • .changeset/field-group-transports-provision.md is excluded by !.changeset/**
📒 Files selected for processing (10)
  • packages/nextly/src/api/__tests__/field-group-route-delegates.test.ts
  • packages/nextly/src/api/field-groups.ts
  • packages/nextly/src/direct-api/__tests__/field-groups-table-name.test.ts
  • packages/nextly/src/direct-api/namespaces/field-groups.ts
  • packages/nextly/src/dispatcher/handlers/component-dispatcher.ts
  • packages/nextly/src/domains/field-groups/__tests__/field-group-create-transports.integration.test.ts
  • packages/nextly/src/domains/field-groups/services/__tests__/field-group-metadata-apply-contract.test.ts
  • packages/nextly/src/domains/field-groups/services/field-group-metadata-service.ts
  • packages/nextly/src/domains/field-groups/services/field-group-schema-service.ts
  • packages/nextly/src/shared/base-validator.ts
📝 Walkthrough

Walkthrough

Field-group creation now uses FieldGroupMetadataService across API, Direct API, and dispatcher paths. The service handles registry writes, dialect-specific table provisioning, runtime schema registration, and localized companion storage.

Changes

Field-group provisioning

Layer / File(s) Summary
Metadata service and table provisioning
packages/nextly/src/domains/field-groups/services/*
Adds FieldGroupMetadataService with migration statuses, DDL execution, registry persistence, runtime schema registration, and localized companion reconciliation.
API and Direct API integration
packages/nextly/src/api/..., packages/nextly/src/direct-api/..., packages/nextly/src/di/...
Routes field-group creation through the metadata service and returns provisioning warnings when table creation fails.
Dispatcher provisioning delegation
packages/nextly/src/dispatcher/...
Removes dispatcher-local provisioning and delegates component creation to the DI-registered metadata service.
Cross-transport integration validation
packages/nextly/src/domains/field-groups/__tests__/field-group-create-transports.integration.test.ts
Tests dispatcher and Direct API creation across database dialects, including registry rows and physical tables.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant API
  participant ComponentDispatcher
  participant FieldGroupMetadataService
  participant DatabaseAdapter
  participant FieldGroupRegistryService
  API->>FieldGroupMetadataService: createFieldGroup(input)
  ComponentDispatcher->>FieldGroupMetadataService: createFieldGroup(component metadata)
  FieldGroupMetadataService->>DatabaseAdapter: apply field-group DDL
  DatabaseAdapter-->>FieldGroupMetadataService: provisioning status
  FieldGroupMetadataService->>FieldGroupRegistryService: persist metadata record
  FieldGroupMetadataService-->>API: record and migrationStatus
  FieldGroupMetadataService-->>ComponentDispatcher: record and migrationStatus
Loading

Possibly related PRs

  • nextlyhq/nextly#556: Applies the same metadata-service and DI extraction pattern to Singles.
  • nextlyhq/nextly#423: Implements related migration, resume-state, locking, and reconciliation logic.
  • nextlyhq/nextly#382: Covers related localized companion-table provisioning and reconciliation.

Suggested labels: scope: core

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: table creation for every field-group creation transport.
Description check ✅ Passed The description explains the defect, design, affected transports, test coverage, verification results, and deliberate omission, so it is mostly complete.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/field-group-metadata-service

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.

@github-actions github-actions Bot added scope: core nextly type: docs Documentation only labels Aug 5, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 5, 2026

Copy link
Copy Markdown

Open in StackBlitz

@nextlyhq/adapter-drizzle

npm i https://pkg.pr.new/@nextlyhq/adapter-drizzle@4896d8b

@nextlyhq/adapter-mysql

npm i https://pkg.pr.new/@nextlyhq/adapter-mysql@4896d8b

@nextlyhq/adapter-postgres

npm i https://pkg.pr.new/@nextlyhq/adapter-postgres@4896d8b

@nextlyhq/adapter-sqlite

npm i https://pkg.pr.new/@nextlyhq/adapter-sqlite@4896d8b

@nextlyhq/admin

npm i https://pkg.pr.new/@nextlyhq/admin@4896d8b

@nextlyhq/admin-css

npm i https://pkg.pr.new/@nextlyhq/admin-css@4896d8b

@nextlyhq/blocks-engine

npm i https://pkg.pr.new/@nextlyhq/blocks-engine@4896d8b

@nextlyhq/blocks-react

npm i https://pkg.pr.new/@nextlyhq/blocks-react@4896d8b

create-nextly-app

npm i https://pkg.pr.new/create-nextly-app@4896d8b

nextly

npm i https://pkg.pr.new/nextly@4896d8b

@nextlyhq/plugin-form-builder

npm i https://pkg.pr.new/@nextlyhq/plugin-form-builder@4896d8b

@nextlyhq/plugin-page-builder

npm i https://pkg.pr.new/@nextlyhq/plugin-page-builder@4896d8b

@nextlyhq/plugin-sdk

npm i https://pkg.pr.new/@nextlyhq/plugin-sdk@4896d8b

@nextlyhq/plugin-seo

npm i https://pkg.pr.new/@nextlyhq/plugin-seo@4896d8b

@nextlyhq/storage-s3

npm i https://pkg.pr.new/@nextlyhq/storage-s3@4896d8b

@nextlyhq/storage-uploadthing

npm i https://pkg.pr.new/@nextlyhq/storage-uploadthing@4896d8b

@nextlyhq/storage-vercel-blob

npm i https://pkg.pr.new/@nextlyhq/storage-vercel-blob@4896d8b

@nextlyhq/ui

npm i https://pkg.pr.new/@nextlyhq/ui@4896d8b

commit: 4896d8b

@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: 1

Caution

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

⚠️ Outside diff range comments (1)
packages/nextly/src/dispatcher/handlers/component-dispatcher.ts (1)

244-249: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Distinguish failed from pending in the response message.

createFieldGroup returns three statuses. pending means no adapter was registered and the DDL was never run. failed means the DDL threw, the table was absent after the migration, or companion provisioning failed. The current message tells the admin to run migrations in both cases. For failed, running migrations is not the corrective action, and the message hides a real error.

♻️ Proposed message split
-      const message =
-        migrationStatus === "applied"
-          ? `Component "${b.slug}" created and table applied!`
-          : `Component "${b.slug}" created. Run migrations to apply the table.`;
+      // Each status has its own corrective action: `pending` waits for a migration run, while
+      // `failed` means the DDL was attempted and did not land, so the server logs hold the cause.
+      const message =
+        migrationStatus === "applied"
+          ? `Component "${b.slug}" created and table applied!`
+          : migrationStatus === "failed"
+            ? `Component "${b.slug}" created, but its table could not be provisioned. Check the server logs and retry.`
+            : `Component "${b.slug}" created. Run migrations to apply the table.`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nextly/src/dispatcher/handlers/component-dispatcher.ts` around lines
244 - 249, Update the message selection near the migrationStatus check to handle
all three statuses returned by createFieldGroup: retain the applied success
message, keep the run-migrations guidance only for pending, and add a distinct
failure message for failed that indicates table or companion provisioning failed
without directing the admin to run migrations.
🧹 Nitpick comments (9)
packages/nextly/src/api/__tests__/field-group-route-delegates.test.ts (1)

101-107: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the status code in the failed-provisioning test.

expect(body.message).not.toBe("Field group created.") passes for any response, including an error response produced before the route reaches createFieldGroup. requireBuilderEnabled("create-component") is not mocked in this file, so that path is reachable. Add a status assertion to pin the documented behavior: a failed provisioning still returns 201 with the created record.

💚 Proposed addition
   const body = (await response.json()) as { message?: string };

+  expect(response.status).toBe(201);
   expect(body.message).not.toBe("Field group created.");
   expect(body.message).toContain("could not be provisioned");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nextly/src/api/__tests__/field-group-route-delegates.test.ts` around
lines 101 - 107, Update the failed-provisioning test around the response body
assertion to also verify that the response status is 201, preserving the
existing message checks and confirming the documented successful creation
response when provisioning fails.
packages/nextly/src/di/register.ts (1)

338-338: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add the one-line doc the sibling entry carries.

singleMetadataService on line 337 documents that it owns the table change together with the registry write. Give fieldGroupMetadataService the equivalent line so the two parallel services read the same way.

♻️ Proposed change
+  /** Owns a Field Group's table change together with the registry write that records it. */
   fieldGroupMetadataService: FieldGroupMetadataService;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nextly/src/di/register.ts` at line 338, Add the same one-line
ownership documentation used by singleMetadataService immediately above
fieldGroupMetadataService, stating that it owns the table change together with
the registry write.
packages/nextly/src/domains/field-groups/services/field-group-metadata-service.ts (3)

166-173: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use describeError for these operator-facing logs.

Both catch blocks in this method (lines 166-173 and 216-223) format the error with error.message. That drops the cause chain, which is the part that identifies a driver failure. This repository uses describeError(error) for operator-facing logs and diagnostics. Neither message is used as a control-flow predicate, so describeError is the right helper here.

Based on learnings that describeError(error) should be used for operator-facing logs and diagnostics, while immediateMessage(error) is reserved for message-based control-flow predicates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/nextly/src/domains/field-groups/services/field-group-metadata-service.ts`
around lines 166 - 173, Update both catch blocks in the field-group metadata
service method to use the repository’s describeError(error) helper when
formatting operator-facing logger.error messages, replacing the current
error.message/String formatting while preserving each existing log context and
return behavior.

Source: Learnings


158-159: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the double assertion with a real type relation.

input.fields as unknown as FieldDefinition[] erases the difference between FieldConfig[] and FieldDefinition[] instead of resolving it. If the two shapes are compatible, widen the reconcileComponentCompanion parameter or declare the relation once in a shared type. If they are not compatible, the cast hides a real mismatch that reaches the companion DDL planner.

As per coding guidelines: "Do not use as any, @ts-expect-error, or eslint-disable directives to silence errors; fix the cause with real types, guards, or generics."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/nextly/src/domains/field-groups/services/field-group-metadata-service.ts`
around lines 158 - 159, Replace the double assertion in the metadata service
with a real type relationship: inspect FieldConfig and FieldDefinition and
either widen reconcileComponentCompanion’s parameter or define a shared
compatible type, preserving type safety through the companion DDL planner. If
the shapes are incompatible, add the necessary conversion or validation instead
of casting.

Source: Coding guidelines


4-13: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Comments across four files document the previous defect instead of the current behavior. Each of these blocks explains what the old code did wrong and how this PR fixed it. Repo convention keeps comments on current behavior and non-obvious rationale, and excludes historical remediation. Keep the parts that state a present-tense contract, such as the atomicity and ordering guarantees.

  • packages/nextly/src/domains/field-groups/services/field-group-metadata-service.ts#L4-L13: remove the "Why this exists" narrative about the three transports and the dispatcher-private helper; keep the atomicity and ordering sections at lines 15-30.
  • packages/nextly/src/api/field-groups.ts#L45-L51: drop the sentences describing what the route "used to" do; keep the statement that the service owns the table and the registry row together.
  • packages/nextly/src/api/__tests__/field-group-route-delegates.test.ts#L1-L14: reduce the header to what the file verifies, which is delegation, and to the pointer at the integration test that verifies provisioning.
  • packages/nextly/src/di/registrations/register-components.ts#L36-L40: remove the closing sentence about the transports being "allowed to disagree"; state the present rationale for the singleton registration.

Based on learnings that source-file comments should document behavior and rationale for non-obvious logic only, and should avoid references to tasks, plans, conversations, review findings, or historical remediation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/nextly/src/domains/field-groups/services/field-group-metadata-service.ts`
around lines 4 - 13, Remove historical remediation commentary while preserving
current behavior and rationale: in
packages/nextly/src/domains/field-groups/services/field-group-metadata-service.ts
lines 4-13, delete the “Why this exists” transport/dispatcher narrative and
retain the atomicity and ordering sections; in
packages/nextly/src/api/field-groups.ts lines 45-51, remove “used to” behavior
and retain that the service owns table and registry-row creation; in
packages/nextly/src/api/__tests__/field-group-route-delegates.test.ts lines
1-14, keep only the delegation purpose and integration-test pointer; in
packages/nextly/src/di/registrations/register-components.ts lines 36-40, remove
the transports-disagreement closing sentence and state the current rationale for
singleton registration.

Sources: Coding guidelines, Learnings

packages/nextly/src/domains/field-groups/services/field-group-table-provisioning.ts (1)

11-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

A domain module now depends on the dispatcher layer.

This file moved into domains/field-groups/services/ but still imports getConfigFromDI and getSchemaRegistryFromDI from ../../../dispatcher/helpers/di. The dependency now points from the domain layer into the transport layer, which is the reverse of the direction the rest of this stack uses. Consider moving those two DI accessors to a shared location so the domain module does not reach into dispatcher/.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/nextly/src/domains/field-groups/services/field-group-table-provisioning.ts`
around lines 11 - 14, Move getConfigFromDI and getSchemaRegistryFromDI out of
dispatcher/helpers/di into an appropriate shared infrastructure or DI module,
then update field-group table provisioning and existing consumers to import them
from that shared location. Preserve both accessors’ behavior while removing the
domain module’s dependency on dispatcher/.
packages/nextly/src/direct-api/__tests__/field-groups-table-name.test.ts (1)

29-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Narrow the registry double instead of asserting through unknown.

registry as unknown as FieldGroupRegistryService hides which members the service actually needs. Type the double against the surface it must satisfy, so a future constructor dependency in FieldGroupMetadataService fails at compile time rather than at runtime.

♻️ Proposed change
-    const registry = { registerComponent };
+    const registry: Pick<FieldGroupRegistryService, "registerComponent"> = {
+      registerComponent,
+    };
     const ctx = {
       fieldGroupRegistryService: registry,
       fieldGroupMetadataService: new FieldGroupMetadataService(
-        registry as unknown as FieldGroupRegistryService,
+        registry as FieldGroupRegistryService,
         logger
       ),

As per coding guidelines: "Do not use as any, @ts-expect-error, or eslint-disable directives to silence errors; fix the cause with real types, guards, or generics."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nextly/src/direct-api/__tests__/field-groups-table-name.test.ts`
around lines 29 - 38, Update the registry double used to construct
FieldGroupMetadataService so it is typed against the minimal
FieldGroupRegistryService surface required by that constructor, removing the
unknown-based double assertion. Preserve the existing registerComponent behavior
while ensuring future required registry members produce compile-time errors.

Source: Coding guidelines

packages/nextly/src/api/field-groups.ts (1)

225-233: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider reporting the provisioning failure through the warnings channel.

respondMutation already carries a warnings array for exactly this case: a durable row plus a side-effect phase that failed. Encoding the failure only into message means a client must string-match to detect it. The record also carries migrationStatus, so the information is available, but a warning entry would make the failure machine-readable and consistent with the other mutation routes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nextly/src/api/field-groups.ts` around lines 225 - 233, Update the
field-group creation response around respondMutation so a non-"applied"
migrationStatus also adds a machine-readable warning through its warnings
option, while retaining the existing message and 201 status. Keep the warning
absent for successfully applied migrations and follow the warning structure used
by other mutation routes.
packages/nextly/src/dispatcher/handlers/component-dispatcher.ts (1)

216-227: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Comments across this PR explain the previous defect instead of the current code. Each new comment below describes what the old implementation did wrong and how this PR repairs it. Repository guidelines and prior learnings require comments that state current behavior and the rationale for non-obvious logic, with no reference to past findings or remediation. Apply the same rule at each site.

  • packages/nextly/src/dispatcher/handlers/component-dispatcher.ts#L216-L227: state that one service owns both the DDL and the registry write, and drop the sentence about this handler previously holding the DDL.
  • packages/nextly/src/dispatcher/handlers/component-dispatcher.ts#L133-L135: state where the helpers live and why every transport uses them, and drop the "now live" comparison with their previous location.
  • packages/nextly/src/direct-api/namespaces/field-groups.ts#L176-L178: state that the service writes the row with the provisioning outcome, and drop the sentence about this path previously answering success.
  • packages/nextly/src/domains/field-groups/__tests__/field-group-create-transports.integration.test.ts#L1-L29: keep the invariant under test, the reason the REST transport is covered elsewhere, and the per-dialect skip rule; drop "The gap this closes" and the coverage-history narrative.

Based on learnings: "source-file comments should document the code's behavior and the rationale for non-obvious logic only. Avoid comments that reference tasks/plans, conversations, review findings, or historical remediation." As per coding guidelines: "Every code change must include a comment explaining what the code does and why; comments must not reference tasks, plans, conversations, or review findings."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nextly/src/dispatcher/handlers/component-dispatcher.ts` around lines
216 - 227, Update the comments at
packages/nextly/src/dispatcher/handlers/component-dispatcher.ts#L216-L227 to
describe that one service owns both DDL and registry writes, removing historical
wording; at
packages/nextly/src/dispatcher/handlers/component-dispatcher.ts#L133-L135,
explain where the helpers live and why every transport uses them without
comparing their former location; at
packages/nextly/src/direct-api/namespaces/field-groups.ts#L176-L178, state that
the service writes the row with the provisioning outcome and remove prior-path
history; and at
packages/nextly/src/domains/field-groups/__tests__/field-group-create-transports.integration.test.ts#L1-L29,
retain the tested invariant, separate REST coverage rationale, and per-dialect
skip rule while removing coverage-gap and history narrative.

Sources: Coding guidelines, Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/nextly/src/di/registrations/register-components.ts`:
- Around line 36-49: Add the migration lock around the complete create flow in
FieldGroupMetadataService.createFieldGroup, covering both field-group table
changes and the fieldGroupRegistryService.registerComponent write, so concurrent
creates for the same slug cannot overlap. Ensure the singleton factory in
register-components.ts still supplies the shared service instance, and update
its explanatory comment only if the lock is implemented elsewhere.

---

Outside diff comments:
In `@packages/nextly/src/dispatcher/handlers/component-dispatcher.ts`:
- Around line 244-249: Update the message selection near the migrationStatus
check to handle all three statuses returned by createFieldGroup: retain the
applied success message, keep the run-migrations guidance only for pending, and
add a distinct failure message for failed that indicates table or companion
provisioning failed without directing the admin to run migrations.

---

Nitpick comments:
In `@packages/nextly/src/api/__tests__/field-group-route-delegates.test.ts`:
- Around line 101-107: Update the failed-provisioning test around the response
body assertion to also verify that the response status is 201, preserving the
existing message checks and confirming the documented successful creation
response when provisioning fails.

In `@packages/nextly/src/api/field-groups.ts`:
- Around line 225-233: Update the field-group creation response around
respondMutation so a non-"applied" migrationStatus also adds a machine-readable
warning through its warnings option, while retaining the existing message and
201 status. Keep the warning absent for successfully applied migrations and
follow the warning structure used by other mutation routes.

In `@packages/nextly/src/di/register.ts`:
- Line 338: Add the same one-line ownership documentation used by
singleMetadataService immediately above fieldGroupMetadataService, stating that
it owns the table change together with the registry write.

In `@packages/nextly/src/direct-api/__tests__/field-groups-table-name.test.ts`:
- Around line 29-38: Update the registry double used to construct
FieldGroupMetadataService so it is typed against the minimal
FieldGroupRegistryService surface required by that constructor, removing the
unknown-based double assertion. Preserve the existing registerComponent behavior
while ensuring future required registry members produce compile-time errors.

In `@packages/nextly/src/dispatcher/handlers/component-dispatcher.ts`:
- Around line 216-227: Update the comments at
packages/nextly/src/dispatcher/handlers/component-dispatcher.ts#L216-L227 to
describe that one service owns both DDL and registry writes, removing historical
wording; at
packages/nextly/src/dispatcher/handlers/component-dispatcher.ts#L133-L135,
explain where the helpers live and why every transport uses them without
comparing their former location; at
packages/nextly/src/direct-api/namespaces/field-groups.ts#L176-L178, state that
the service writes the row with the provisioning outcome and remove prior-path
history; and at
packages/nextly/src/domains/field-groups/__tests__/field-group-create-transports.integration.test.ts#L1-L29,
retain the tested invariant, separate REST coverage rationale, and per-dialect
skip rule while removing coverage-gap and history narrative.

In
`@packages/nextly/src/domains/field-groups/services/field-group-metadata-service.ts`:
- Around line 166-173: Update both catch blocks in the field-group metadata
service method to use the repository’s describeError(error) helper when
formatting operator-facing logger.error messages, replacing the current
error.message/String formatting while preserving each existing log context and
return behavior.
- Around line 158-159: Replace the double assertion in the metadata service with
a real type relationship: inspect FieldConfig and FieldDefinition and either
widen reconcileComponentCompanion’s parameter or define a shared compatible
type, preserving type safety through the companion DDL planner. If the shapes
are incompatible, add the necessary conversion or validation instead of casting.
- Around line 4-13: Remove historical remediation commentary while preserving
current behavior and rationale: in
packages/nextly/src/domains/field-groups/services/field-group-metadata-service.ts
lines 4-13, delete the “Why this exists” transport/dispatcher narrative and
retain the atomicity and ordering sections; in
packages/nextly/src/api/field-groups.ts lines 45-51, remove “used to” behavior
and retain that the service owns table and registry-row creation; in
packages/nextly/src/api/__tests__/field-group-route-delegates.test.ts lines
1-14, keep only the delegation purpose and integration-test pointer; in
packages/nextly/src/di/registrations/register-components.ts lines 36-40, remove
the transports-disagreement closing sentence and state the current rationale for
singleton registration.

In
`@packages/nextly/src/domains/field-groups/services/field-group-table-provisioning.ts`:
- Around line 11-14: Move getConfigFromDI and getSchemaRegistryFromDI out of
dispatcher/helpers/di into an appropriate shared infrastructure or DI module,
then update field-group table provisioning and existing consumers to import them
from that shared location. Preserve both accessors’ behavior while removing the
domain module’s dependency on dispatcher/.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b18814c4-773c-41f4-b97f-e9b8f4df7b97

📥 Commits

Reviewing files that changed from the base of the PR and between 8b7ce78 and be3117e.

⛔ Files ignored due to path filters (1)
  • .changeset/field-group-transports-provision.md is excluded by !.changeset/**
📒 Files selected for processing (15)
  • packages/nextly/src/api/__tests__/field-group-route-delegates.test.ts
  • packages/nextly/src/api/field-groups.ts
  • packages/nextly/src/di/register.ts
  • packages/nextly/src/di/registrations/register-components.ts
  • packages/nextly/src/direct-api/__tests__/field-groups-table-name.test.ts
  • packages/nextly/src/direct-api/namespaces/context.ts
  • packages/nextly/src/direct-api/namespaces/field-groups.ts
  • packages/nextly/src/direct-api/nextly.ts
  • packages/nextly/src/dispatcher/handlers/__tests__/component-dispatcher-ddl.test.ts
  • packages/nextly/src/dispatcher/handlers/__tests__/component-dispatcher-shapes.test.ts
  • packages/nextly/src/dispatcher/handlers/component-dispatcher.ts
  • packages/nextly/src/dispatcher/helpers/di.ts
  • packages/nextly/src/domains/field-groups/__tests__/field-group-create-transports.integration.test.ts
  • packages/nextly/src/domains/field-groups/services/field-group-metadata-service.ts
  • packages/nextly/src/domains/field-groups/services/field-group-table-provisioning.ts

Comment thread packages/nextly/src/di/registrations/register-components.ts
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex please review this PR

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: be3117eda0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/nextly/src/domains/field-groups/services/field-group-metadata-service.ts Outdated
Comment thread packages/nextly/src/api/field-groups.ts
…und the slug

The table-name conflict check lived in the dispatcher, so the Direct API and
the mounted route reached the DDL without it: a slug normalising onto a table
another field group owns rebound that field group's storage to the new field
list before the registry rejected anything. It now runs inside the service
that owns the DDL, and the dispatcher's copy is gone.

The post-migration tableExists probe sat outside the catch, so a transient
query failure rejected the apply and skipped the registry write, orphaning the
table it had just made. It now shares the statements' catch and records failed.

The route's slug bound was 255 while the rest of the product validates 50; a
longer slug becomes an identifier PostgreSQL truncates and MySQL rejects. The
bound is now named once in base-validator and used by both.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex please review this PR

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 27369a75cd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

… written

Two creates whose slugs normalise to one table can both pass the ownership
check, because a read cannot exclude a write that has not happened yet. The
registry's table_name unique index is what separates them, and that only helps
if nothing irreversible has happened first.

Binding ran before the insert, so the request that loses rebound the shared
table to its own field list and only then failed, leaving the winner reading
through a schema that does not describe it until the process restarts. The bind
now runs after the insert that would reject it.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex please review this PR

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2855850a8a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex please review this PR

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2855850a8a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/nextly/src/api/field-groups.ts Outdated
…nerates

The 50-character bound measured the slug. The longest thing the database sees
is idx_ + comp_ + slug + _parent, sixteen characters longer, so a 50-character
slug produced a 66-character index name: MySQL rejects past 64, leaving the
table created, the index creation failed, and the field group recorded failed
with an unbound table.

The bound is now derived from the same constants the names are built from, so
changing a prefix moves it rather than leaving a stale number. The budget is
PostgreSQL's 63 rather than MySQL's 64 because it is tighter and its failure is
worse: PostgreSQL truncates an over-long identifier instead of rejecting it.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex please review this PR

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab8b2d4262

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/nextly/src/direct-api/namespaces/field-groups.ts
…sport

A slug bound cannot make this safe on its own. A field's index is named
idx_<tableName>_<columnName>, so the longest identifier depends on the slug AND
the longest indexed field name — two independent inputs, neither constraining
the other. A slug at its own bound paired with authorId still generates a
66-character index.

The check is therefore over the names themselves, enumerated by the service
that builds them, and it runs before any DDL. Without it the table and its
parent index are created, the field index fails, and the caller receives a
record whose migration is recorded failed: a field group that exists and
cannot be queried, from a request that returned a success shape.

It lives in the metadata service rather than in a transport for the same reason
the ownership check does — the mounted route bounded its slug and the other two
transports did not.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex please review this PR

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b2a655347

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/nextly/src/domains/field-groups/services/field-group-schema-service.ts Outdated
Comment thread packages/nextly/src/domains/field-groups/services/field-group-schema-service.ts Outdated
Comment thread packages/nextly/src/domains/field-groups/services/field-group-schema-service.ts Outdated
Comment thread packages/nextly/src/direct-api/namespaces/field-groups.ts Outdated
The previous check walked the field list again to work out which names a create
would generate. That was a second transcription of rules the renderer already
applies, and it got three of them wrong: it missed unique indexes, which are
named uq_ and need no index flag; it missed the column names themselves, which
are identifiers too; and it counted an index for localized fields that the
renderer never emits, refusing valid creates over a name that does not exist.

It now scans the rendered statements. Every identifier this service emits is
quoted, and no dialect uses that character for string literals, so the quoted
tokens are identifiers and nothing else. A check over the output cannot drift
from it.

The failure message no longer tells callers to retry. A failed create keeps its
registry row, and that row holds the slug, so a retry is refused as a duplicate
through either transport; it now says to delete the field group first.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex please review this PR

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: 4896d8b52b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@mobeenabdullah
mobeenabdullah merged commit ef2ffdc into main Aug 10, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: core nextly type: docs Documentation only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant