turbine-orm 0.77.1 → 0.78.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +13 -9
  2. package/dist/cjs/cli/config.d.ts +7 -1
  3. package/dist/cjs/cli/config.js +11 -2
  4. package/dist/cjs/cli/destructive.d.ts +1 -1
  5. package/dist/cjs/cli/destructive.js +307 -9
  6. package/dist/cjs/cli/index.js +252 -42
  7. package/dist/cjs/cli/mcp.d.ts +23 -0
  8. package/dist/cjs/cli/mcp.js +190 -152
  9. package/dist/cjs/cli/migrate.d.ts +243 -3
  10. package/dist/cjs/cli/migrate.js +432 -43
  11. package/dist/cjs/cli/sql-statements.js +27 -0
  12. package/dist/cjs/cli/studio.d.ts +0 -1
  13. package/dist/cjs/cli/studio.js +9 -7
  14. package/dist/cjs/client.d.ts +8 -1
  15. package/dist/cjs/client.js +7 -0
  16. package/dist/cjs/connection-url.d.ts +82 -0
  17. package/dist/cjs/connection-url.js +187 -1
  18. package/dist/cjs/errors.d.ts +112 -12
  19. package/dist/cjs/errors.js +558 -114
  20. package/dist/cjs/generate.js +47 -15
  21. package/dist/cjs/index.d.ts +1 -1
  22. package/dist/cjs/introspect.d.ts +33 -0
  23. package/dist/cjs/introspect.js +54 -1
  24. package/dist/cjs/mssql.js +21 -1
  25. package/dist/cjs/nested-write.js +85 -14
  26. package/dist/cjs/pipeline-submittable.d.ts +2 -0
  27. package/dist/cjs/pipeline-submittable.js +88 -3
  28. package/dist/cjs/pipeline.js +13 -1
  29. package/dist/cjs/powdb-introspect.d.ts +5 -1
  30. package/dist/cjs/powdb-introspect.js +5 -1
  31. package/dist/cjs/powql.d.ts +14 -0
  32. package/dist/cjs/powql.js +44 -4
  33. package/dist/cjs/prisma-compat.js +95 -8
  34. package/dist/cjs/query/aggregates.js +56 -6
  35. package/dist/cjs/query/builder.d.ts +76 -13
  36. package/dist/cjs/query/builder.js +188 -58
  37. package/dist/cjs/query/compound-unique.d.ts +76 -0
  38. package/dist/cjs/query/compound-unique.js +129 -0
  39. package/dist/cjs/query/index.d.ts +1 -1
  40. package/dist/cjs/query/types.d.ts +65 -11
  41. package/dist/cjs/query/where.d.ts +85 -19
  42. package/dist/cjs/query/where.js +262 -47
  43. package/dist/cjs/query/writes.d.ts +11 -2
  44. package/dist/cjs/query/writes.js +116 -21
  45. package/dist/cjs/seed.d.ts +16 -0
  46. package/dist/cjs/seed.js +16 -0
  47. package/dist/cli/config.d.ts +7 -1
  48. package/dist/cli/config.js +11 -2
  49. package/dist/cli/destructive.d.ts +1 -1
  50. package/dist/cli/destructive.js +307 -9
  51. package/dist/cli/index.js +254 -44
  52. package/dist/cli/mcp.d.ts +23 -0
  53. package/dist/cli/mcp.js +187 -150
  54. package/dist/cli/migrate.d.ts +243 -3
  55. package/dist/cli/migrate.js +423 -45
  56. package/dist/cli/sql-statements.js +27 -0
  57. package/dist/cli/studio.d.ts +0 -1
  58. package/dist/cli/studio.js +10 -7
  59. package/dist/client.d.ts +8 -1
  60. package/dist/client.js +7 -0
  61. package/dist/connection-url.d.ts +82 -0
  62. package/dist/connection-url.js +183 -0
  63. package/dist/errors.d.ts +112 -12
  64. package/dist/errors.js +558 -114
  65. package/dist/generate.js +47 -15
  66. package/dist/index.d.ts +1 -1
  67. package/dist/introspect.d.ts +33 -0
  68. package/dist/introspect.js +53 -1
  69. package/dist/mssql.js +21 -1
  70. package/dist/nested-write.js +85 -14
  71. package/dist/pipeline-submittable.d.ts +2 -0
  72. package/dist/pipeline-submittable.js +87 -3
  73. package/dist/pipeline.js +14 -2
  74. package/dist/powdb-introspect.d.ts +5 -1
  75. package/dist/powdb-introspect.js +5 -1
  76. package/dist/powql.d.ts +14 -0
  77. package/dist/powql.js +45 -5
  78. package/dist/prisma-compat.js +96 -9
  79. package/dist/query/aggregates.js +56 -6
  80. package/dist/query/builder.d.ts +76 -13
  81. package/dist/query/builder.js +188 -58
  82. package/dist/query/compound-unique.d.ts +76 -0
  83. package/dist/query/compound-unique.js +126 -1
  84. package/dist/query/index.d.ts +1 -1
  85. package/dist/query/types.d.ts +65 -11
  86. package/dist/query/where.d.ts +85 -19
  87. package/dist/query/where.js +260 -47
  88. package/dist/query/writes.d.ts +11 -2
  89. package/dist/query/writes.js +117 -22
  90. package/dist/seed.d.ts +16 -0
  91. package/dist/seed.js +16 -0
  92. package/package.json +3 -3
  93. package/skills/turbine-orm/SKILL.md +37 -10
@@ -84,6 +84,19 @@ export interface MigrationRunResult {
84
84
  * unrecorded, so every statement in one of these must be idempotent.
85
85
  */
86
86
  noTransaction: MigrationFile[];
87
+ /**
88
+ * Applied migrations whose stored checksum was re-baselined to the file's
89
+ * current hash, because the run was given `allowDrift`. Empty otherwise.
90
+ */
91
+ rebaselined: RebaselinedChecksum[];
92
+ }
93
+ /** One applied migration whose stored checksum was rewritten to match its file. */
94
+ export interface RebaselinedChecksum {
95
+ name: string;
96
+ /** The hash the tracking table held, i.e. the content that was actually applied. */
97
+ from: string;
98
+ /** The hash of the file as it stands now. */
99
+ to: string;
87
100
  }
88
101
  /** Extract the YYYYMMDDHHMMSS timestamp prefix from a migration name, or null. */
89
102
  export declare function migrationTimestamp(name: string): string | null;
@@ -106,6 +119,24 @@ export declare function collectUpDestructive(files: MigrationFile[]): Destructiv
106
119
  * a gate. This entry point lets the RULE be asserted directly, once per code.
107
120
  */
108
121
  export declare function ensureTrackingTable(client: MigrationQueryClient, dialect?: Dialect): Promise<void>;
122
+ /**
123
+ * The applied rows for a READ-ONLY caller: existence is TESTED, never created.
124
+ *
125
+ * `migrate status` used to reach the tracking table through
126
+ * {@link ensureTrackingTable}, which issues `CREATE TABLE IF NOT EXISTS`
127
+ * unconditionally. PostgreSQL checks the CREATE privilege on the schema BEFORE
128
+ * the `IF NOT EXISTS` short-circuit, so a read-only role got `permission denied
129
+ * for schema public` and a CI "is production up to date?" check could not run
130
+ * as one. A report must not need write rights to say what it can see: an
131
+ * absent table means "no migration has been applied here", which is the honest
132
+ * answer and the one `up` will act on.
133
+ *
134
+ * @internal exported for tests.
135
+ */
136
+ export declare function readAppliedMigrations(client: MigrationQueryClient, dialect?: Dialect): Promise<{
137
+ applied: AppliedMigration[];
138
+ trackingTableExists: boolean;
139
+ }>;
109
140
  /**
110
141
  * Parse a migration filename into its components.
111
142
  * Expected format: YYYYMMDDHHMMSS_description.sql
@@ -147,6 +178,15 @@ export declare function getPendingMigrations(migrationsDir: string, applied: str
147
178
  * List all migration files in the migrations directory, sorted by name.
148
179
  */
149
180
  export declare function listMigrationFiles(migrationsDir: string): MigrationFile[];
181
+ /**
182
+ * `.sql` files in `migrationsDir` that the runner NEVER sees, because their
183
+ * name lacks the `YYYYMMDDHHMMSS_<name>.sql` shape {@link listMigrationFiles}
184
+ * requires (a hand-written `add_thing.sql`, a 13-digit prefix). Such a file
185
+ * used to be skipped in silence by `status`, `up` and `deploy` alike, so a
186
+ * misnamed migration simply never ran and nothing said so. The CLI prints one
187
+ * warning per entry.
188
+ */
189
+ export declare function listIgnoredSqlFiles(migrationsDir: string): string[];
150
190
  /** The parsed sections of a migration file plus its execution directives. */
151
191
  export interface ParsedMigration {
152
192
  up: string;
@@ -284,6 +324,96 @@ export declare function createMigration(migrationsDir: string, name: string, aut
284
324
  * without contending on a single hardcoded lock ID.
285
325
  */
286
326
  export declare function deriveLockId(databaseName: string): number;
327
+ /**
328
+ * Fetch the current database name from the connected client. Used to derive
329
+ * the advisory lock ID so concurrent migrations in sibling databases do not
330
+ * contend on one another.
331
+ *
332
+ * Asked of `current_database()` and NOT parsed out of the connection string:
333
+ * the string may carry no path at all (a `PGDATABASE` environment default, a
334
+ * service file, a `.pgpass` entry), and a lock id derived from an empty name
335
+ * would put every such database on ONE lock. The empty-string fallback is the
336
+ * same hazard one step further in, so it is here to be seen rather than to be
337
+ * relied on.
338
+ *
339
+ * @internal exported for tests. `deriveLockId` is tested for stability and for
340
+ * staying inside the positive int4 range; this is the value it is given, and an
341
+ * untested input to a tested function is an untested pair.
342
+ */
343
+ export declare function getCurrentDatabaseName(client: {
344
+ query: (sql: string) => Promise<{
345
+ rows: {
346
+ current_database?: string;
347
+ }[];
348
+ }>;
349
+ }): Promise<string>;
350
+ /**
351
+ * The connection string the migration runner (and `turbine seed`) connects
352
+ * with for a configured schema: unchanged for the default, pinned otherwise.
353
+ *
354
+ * "Not configured" emits NOTHING: with no `schema` at all the role's own
355
+ * search_path decides, as it always has. A CONFIGURED schema is applied as the
356
+ * `options=-c search_path=...` startup parameter through
357
+ * {@link withSearchPathOption}, never as a `SET`, so the migration's own DDL,
358
+ * its `_turbine_migrations` tracking table, and the lock connection all resolve
359
+ * unqualified names in the schema the rest of the CLI (`push`, `generate`,
360
+ * `doctor`, Studio) already reads. Before this the runner had no schema at all,
361
+ * and a code-first project on `schema: 'app'` pushed its tables into `app`,
362
+ * diffed against `app`, and then applied the resulting migration into `public`.
363
+ *
364
+ * `public` is pinned like any other name, and used to be exempted on the
365
+ * reasoning that it is the default anyway. It is not: `search_path` is a
366
+ * role/database/connection-string setting, so on a role whose path is
367
+ * `app, public` a project configured `schema: 'public'` had `push` creating in
368
+ * `public` (its own pin is unconditional) while `migrate up` created in `app`.
369
+ * That is the same split this function exists to close, reintroduced for the
370
+ * one schema most projects actually use. `schema-sql.ts`'s `pinSearchPath` is
371
+ * the other half of the rule and the two now answer identically: pin
372
+ * unconditionally, and EXTEND the caller's path rather than replace it. The
373
+ * extension is why `inheritedSchemas` exists, since a startup parameter cannot
374
+ * read the path it is about to override; {@link connectMigrationClient} probes
375
+ * for it. Left empty, the emitted parameter is byte-identical to before.
376
+ *
377
+ * The helper is a pure leaf and returns `null` for a name it cannot emit safely
378
+ * or a string it cannot rewrite; here each becomes the E003 the rest of the CLI
379
+ * raises for a bad configured value, because a connection parameter is a
380
+ * literal and a name that can carry a space can carry a second `-c`.
381
+ *
382
+ * @internal exported for the seed runner and tests.
383
+ */
384
+ /**
385
+ * Refuse a configured schema NAME the CLI cannot emit into a connection
386
+ * parameter, before anything opens a connection with it.
387
+ *
388
+ * Separate from {@link assertSchemaExists}, and it has to run FIRST: they answer
389
+ * different questions and only one of them has a useful answer for a name like
390
+ * `bad name`. Asking the catalog first reports "schema does not exist" and
391
+ * suggests `CREATE SCHEMA "bad name"`, which is true and useless; the real
392
+ * problem is that a value carrying whitespace cannot go into `options=-c` at
393
+ * all, because libpq splits that parameter on spaces and a second `-c` would
394
+ * set any GUC it liked.
395
+ *
396
+ * @internal exported for tests.
397
+ */
398
+ export declare function assertPinnableSchema(schema: string): void;
399
+ export declare function connectionStringForSchema(connectionString: string, schema?: string, inheritedSchemas?: readonly string[]): string;
400
+ /**
401
+ * Refuse a configured schema that does not exist in this database.
402
+ *
403
+ * Postgres does not validate `search_path`: pinning it to a namespace that does
404
+ * not exist succeeds, and the first unqualified statement then fails with
405
+ * `relation "..." does not exist` or "no schema has been selected to create in",
406
+ * neither of which names the schema or the setting that caused it. This is the
407
+ * ONE place that question is asked, and it is asked of the CATALOG
408
+ * (`to_regnamespace`, bound as a parameter) rather than of the connection's own
409
+ * resolved `current_schema()`, so it gives the same answer whether the client
410
+ * calling it is pinned or not. That is what lets `turbine seed` use it: the seed
411
+ * runs its callback in a child process and could not have been checked through
412
+ * the pin at all.
413
+ *
414
+ * @internal exported for the seed runner and tests.
415
+ */
416
+ export declare function assertSchemaExists(client: MigrationQueryClient, schema: string): Promise<void>;
287
417
  /**
288
418
  * The connection surface the migration lock needs. `pg.Client` satisfies it;
289
419
  * tests substitute a fake so the dedicated-connection wiring can be exercised
@@ -389,7 +519,22 @@ export interface ChecksumMismatch {
389
519
  export interface MigrationDeployPlan {
390
520
  pending: MigrationFile[];
391
521
  mismatches: ChecksumMismatch[];
522
+ /**
523
+ * The out-of-order applies the real run WOULD flag (a pending migration whose
524
+ * timestamp is older than one already applied). Present so `deploy --dry-run`
525
+ * can print the same warning the real deploy prints instead of being quieter
526
+ * than the thing it previews.
527
+ */
528
+ outOfOrder: OutOfOrderApply[];
392
529
  }
530
+ /**
531
+ * Which of `pending` would be applied out of timestamp order, given `applied`.
532
+ *
533
+ * The rule `migrateUp` applies, extracted so the dry run cannot drift from it.
534
+ *
535
+ * @internal exported for tests.
536
+ */
537
+ export declare function predictOutOfOrder(applied: AppliedMigration[], pending: MigrationFile[]): OutOfOrderApply[];
393
538
  /**
394
539
  * Validate that applied migration files have not been modified or deleted since they were run.
395
540
  * Returns an array of mismatched migrations (empty if all are clean).
@@ -397,7 +542,62 @@ export interface MigrationDeployPlan {
397
542
  * @internal exported for tests; not part of the CLI's public surface.
398
543
  */
399
544
  export declare function validateChecksums(client: MigrationQueryClient, migrationsDir: string, dialect?: Dialect): Promise<ChecksumMismatch[]>;
400
- export declare function formatChecksumMismatchError(mismatches: ChecksumMismatch[]): string;
545
+ /**
546
+ * THE drift comparison, over rows already read: which applied migrations no
547
+ * longer match their file, and which are merely carrying a pre-v0.6 hash.
548
+ *
549
+ * Split out of {@link validateChecksums} so a DRY RUN can ask the same question
550
+ * without answering it in the database. `validateChecksums` reads through
551
+ * `getAppliedMigrations`, which CREATES the tracking table, and then WRITES the
552
+ * legacy-hash upgrades; a preview must do neither. A dry run that re-derived
553
+ * drift its own way is how `migrate down --dry-run` came to green-light a batch
554
+ * the real command refuses, so the rule lives here once and both paths call it.
555
+ *
556
+ * @internal exported for tests.
557
+ */
558
+ export declare function compareAppliedChecksums(applied: readonly AppliedMigration[], migrationsDir: string): {
559
+ mismatches: ChecksumMismatch[];
560
+ legacyUpgrades: Array<{
561
+ name: string;
562
+ checksum: string;
563
+ }>;
564
+ };
565
+ /**
566
+ * Rewrite the stored checksum of each MODIFIED migration to its file's current
567
+ * hash, and report what changed.
568
+ *
569
+ * This is what `--allow-drift` was always documented to mean ("when
570
+ * intentionally rewriting history") and never did. The flag let the run
571
+ * proceed and left the stored hash alone, so the very next `up` was blocked
572
+ * again, forever: the only ways out were reverting the file byte-for-byte or
573
+ * editing `_turbine_migrations` by hand. Recording the new hash is what makes
574
+ * the flag a one-time decision instead of a permanent one.
575
+ *
576
+ * DELETED files are deliberately not touched. There is no content to hash, and
577
+ * the row is the only remaining record that the migration ran; rewriting or
578
+ * removing it would erase history rather than re-baseline it.
579
+ *
580
+ * @internal exported for tests.
581
+ */
582
+ export declare function rebaselineChecksums(client: MigrationQueryClient, migrationsDir: string, mismatches: ChecksumMismatch[], dialect?: Dialect): Promise<RebaselinedChecksum[]>;
583
+ export declare function formatChecksumMismatchError(mismatches: ChecksumMismatch[], action?: 'apply pending migrations' | 'roll back'): string;
584
+ /**
585
+ * Refuse a batch containing a migration whose UP section runs NOTHING.
586
+ *
587
+ * The untouched `migrate create` scaffold is exactly this: a `-- UP` marker
588
+ * followed by `-- Write your migration SQL here`. It used to be applied and
589
+ * recorded, and the developer who then filled the file in was refused as
590
+ * drift; with `--allow-drift` the file was "already applied" and its SQL never
591
+ * ran, and the only exit was editing `_turbine_migrations` by hand. The runner
592
+ * already refuses a file with no marker on the grounds that "nothing would run,
593
+ * and the migration would still be recorded as applied": a marker over zero
594
+ * executable statements records the same thing. Same rule for a
595
+ * `-- turbine:no-transaction` file. Pre-flight over the whole batch, alongside
596
+ * {@link assertNoEmbeddedTransactions}, so nothing is applied first.
597
+ *
598
+ * @internal exported for tests.
599
+ */
600
+ export declare function assertUpHasStatements(files: MigrationFile[]): void;
401
601
  /**
402
602
  * Build a deploy plan from local migration files and applied migration rows.
403
603
  * This is pure file-system planning; callers with a database connection should
@@ -407,7 +607,9 @@ export declare function planMigrationDeploy(migrationsDir: string, applied: Appl
407
607
  /**
408
608
  * Inspect deploy status without applying migrations.
409
609
  */
410
- export declare function inspectMigrationDeploy(connectionString: string, migrationsDir: string): Promise<MigrationDeployPlan>;
610
+ export declare function inspectMigrationDeploy(connectionString: string, migrationsDir: string, options?: {
611
+ schema?: string;
612
+ }): Promise<MigrationDeployPlan>;
411
613
  /**
412
614
  * Apply all pending migrations (UP).
413
615
  *
@@ -435,6 +637,11 @@ export declare function migrateUp(connectionString: string, migrationsDir: strin
435
637
  * long time on other transactions and otherwise looks hung).
436
638
  */
437
639
  onNoTransaction?: (file: MigrationFile) => void;
640
+ /**
641
+ * The Postgres schema unqualified identifiers resolve in, for the migration
642
+ * SQL and the tracking table alike. See {@link connectionStringForSchema}.
643
+ */
644
+ schema?: string;
438
645
  }): Promise<MigrationRunResult>;
439
646
  /**
440
647
  * Production migration apply. This intentionally applies files as written and
@@ -443,6 +650,7 @@ export declare function migrateUp(connectionString: string, migrationsDir: strin
443
650
  export declare function migrateDeploy(connectionString: string, migrationsDir: string, options?: {
444
651
  adapter?: DatabaseAdapter;
445
652
  allowDrift?: boolean;
653
+ schema?: string;
446
654
  }): Promise<MigrationRunResult>;
447
655
  /**
448
656
  * Roll back a prepared LIFO batch, newest first, stopping at the first
@@ -471,6 +679,32 @@ export declare function rollbackMigrations(client: MigrationTxClient, toRollback
471
679
  error: string;
472
680
  }>;
473
681
  }>;
682
+ /**
683
+ * What `migrateDown` WOULD roll back, computed by the same rule it uses, and
684
+ * where it would stop.
685
+ *
686
+ * `migrate down --dry-run` used to build its list from `migrateStatus`, which
687
+ * sorts by filename and drops applied migrations whose file is missing. The
688
+ * real run walks the tracking table newest-APPLIED first and STOPS at the first
689
+ * migration it cannot read, so with a deleted file the two named different
690
+ * migrations: the dry run reported the one after the gap. A dry run that names
691
+ * the wrong migration is not a dry run, so both now read the same function.
692
+ *
693
+ * `stoppedAt` is the migration the real run would fail on (a missing file, or a
694
+ * missing DOWN section) together with the message it would print; `toRollback`
695
+ * is what it would get through first.
696
+ */
697
+ export declare function planMigrationRollback(connectionString: string, migrationsDir: string, options?: {
698
+ step?: number;
699
+ schema?: string;
700
+ allowDrift?: boolean;
701
+ }): Promise<{
702
+ toRollback: MigrationFile[];
703
+ stoppedAt: {
704
+ name: string;
705
+ error: string;
706
+ } | null;
707
+ }>;
474
708
  /**
475
709
  * Rollback the last N migrations (DOWN).
476
710
  *
@@ -483,12 +717,16 @@ export declare function migrateDown(connectionString: string, migrationsDir: str
483
717
  step?: number;
484
718
  allowDestructive?: boolean;
485
719
  adapter?: DatabaseAdapter;
720
+ schema?: string;
721
+ /** Roll back anyway when an applied file has drifted, re-baselining as `up` does. */
722
+ allowDrift?: boolean;
486
723
  }): Promise<{
487
724
  rolledBack: MigrationFile[];
488
725
  errors: Array<{
489
726
  file: MigrationFile;
490
727
  error: string;
491
728
  }>;
729
+ rebaselined: RebaselinedChecksum[];
492
730
  }>;
493
731
  /**
494
732
  * Get the status of all migrations (applied vs pending).
@@ -497,4 +735,6 @@ export declare function migrateDown(connectionString: string, migrationsDir: str
497
735
  * unchanged pre-v0.6 row reports the same way `migrate up` treats it (valid,
498
736
  * pending an in-place hash upgrade) rather than looking like drift.
499
737
  */
500
- export declare function migrateStatus(connectionString: string, migrationsDir: string): Promise<MigrationStatus[]>;
738
+ export declare function migrateStatus(connectionString: string, migrationsDir: string, options?: {
739
+ schema?: string;
740
+ }): Promise<MigrationStatus[]>;