Skip to content

feat(tailordb): warning tier and migration script subcommand#1200

Draft
toiroakr wants to merge 4 commits into
mainfrom
feat/remove-field-phase
Draft

feat(tailordb): warning tier and migration script subcommand#1200
toiroakr wants to merge 4 commits into
mainfrom
feat/remove-field-phase

Conversation

@toiroakr
Copy link
Copy Markdown
Contributor

@toiroakr toiroakr commented May 20, 2026

Summary

  • Three-tier change classification (safe / warning / breaking): field_removed and type_removed are now reported as warnings during tailordb migration generate, not silent changes. They are no longer dropped before migrate.ts runs — the field/type stays available during the Pre-migration phase so scripts can read it (e.g. innerJoin through a foreign key being dropped in the same migration), and the physical drop happens in Post-migration.
  • New tailordb migration script <number> subcommand to add a migrate.ts (and db.ts) template to an existing migration directory. Useful for warning-tier changes where you want a custom data migration even though one was not generated automatically.
  • Execute migrate.ts whenever the file exists on disk, regardless of whether the diff originally required a script. Breaking changes still hard-require a script as before.
  • Extracted pre-migration schema computation from deploy/tailordb/index.ts into a dedicated cli/commands/tailordb/migrate/pre-migration-schema.ts module.
  • Updated docs/services/tailordb-migration.md with the new warning behavior and script subcommand; changeset bumped to minor.

Notes

  • The diff JSON now carries parallel warnings / hasWarnings fields alongside the existing breakingChanges / hasBreakingChanges.
  • PendingMigration gains a hasScript: boolean to decouple "run the script" from "the diff required a script".
  • The safety check requiresMigrationScript && !hasScript is preserved — breaking changes without a script still fail loudly.

Closes: GitHub Discussion #249

In tailordb migrations, columns scheduled for removal (`field_removed`
diff) were dropped from the type schema in the Pre-migration phase,
before `migrate.ts` ran. This made it impossible for migration scripts
to read the about-to-be-deleted column — most notably, joining through
a foreign key that is being removed in the same migration would fail
with `field 'X' not found`.

Extend `applyPreMigrationFieldAdjustments` so that `field_removed`
changes re-insert the field with its pre-migration config into the
cloned Pre-phase request. The Post-migration phase still sends the
final schema (without the field), so the physical column drop happens
there as documented.

Also update `docs/services/tailordb-migration.md` so the Pre/Post
phase descriptions match the new behavior.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 20, 2026

🦋 Changeset detected

Latest commit: 37e6827

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 Minor
@tailor-platform/create-sdk Minor

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 20, 2026

⚡ pkg.pr.new

@tailor-platform/sdk

pnpm add https://pkg.pr.new/@tailor-platform/sdk@37e6827
pnpm dlx https://pkg.pr.new/@tailor-platform/sdk@37e6827 --help

@tailor-platform/create-sdk

pnpm add https://pkg.pr.new/@tailor-platform/create-sdk@37e6827
pnpm dlx https://pkg.pr.new/@tailor-platform/create-sdk@37e6827 my-app

commit: 37e6827

@github-actions

This comment has been minimized.

- Classify field_removed/type_removed as warning-tier (data-loss possible but not breaking) and surface them during 'tailordb migration generate'
- Add 'tailordb migration script <number>' subcommand to add a migrate.ts (and db.ts) template to an existing migration directory
- Execute migrate.ts whenever the file exists on disk, regardless of whether the diff originally required a script; breaking changes still hard-require one
- Extract pre-migration schema computation to a dedicated migrate/pre-migration-schema.ts module
@toiroakr toiroakr changed the title fix(tailordb): keep removed fields readable during pre-migration phase feat(tailordb): warning tier and migration script subcommand May 20, 2026
@toiroakr toiroakr requested a review from Copilot May 20, 2026 11:47
@github-actions

This comment has been minimized.

This comment was marked as outdated.

Address Copilot review on #1200:

- loadDiff derives hasWarnings from warnings.length so the two stay consistent even if a hand-edited diff.json sets one side
- Add tests asserting compareSnapshots populates warnings/hasWarnings for field_removed and type_removed
- Add unit tests for formatWarnings (field, type-level, multiple)
- Doc: per-migration script step now reflects file-existence based execution
- Rewrite pre-migration-schema header comment to scope to fields only (type_removed is handled in the deploy flow)
- Neutralize migrate.ts template docstring so it covers both breaking and warning-tier changes
@github-actions

This comment has been minimized.

This comment was marked as outdated.

…idation

Address Copilot review feedback:
- Rename misleading 'breakingChanges' to 'preMigrationChanges' in
  deploy/tailordb/index.ts since the map also contains warning-tier
  field_removed changes, not only breaking ones.
- Tighten migration number validation in the 'script' subcommand to
  reject inputs like '1abc' that parseInt would silently accept.
- Add CLI argument-schema tests for the new 'script' subcommand.
@github-actions
Copy link
Copy Markdown

Code Metrics Report (packages/sdk)

main (b2bd4aa) #1200 (a3d0f1e) +/-
Coverage 62.3% 62.2% -0.2%
Code to Test Ratio 1:0.4 1:0.4 +0.0
Details
  |                    | main (b2bd4aa) | #1200 (a3d0f1e) |  +/-  |
  |--------------------|----------------|-----------------|-------|
- | Coverage           |          62.3% |           62.2% | -0.2% |
  |   Files            |            364 |             366 |    +2 |
  |   Lines            |          12773 |           12852 |   +79 |
+ |   Covered          |           7967 |            7996 |   +29 |
+ | Code to Test Ratio |          1:0.4 |           1:0.4 |  +0.0 |
  |   Code             |          83913 |           84323 |  +410 |
+ |   Test             |          35136 |           35338 |  +202 |

Code coverage of files in pull request scope (55.0% → 54.2%)

Files Coverage +/- Status
packages/sdk/src/cli/commands/deploy/tailordb/index.ts 41.3% +2.2% modified
packages/sdk/src/cli/commands/deploy/tailordb/migration.ts 58.4% +0.5% modified
packages/sdk/src/cli/commands/tailordb/migrate/diff-calculator.ts 75.2% +1.9% modified
packages/sdk/src/cli/commands/tailordb/migrate/generate.ts 0.8% -0.1% modified
packages/sdk/src/cli/commands/tailordb/migrate/index.ts 100.0% 0.0% modified
packages/sdk/src/cli/commands/tailordb/migrate/pre-migration-schema.ts 35.0% +35.0% added
packages/sdk/src/cli/commands/tailordb/migrate/script.ts 1.9% +1.9% added
packages/sdk/src/cli/commands/tailordb/migrate/snapshot-manifest.ts 80.1% 0.0% modified
packages/sdk/src/cli/commands/tailordb/migrate/snapshot.ts 71.3% +0.3% modified
packages/sdk/src/cli/commands/tailordb/migrate/template-generator.ts 89.5% 0.0% modified
packages/sdk/src/cli/commands/tailordb/migrate/types.ts 100.0% 0.0% modified

SDK Configure Bundle Size

main (b2bd4aa) #1200 (a3d0f1e) +/-
configure-index-size 18KB 18KB 0KB
dependency-chunks-size 33.52KB 33.52KB 0KB
total-bundle-size 51.51KB 51.51KB 0KB

Runtime Performance

main (b2bd4aa) #1200 (a3d0f1e) +/-
Generate Median 2,279ms 2,731ms 452ms
Generate Max 2,294ms 2,772ms 478ms
Apply Build Median 2,317ms 2,762ms 445ms
Apply Build Max 2,453ms 2,789ms 336ms

Type Performance (instantiations)

main (b2bd4aa) #1200 (a3d0f1e) +/-
tailordb-basic 35,130 35,130 0
tailordb-optional 3,841 3,841 0
tailordb-relation 7,428 7,428 0
tailordb-validate 2,566 2,566 0
tailordb-hooks 5,767 5,767 0
tailordb-object 12,136 12,136 0
tailordb-enum 2,462 2,462 0
resolver-basic 9,424 9,424 0
resolver-nested 26,111 26,111 0
resolver-array 18,187 18,187 0
executor-schedule 4,234 4,234 0
executor-webhook 873 873 0
executor-record 8,166 8,166 0
executor-resolver 4,369 4,369 0
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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

packages/sdk/src/cli/commands/deploy/tailordb/migration.ts:138

  • Now that migrations execute migrate.ts whenever it exists, a user can end up with migrate.ts present but missing the companion db.ts (e.g. manual edits). The bundler will then fail on import type { Transaction } from "./db" with a low-level build error. Consider validating db.ts existence when hasScript is true and surfacing a clear warning/error (similar to the existing breaking-change script check).
      // Load the diff to inspect breaking/warning classification
      const diff = loadDiff(diffPath);

      // The migration script is executed when migrate.ts exists on disk.
      // Breaking changes still hard-require a script; warnings (e.g. field_removed)
      // may optionally have one added via `tailordb migration script <num>`.
      const scriptPath = getMigrationFilePath(migrationsDir, file.number, "migrate");
      const hasScript = fs.existsSync(scriptPath);
      if (diff.requiresMigrationScript && !hasScript) {
        logger.warn(
          `Migration ${namespace}/${file.number} requires a script but migrate.ts not found`,
        );
        continue;
      }

Comment on lines +45 to +58
// Accept either the canonical 4-digit form ("0001") or a bare non-negative
// integer ("1"). Reject anything containing non-digit characters so that
// inputs like "1abc" don't silently parse to migration 1.
let migrationNumber: number;
if (isValidMigrationNumber(options.number)) {
migrationNumber = parseInt(options.number, 10);
} else if (/^\d+$/.test(options.number)) {
migrationNumber = parseInt(options.number, 10);
} else {
throw new Error(
`Invalid migration number format: ${options.number}. Expected 4-digit format (e.g., 0001) or integer (e.g., 1).`,
);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants