Skip to content

feat(tailordb,resolver)!: object-literal descriptor API and record-level hooks/validate#905

Draft
dqn wants to merge 75 commits into
mainfrom
feat/object-literal-descriptor-api
Draft

feat(tailordb,resolver)!: object-literal descriptor API and record-level hooks/validate#905
dqn wants to merge 75 commits into
mainfrom
feat/object-literal-descriptor-api

Conversation

@dqn
Copy link
Copy Markdown
Contributor

@dqn dqn commented Apr 3, 2026

Add createTable and resolver field descriptors as an alternative object-literal syntax for defining TailorDB types and resolver input/output fields. Move TailorDB hooks and validators from field level to record level, emitted per-field at the wire layer so override-populated fields are optional in GraphQL inputs.

Usage

// TailorDB type with createTable (object-literal fields)
const order = createTable(
  "Order",
  {
    name: { kind: "string" },
    quantity: { kind: "int", index: true },
    status: { kind: "enum", values: ["pending", "shipped"] },
    address: {
      kind: "object",
      fields: {
        city: { kind: "string" },
        zip: { kind: "string" },
      },
    },
    fullAddress: { kind: "string" },
    ...timestampFields(),
  },
  {
    permission: unsafeAllowAllTypePermission,
    hooks: {
      create: ({ data }) => ({
        fullAddress: `${data.address.city} ${data.address.zip}`,
      }),
      update: ({ data }) => ({
        fullAddress: `${data.address.city} ${data.address.zip}`,
      }),
    },
    validate: [
      [({ data }) => data.quantity >= 0, "Quantity must be non-negative"],
      ({ data }) => data.name.length > 0,
    ],
  },
);

// Resolver with descriptor fields (mixable with fluent API)
const resolver = createResolver({
  name: "addNumbers",
  operation: "query",
  input: {
    a: { kind: "int" },
    b: { kind: "int" },
  },
  output: { kind: "int" },
  body: ({ input }) => input.a + input.b,
});

Main Changes

  • Add createTable object-literal API for TailorDB type definitions with full descriptor support (all scalar kinds, enum, nested object, serial, relation, permissions, indices)
  • Add ResolverFieldDescriptor for resolver input/output fields, allowing { kind: "string" } syntax alongside fluent t.string() fields
  • Breaking: Remove field-level .hooks() and .validate() from TailorDB field builders and descriptors. Configure hooks/validators at the record level via the .hooks(...) / .validate(...) builder methods (or via the third options argument of createTable).
  • Breaking: Record-level hooks return an object listing only the keys to override — omitted fields keep their incoming values. The SDK statically extracts the override key set from the returned object literal and expands each entry into a field-level FieldHook on the affected field, so the platform-generated GraphQL CreateInput treats those fields as optional.
  • Emit record-level hooks as per-field FieldHook (bound to _data); keep record-validate as a single type-level type_validate returning a map (bound to _input), per platform contract
  • Add generated metadata flag to timestamp fields so the Kysely plugin emits Generated<Timestamp> (insert-generated only) and the seed hook auto-fills values
  • Extract a shared resolver/descriptor.ts for ResolverFieldDescriptor resolution; createTable carries its own kindToFieldType table aligned with the resolver one
  • Runtime/parse-time validation: reject unknown descriptor kinds, unknown descriptor option keys, malformed passthrough fields, invalid decimal scale, empty/duplicate enum values, and id overrides in record-level hooks
  • Tighten the deploy/migrate pipeline for the new emit shape: route the deploy manifest through a snapshot-manifest layer, detect record-level validator diffs, detect remote type_validate drift in the snapshot check, restore removed relationships during the Pre-phase, and tighten migration-script subcommand + deploy logging
  • Add Product type to example/ using the new createTable API, and regenerate migrations (0004 / 0005) to match the new emit shape

Notes

  • Record-level hook bodies must end in a static object literal (({ data }) => ({ k1: v1, k2: v2 })) so the override keys can be statically resolved via oxc-parser. Branched returns, spread (...data), computed keys, getter/setter properties, and method shorthand all throw at parse time — the user must list the overridden keys explicitly with plain key: value form.
  • Record-level hooks cannot override the synthetic id field; the deploy manifest strips id from per-field hooks, so a hook returning { id: ... } is rejected at parse time to keep local and deployed behavior aligned.
  • type_validate is still emitted as a single type-level script returning a map; only type_hook was replaced by per-field hooks.

dqn added 11 commits April 1, 2026 18:22
…ver descriptor support

Add createTable() and timestampFields() as an alternative to the fluent
db.type() API for defining TailorDB types using plain object literals.
This is a reworked version of the closed PR #645 (createType), renamed
to createTable.

Extend createResolver() to accept object-literal field descriptors
({ kind: "string" }) alongside the existing fluent t.string() API in
both input and output parameters. Fluent and descriptor styles can be
mixed freely.
…date decimal scale

- Tighten isResolverFieldDescriptor to check kind is a known string value,
  preventing false positives when output records contain a field named "kind"
- Add decimal scale validation (integer 0-12) in createTable to match db.decimal()
…lverFieldMap, add boundary tests

- Export KindToFieldType from descriptor.ts, remove duplicate in resolver.ts
- Move isTailorField from closure to module-level function
- Replace two-pass iteration in resolveResolverFieldMap with single-pass loop
- Add decimal scale boundary value tests (0 and 12) for createTable
Add runtime guards so that untyped callers (JS, JSON-driven schemas)
get a clear error instead of silently producing fields with undefined
type when passing an invalid kind like "strng".
…hook typing trade-off

Reject enum descriptors that omit the required `values` array at
runtime, preventing permissive fields from being silently created by
untyped callers.  Document the accepted trade-off that descriptor hook
callbacks receive the base scalar type rather than the final output
type adjusted for optional/array.
…sthrough fields

ValidateHookTypes now checks against DescriptorBaseOutput (base scalar)
instead of DescriptorOutput (with array/optional applied), matching the
IndexableOptions typing contract. Also reject plain objects without
`kind` or `type` that would silently pass through as TailorDBField.
…and metadata

Strengthen the passthrough field check to verify both `type` (string)
and `metadata` (object) properties, catching plain objects that are
neither descriptors nor real field instances.  Apply the same guard
to both resolver and tailordb descriptor paths.
…vious comments

Delegate field resolution in resolveResolverFieldMap to
resolveResolverField instead of inlining the same validation logic.
Remove self-evident WHAT comments from createResolver and resolveOutput.
Also fix pre-existing import order in processOrder.ts test fixture.
The import-x/order rule changed after merging main, making the
original order (date-fns before @tailor-platform/sdk) correct again.
@dqn dqn requested review from remiposo and toiroakr as code owners April 3, 2026 07:11
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 3, 2026

🦋 Changeset detected

Latest commit: 532df7f

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

This PR includes changesets to release 2 packages
Name Type
@tailor-platform/sdk Major
@tailor-platform/create-sdk Major

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

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 3, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@tailor-platform/create-sdk@905

commit: 216ef75

@claude
Copy link
Copy Markdown

claude Bot commented Apr 3, 2026

Docs Consistency Check

No inconsistencies found between documentation and implementation.

Checked areas:

TailorDB Documentation (packages/sdk/docs/services/tailordb.md):

  • createTable API signature matches implementation (name, descriptors, options)
  • timestampFields() helper properly documented (returns createdAt and updatedAt descriptors)
  • Object-literal descriptor syntax documented with all field kinds
  • Record-level hooks documented with correct signature
  • Hook override-only semantics clearly explained (lines 329-364)
  • Record-level validate documented with correct signature
  • Breaking change clearly noted: field-level hooks and validate removed (line 84)
  • Guidance provided for when to use db.type() vs createTable (lines 88-91)

Resolver Documentation (packages/sdk/docs/services/resolver.md):

  • Object-literal descriptor syntax documented (lines 121-136)
  • Supported descriptor options listed: optional, array, description, validate, typeName
  • Mixing fluent and descriptor fields documented (lines 138-150)
  • Examples match test implementations

CLAUDE.md:

  • Updated to mention createTable alongside db.type() (line 44)
  • Points to example/ for working implementations

Examples:

  • example/tailordb/product.ts demonstrates createTable with descriptors
  • example/tailordb/customer.ts demonstrates record-level hooks and validate
  • example/tailordb/file.ts includes migration note about removed field-level validate
  • Note: Resolver examples use fluent API only (descriptor API is optional and well-tested)

Changeset:

  • .changeset/object-literal-descriptor-api.md accurately describes breaking changes
  • Migration guidance provided for moving to record-level hooks/validate

Implementation Verification:

  • createTable signature in createTable.ts matches documented signature
  • CreateTableOptions type includes all documented options: description, pluralForm, features, indexes, files, permission, gqlPermission, plugins, hooks, validate
  • ResolverFieldDescriptor type in descriptor.ts supports all documented kinds and options
  • Tests in createTable.test.ts and resolver.test.ts validate documented behaviors
  • Runtime validation properly rejects field-level hooks/validate (lines 277-300 in createTable.ts)

Re-run this check by adding the docs-check label to the PR.


@claude
Copy link
Copy Markdown

claude Bot commented Apr 3, 2026

📖 Docs Consistency Check

⚠️ Inconsistencies Found

File Issue Suggested Fix
packages/sdk/docs/services/tailordb.md Missing createTable() API documentation Add section documenting the object-literal descriptor API as an alternative to db.type()
packages/sdk/docs/services/tailordb.md Missing timestampFields() helper Document the timestampFields() helper function and its usage
packages/sdk/docs/services/resolver.md Missing descriptor syntax for input/output fields Add section showing { kind: "string" } syntax alongside fluent API examples
CLAUDE.md Code Patterns section only mentions db.type() Add createTable() as an alternative pattern with reference to examples
example/ directory No examples using new APIs Add at least one example file demonstrating createTable() and resolver descriptor syntax

Details

1. TailorDB createTable() API (packages/sdk/docs/services/tailordb.md)

What the implementation does:

  • packages/sdk/src/configure/services/tailordb/createTable.ts:469-504 - Exports createTable() function that accepts object-literal field descriptors as an alternative to the fluent db.type() API
  • packages/sdk/src/configure/services/index.ts:4 - Exports createTable from the main SDK package
  • JSDoc example in the implementation shows:
    export const user = createTable("User", {
      name: { kind: "string" },
      email: { kind: "string", unique: true },
      role: { kind: "enum", values: ["MANAGER", "STAFF"] },
      ...timestampFields(),
    });

What the documentation says:

  • The TailorDB documentation only documents db.type() with the fluent API
  • No mention of createTable() anywhere in the docs
  • The "Type Definition" section (lines 17-61) only shows the db.type() pattern

Impact:
Users won't know that the object-literal descriptor API exists as an alternative style for defining TailorDB types.

2. TailorDB timestampFields() helper (packages/sdk/docs/services/tailordb.md)

What the implementation does:

  • packages/sdk/src/configure/services/tailordb/createTable.ts:516-530 - Exports timestampFields() helper that returns standard createdAt/updatedAt fields with auto-hooks
  • packages/sdk/src/configure/services/index.ts:5 - Exports timestampFields from the main SDK package
  • Can be used with both createTable() and db.type() via spread syntax

What the documentation says:

  • The docs show ...db.fields.timestamps() (line 357-358) as the pattern for timestamp fields
  • No mention of timestampFields() helper

Impact:
Users using createTable() won't know about the timestampFields() helper designed for the descriptor API. The existing db.fields.timestamps() is for the fluent API.

3. Resolver descriptor syntax (packages/sdk/docs/services/resolver.md)

What the implementation does:

  • packages/sdk/src/configure/services/resolver/descriptor.ts:1-212 - Implements ResolverFieldDescriptor type system supporting { kind: "string" } syntax
  • packages/sdk/src/configure/services/resolver/resolver.ts:79-82 - Documents that input/output fields accept both fluent API and object-literal descriptors, and both can be mixed
  • JSDoc example in resolver.ts:106-116 shows:
    input: {
      a: { kind: "int", description: "First number" },
      b: { kind: "int", description: "Second number" },
    },
    output: { kind: "int", description: "Sum" },

What the documentation says:

  • packages/sdk/docs/services/resolver.md:84-142 - Only shows fluent API examples with t.string(), t.int(), etc.
  • No mention that descriptor syntax { kind: "int" } is supported
  • No examples mixing both styles

Impact:
Users won't know they can use descriptor syntax in resolvers, which provides a more concise alternative for simple fields and matches the TailorDB createTable() style.

4. CLAUDE.md Code Patterns section

What the implementation does:

  • Adds createTable as a new exported API for defining TailorDB types

What CLAUDE.md says:

  • Lines 43: "Model definitions with db.type()" - only mentions the fluent API
  • No mention of createTable() as an alternative pattern

Impact:
Claude Code won't know about the new API when helping users write TailorDB models, and won't suggest it as an option.

5. Example files

What the implementation does:

  • Adds fully functional createTable() and descriptor APIs

What the examples show:

  • example/tailordb/*.ts - All examples use db.type() fluent API only
  • example/resolvers/*.ts - All examples use t.string() fluent API only
  • No examples demonstrate the new descriptor syntax

Impact:
Users learning from examples won't see the descriptor API in action. The example/ directory is specifically referenced in CLAUDE.md as the source for "working implementations of all patterns."

Recommended Actions

  1. Add createTable() section to TailorDB docs - Document the object-literal API with full examples showing all field descriptor types (scalar, enum, object, relations, hooks, validation, etc.)

  2. Document timestampFields() helper - Add to TailorDB docs, likely in a "Common Fields" or "Helper Functions" section, showing it works with both createTable() and spread into db.type()

  3. Add descriptor syntax section to Resolver docs - Show examples of { kind: "string" } syntax for input/output fields, demonstrate mixing with fluent API

  4. Update CLAUDE.md - Add createTable to the Code Patterns section as an alternative to db.type(), update the reference to mention both APIs

  5. Add example files - Create at least one TailorDB type using createTable() and one resolver using descriptor syntax in the example/ directory


@github-actions

This comment has been minimized.

Cover pluralForm (string and tuple), description, features, and
gqlPermission options that were missing from the test suite.
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 7 additional findings.

Open in Devin Review

@github-actions

This comment has been minimized.

dqn added 2 commits April 3, 2026 16:42
Document the object-literal API (createTable, timestampFields) in
tailordb.md and resolver field descriptors in resolver.md. Update
CLAUDE.md code patterns to mention both API styles.
Demonstrate the object-literal descriptor API with a Product model
that includes enum, relation, timestamps, and permissions.
@toiroakr
Copy link
Copy Markdown
Contributor

toiroakr commented Apr 3, 2026

Hook callback typing in descriptors uses the base scalar type (e.g. string, number) rather than the final output type adjusted for optional/array, due to combinatorial type explosion. Use db.*() fluent API when precise hook typing matters

This is a major issue...
I wonder what we should do... 🤔

@dqn
Copy link
Copy Markdown
Contributor Author

dqn commented Apr 4, 2026

Addressed in f987b90 and 47ab17c:

  • Added createTable and timestampFields() documentation to tailordb.md
  • Added descriptor syntax documentation to resolver.md
  • Updated CLAUDE.md code patterns
  • Added example/tailordb/product.ts demonstrating the new API

dqn added 3 commits April 4, 2026 17:22
Descriptor inline hooks now receive the array output type for array
fields (e.g. Hook<unknown, string[]> instead of Hook<unknown, string>).

- Introduce ScalarOrArrayHooks<O> discriminated union that narrows
  hooks to Hook<unknown, O> for scalar and Hook<unknown, O[]> for array
- Unify ValidatedDescriptors into a single mapped type to avoid
  combinatorial type explosion with the doubled descriptor union
- Compute DescriptorHookOutput directly from field properties instead
  of intersecting with the FieldDescriptor union
- Keep validate callbacks at base scalar type to preserve contextual
  typing for inline lambdas
…ping

TailorAnyDBField in FieldEntry union prevented TypeScript from narrowing
FieldDescriptor during generic inference, causing inline hook callbacks
to lose contextual typing (value resolved to any). Add a FieldDescriptor-only
overload that TypeScript tries first, restoring correct type resolution
for inline scalar, array, and datetime hooks.
…nd tests

Add tests showing that inline enum descriptor hooks cannot narrow
value to the literal union (TS reverse-inference limitation), and
document the two working workarounds: fluent API db.enum().hooks()
and type-level options.hooks.<field>.
@dqn
Copy link
Copy Markdown
Contributor Author

dqn commented Apr 4, 2026

Status: Inline hook typing improvements

What changed

  • Hook typing now resolves per-field output type: scalar fields get Hook<O>, array fields get Hook<O[]> (PR body noted this as a known limitation)
  • Added overload for inline hook contextual typing (string/int/float/etc. inline callbacks now auto-resolve)
  • Inline enum hooks remain a known limitation (see below)

Known limitation: inline enum hooks

Inline enum descriptor hooks ({ kind: "enum", values: [...], hooks: { ... } }) cannot narrow value to the literal union type. This is a fundamental TypeScript reverse-inference limitation: the generic V in EnumDescriptor<V> is not in a direct inference position when contextual-typing callbacks inside a mapped object parameter.

Working alternatives (both tested):

  1. options.hooks.<field> — declare the enum without hooks, then provide hooks via the third argument:
createTable(
  "Test",
  { role: { kind: "enum", values: ["ADMIN", "USER"] } },
  {
    hooks: {
      role: {
        create: ({ value }) => {
          // value: "ADMIN" | "USER" | null  ✅
          return value ?? "USER";
        },
      },
    },
  },
);
  1. Fluent API — db.enum(...).hooks(...):
createTable("Test", {
  role: db.enum(["ADMIN", "USER"]).hooks({
    create: ({ value }) => {
      // value: "ADMIN" | "USER" | null  ✅
      return value ?? "USER";
    },
  }),
});

dqn added 2 commits May 21, 2026 20:05
The warning-tier `field_removed` flow was already re-inserting the
underlying FK column so `migrate.ts` could read it, but the forward /
backward relationship entries on the type's `TypeConfig.relationships`
map were still emitted as removed in the Pre-phase request. A
`migrate.ts` that wanted to `innerJoin` through the dropped FK by its
relationship name therefore could not resolve it.

Add a parallel `relationship_removed` adjuster (`buildPreMigrationRelationshipChangesMap`
+ `applyPreMigrationRelationshipAdjustments`) that re-inserts the removed
relationship in the cloned Pre-phase request, honoring the forward /
backward refField / srcField swap used by `toProtoTypeMessage`. The
physical drop still happens together with the FK in Post-phase. While
in here, fold the three nearly-identical clone-and-apply blocks in
`executeSingleMigrationPrePhase` into one helper so field- and
relationship-side adjustments are applied uniformly.
…ralForm conflict

- Reject `createTable(["Name","Plural"], …, { pluralForm: "Other" })` at
  runtime; previously the tuple silently won.
- Cover descriptor `validate: [fn1, fn2]` (bare predicate array) and
  `validate: [[fn1, m1], [fn2, m2]]` (mixed tuple array) for resolver
  inputs — the existing coverage stopped at a single tuple.
- Cover `relation.toward.as`, `relation.backward`, and the enum
  `[{value, description}]` form on `createTable` so the rawRelation /
  allowedValues passthroughs don't regress unnoticed.
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

dqn added 20 commits May 21, 2026 20:39
…ip-only Pre-phase

- normalizeComparableTailorDBType now keeps schema.typeValidate, so
  validator-only changes are surfaced as updates instead of unchanged.
- executeSingleMigrationPostPhase now unions field- and relationship-level
  pre-migration maps so a relationship-only adjustment from Pre-phase is
  reverted on the platform during Post-phase.
The deploy manifest strips the synthetic `id` field from per-field hooks,
so a hook returning `{ id: ... }` was applied locally (in seeds/tests)
but never reached the platform. Exclude `id` from the public
`RecordHookFn` return type and throw at parse time if it slips through,
so local and deployed behavior stay aligned.
Consolidate repeated `descriptor.array !== true` checks into a local
`isArray` flag, derive a single `relation` reference for the uuid
relation handling block, and move the enum values validation into a
single guarded branch so we no longer test `descriptor.kind === "enum"`
twice. Also clarify the overload-1 comment now that field-level inline
hooks have moved to record level.
Replace the inline tuple-detection branch in `runRecordValidators` with
a dedicated `isRecordValidatorTuple` type-guard so the destructure of
`[rawFn, message]` no longer needs five separate `as` casts and the
helper-local `FnTuple` type is gone. Behavior is unchanged.
Simplify `isPassthroughField` to a pure `"kind" in entry` check and move
the unknown-kind validation into `buildResolverField` so the throw sits
beside the kind use (mirroring `createTable.ts:buildField`). Track
`hasDescriptor` in `resolveResolverFieldMap` via the same cheap check
instead of re-running the full `isResolverFieldDescriptor` predicate per
entry, and consolidate the enum descriptor's `values` extraction with
its non-empty validation into a single guarded block.
…aths

Widen `isResolverFieldDescriptor` to accept `unknown` so callers no longer
need an `as ResolverFieldEntry` lie just to satisfy the predicate signature,
and drop the residual `as ResolverFieldDescriptor` cast in
`resolveResolverField` (the `!isPassthroughField` narrowing already produces
that type). The predicate now also rejects null/primitive inputs explicitly,
matching the broader `unknown` contract.
…dler

The record-level and field-level branches of `collectScriptTargets` repeated
the same `Function | [Function, string]` destructure-and-push loop with only
the target kind varying. Lift the loop into a `pushValidateTargets(...)`
helper so both call sites read as a one-liner and the destructure logic
lives in one place.
TS structural typing accepts extra keys on inferred descriptors, so
field-level `hooks`/`validate` (removed in this branch) and typos like
`uniqe` would compile silently with no effect. Validate descriptor keys
against a per-kind allowlist in both `createTable` and resolver field
descriptors, and report a path-qualified error.
The previous commit added `as { ... }` casts to satisfy what looked
like an excess-property check, but TS does not actually error on these
extra keys (the same structural typing that necessitates the runtime
check also lets the literal compile cleanly). Use plain literals.
The platform synthesizes a `new Date()` create hook for required
generated datetime fields (e.g. `createdAt` from
`db.fields.timestamps()` / `timestampFields()`) via
`metadata.generated`, but the test/seed helper only consulted
`metadata.hooks.create`. Seed records and tests were therefore
missing `createdAt`, drifting from deployed behavior. Mirror the
synthesis in `createTailorDBHook` so seed/test data matches the
platform.
compareRemoteWithSnapshot only compared fields, so a remote whose
`type_validate.create`/`update` script was modified or dropped
out-of-band would report no drift even when validators in the snapshot
no longer matched the platform. Compare the canonical combined
expression (shared with the deploy and snapshot-manifest emitters via
`buildCombinedTypeValidateExpr`) against both remote scripts and emit
a `type_validate_mismatch` drift on disagreement.
`metadata.generated` alone is only honored server-side for datetime
fields — the parser synthesizes `new Date()` create/update hooks for
generated datetime, but the deploy manifest never sends `generated` to
the platform for non-datetime kinds. Previously the kysely type marked
any `generated: true` field as Generated<T>, letting inserts omit a
column the platform still required and fail at runtime. Restrict
Generated<T> to fields with either an explicit create hook or
generated datetime.
…en map writes

- Drop the duplicate toProtoTypeValidate in deploy/tailordb and route
  manifest emission through the canonical toProtoSnapshotTypeValidate
  exported from migrate/snapshot-manifest, so the two callers can no
  longer drift.
- Replace the `map.set(...).get(...) ?? new Map` idiom in
  buildPreMigrationChangesMap / buildPreMigrationRelationshipChangesMap
  with a single get-then-set per typeName.
…hema export

- Export assertValidDecimalScale from tailordb/schema and route the
  fluent db.decimal() factory and the createTable decimal descriptor
  branch through it, so both APIs share one error path.
- Extract the repeated outer.get/inner-new-Map pattern in
  buildPreMigrationChangesMap and buildPreMigrationRelationshipChangesMap
  into a getOrCreateInnerMap helper.
- Extract the shared defineSchema(createStandardSchema(...)) trailer in
  generateLinesDbSchemaFile and generateLinesDbSchemaFileWithPluginAPI
  into buildSchemaExportCode.
…onverter

The deploy path duplicated the entire snapshot->proto conversion
(generateTailorDBTypeManifest re-implemented field/relationship/index/
permission/hooks/validate conversion already covered by
generateTailorDBTypeManifestFromSnapshot in migrate/snapshot-manifest).
Drop the duplicate body — the deploy wrapper now only resolves
publishRecordEvents from the executor set and delegates, which also
picks up the snapshot-manifest's stricter conversions (BigInt for
serial.start, enum value spread, etc.).

Also: merge a duplicated ./diff-calculator import in
pre-migration-schema.test.ts and fill in missing JSDoc @param
descriptions on compareTypeHooksValidate.
- Introduce compileScriptExpr(fn, argMap) plus a SCRIPT_ARG_MAPS table
  in parser/tailordb/field.ts covering field, recordHook, and
  recordValidate binding contexts. convertHookToExpr is now an alias,
  parseFieldConfig's validate fallback uses it, and type-parser's
  buildRecordHookFieldExpr / convertRecordValidators drop their own
  precompiled-or-stringify copies — also fixes the validate fallback to
  go through stringifyFunction (method-shorthand normalization).
- Export convertRelationshipToProto from migrate/snapshot-manifest and
  delete the duplicated forward/backward switch in
  applyPreMigrationRelationshipAdjustments.
- processNestedFieldsFromSnapshot now delegates to
  convertFieldConfigToProto and only clears the sub-field-illegal
  flags afterward, removing the duplicated 50-line branch.
…lidators

field.precompiled.test.ts exercised the precompiled-expression
mechanism via the removed field-level .hooks()/.validate() APIs. The
same lookup now backs compileScriptExpr's recordHook and recordValidate
branches but had no test coverage. Assert the precompiled expression
flows through to both emitted field-level hooks and wrapped
type_validate scripts.
Export SCRIPT_ARG_MAPS from parser/tailordb/field and route the
bundler's scriptInvocationArgs through it, so the field-level /
record-hook / record-validate argument-map strings live in one place.
Drop the now-redundant convertHookToExpr alias — all in-file callers
go straight through compileScriptExpr (which defaults argMap to
"field").
…position

parseFieldConfig (field-level validate) and convertRecordValidators
(record-level validate) both decomposed validator entries with the
same `typeof v === "function" ? { fn, message: \`failed by ...\` }
: { fn: v[0], message: v[1] }` block. Centralize the normalization in
field.ts so both call sites share one source of truth and drop the
as-cast at the use sites.
…escriptor-api

# Conflicts:
#	packages/sdk/src/cli/commands/deploy/tailordb/index.ts
@github-actions
Copy link
Copy Markdown

Code Metrics Report (packages/sdk)

main (e1c64f7) #905 (041177c) +/-
Coverage 63.6% 64.4% +0.7%
Code to Test Ratio 1:0.4 1:0.4 +0.0
Details
  |                    | main (e1c64f7) | #905 (041177c) |  +/-  |
  |--------------------|----------------|----------------|-------|
+ | Coverage           |          63.6% |          64.4% | +0.7% |
  |   Files            |            367 |            378 |   +11 |
  |   Lines            |          12946 |          13309 |  +363 |
+ |   Covered          |           8241 |           8576 |  +335 |
+ | Code to Test Ratio |          1:0.4 |          1:0.4 |  +0.0 |
  |   Code             |          85641 |          88727 | +3086 |
+ |   Test             |          36442 |          38665 | +2223 |

Code coverage of files in pull request scope (71.3% → 74.2%)

Files Coverage +/- Status
packages/sdk/scripts/perf/features/tailordb-basic.ts 100.0% +100.0% affected
packages/sdk/scripts/perf/features/tailordb-enum.ts 100.0% +100.0% affected
packages/sdk/scripts/perf/features/tailordb-hooks.ts 33.3% +33.3% modified
packages/sdk/scripts/perf/features/tailordb-object.ts 100.0% +100.0% affected
packages/sdk/scripts/perf/features/tailordb-optional.ts 100.0% +100.0% affected
packages/sdk/scripts/perf/features/tailordb-relation.ts 100.0% +100.0% affected
packages/sdk/scripts/perf/features/tailordb-validate.ts 25.0% +25.0% modified
packages/sdk/src/cli/commands/deploy/tailordb/index.ts 58.4% +3.0% modified
packages/sdk/src/cli/commands/tailordb/migrate/pre-migration-schema.ts 73.2% +10.7% modified
packages/sdk/src/cli/commands/tailordb/migrate/script.ts 18.6% +16.7% modified
packages/sdk/src/cli/commands/tailordb/migrate/snapshot-manifest.ts 81.5% +2.9% modified
packages/sdk/src/cli/commands/tailordb/migrate/snapshot.ts 78.3% +1.6% modified
packages/sdk/src/cli/commands/tailordb/migrate/types.ts 100.0% 0.0% modified
packages/sdk/src/cli/services/tailordb/hooks-validate-bundler.ts 71.5% -11.1% modified
packages/sdk/src/cli/shared/skills-installer.ts 93.3% 0.0% modified
packages/sdk/src/configure/services/index.ts 0.0% 0.0% modified
packages/sdk/src/configure/services/resolver/descriptor.ts 100.0% +100.0% added
packages/sdk/src/configure/services/resolver/resolver.ts 100.0% 0.0% modified
packages/sdk/src/configure/services/tailordb/createTable.ts 98.8% +98.8% added
packages/sdk/src/configure/services/tailordb/index.ts 0.0% 0.0% modified
packages/sdk/src/configure/services/tailordb/schema.ts 89.8% +6.1% modified
packages/sdk/src/configure/types/type.ts 100.0% 0.0% modified
packages/sdk/src/parser/service/tailordb/field.ts 88.0% -7.3% modified
packages/sdk/src/parser/service/tailordb/record-hook-keys.ts 98.2% +98.2% added
packages/sdk/src/parser/service/tailordb/schema.ts 76.4% 0.0% modified
packages/sdk/src/parser/service/tailordb/type-parser.ts 92.1% +1.8% modified
packages/sdk/src/plugin/builtin/kysely-type/type-processor.ts 91.8% +0.0% modified
packages/sdk/src/plugin/builtin/seed/lines-db-processor.ts 0.0% 0.0% modified
packages/sdk/src/types/tailordb.ts 100.0% 0.0% modified
packages/sdk/src/utils/test/index.ts 63.2% -36.8% modified

SDK Configure Bundle Size

main (e1c64f7) #905 (041177c) +/-
configure-index-size 18KB 28.27KB 10.27KB
dependency-chunks-size 33.52KB 33.8KB 0.28KB
total-bundle-size 51.51KB 62.07KB 10.56KB

Runtime Performance

main (e1c64f7) #905 (041177c) +/-
Generate Median 2,781ms 2,835ms 54ms
Generate Max 2,992ms 2,859ms -133ms
Apply Build Median 2,811ms 2,895ms 84ms
Apply Build Max 2,850ms 2,913ms 63ms

Type Performance (instantiations)

main (e1c64f7) #905 (041177c) +/-
tailordb-basic 35,130 37,629 2,499
tailordb-optional 3,841 3,511 -330
tailordb-relation 7,428 3,941 -3,487
tailordb-validate 2,566 5,450 2,884
tailordb-hooks 5,767 5,886 119
tailordb-object 12,136 11,806 -330
tailordb-enum 2,462 2,132 -330
resolver-basic 9,424 11,363 1,939
resolver-nested 26,111 27,954 1,843
resolver-array 18,187 20,127 1,940
executor-schedule 4,234 4,234 0
executor-webhook 873 873 0
executor-record 8,166 4,679 -3,487
executor-resolver 4,369 6,458 2,089
executor-operation-function 869 869 0
executor-operation-gql 869 869 0
executor-operation-webhook 888 888 0
executor-operation-workflow 1,714 1,714 0

Reported by octocov

@dqn dqn marked this pull request as ready for review May 21, 2026 18:52
@dqn
Copy link
Copy Markdown
Contributor Author

dqn commented May 28, 2026

Converting to draft to reconsider whether the object-literal API is actually an improvement over the current fluent API before moving forward.

@dqn dqn marked this pull request as draft May 28, 2026 07:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs-check Trigger Docs Consistency Check

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants