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
@@ -200,7 +200,13 @@ export interface WhereOperator<V = unknown, F extends string = string> {
200
200
  contains?: string;
201
201
  startsWith?: string;
202
202
  endsWith?: string;
203
- /** Set to 'insensitive' to use ILIKE instead of LIKE for string comparisons */
203
+ /**
204
+ * `'insensitive'` makes the comparison case-insensitive on a string column:
205
+ * `contains` / `startsWith` / `endsWith` use ILIKE (or the dialect's
206
+ * equivalent), and `equals` (or a bare value), `not`, `in` and `notIn` fold
207
+ * case on both sides (`LOWER(...)`). A `mode` beside a non-string operand
208
+ * throws `ValidationError` (E003).
209
+ */
204
210
  mode?: 'default' | 'insensitive';
205
211
  }
206
212
  /**
@@ -988,8 +994,15 @@ export interface DeleteManyArgs<T, R extends object = {}> {
988
994
  }
989
995
  export interface UpsertArgs<T, R extends object = {}> {
990
996
  where: WhereClause<T, R>;
997
+ /** The row to insert when `where` matches nothing. Plain values only: there is no stored value to operate on yet. */
991
998
  create: Partial<T>;
992
- update: Partial<T>;
999
+ /**
1000
+ * Applied to the existing row on conflict. Each field takes a plain value or
1001
+ * the same atomic {@link UpdateOperatorInput} `update()` takes, so
1002
+ * `count: { increment: 1 }` adds to the stored value instead of overwriting
1003
+ * it. Scalar fields only: nested relation writes are not part of an upsert.
1004
+ */
1005
+ update: UpdateInput<T>;
993
1006
  /** Query timeout in milliseconds. Rejects with an error if exceeded. */
994
1007
  timeout?: number;
995
1008
  /** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
@@ -1306,16 +1319,21 @@ type GroupByKeys<A> = A extends {
1306
1319
  /** The subset of `by` keys that are plain string field names on the entity `T`. */
1307
1320
  type GroupByFieldKeys<T, A> = Extract<GroupByKeys<A>, keyof T & string>;
1308
1321
  /**
1309
- * `_sum` / `_avg` result block: every requested key maps to `number | null`
1310
- * (an aggregate over zero matching rows is null). Present only when the args
1311
- * actually requested the block. A JSON-path aggregate target keys by its arg
1312
- * key (the alias), so `keyof S` covers both plain columns and JSON aliases.
1322
+ * `_sum` / `_avg` result block: every requested key maps to
1323
+ * `number | string | null`. A `string` comes back for an int8 / bigint or
1324
+ * numeric / decimal source column, whose SUM and AVG PostgreSQL widens to
1325
+ * arbitrary-precision `numeric` and whose exact text is returned to avoid
1326
+ * precision loss (see {@link AggregateResult}); every other column, and every
1327
+ * JSON-path aggregate, is a `number`; `null` is an aggregate over zero
1328
+ * matching rows. Present only when the args actually requested the block. A
1329
+ * JSON-path aggregate target keys by its arg key (the alias), so `keyof S`
1330
+ * covers both plain columns and JSON aliases.
1313
1331
  */
1314
1332
  type GroupBySumAvgPart<A, Key extends '_sum' | '_avg'> = A extends {
1315
1333
  [P in Key]: infer S;
1316
1334
  } ? [S] extends [object] ? {
1317
1335
  [P in Key]: {
1318
- [K in keyof S & string]: number | null;
1336
+ [K in keyof S & string]: number | string | null;
1319
1337
  };
1320
1338
  } : unknown : unknown;
1321
1339
  /**
@@ -1404,11 +1422,20 @@ export interface AggregateArgs<T, R extends object = {}> {
1404
1422
  /** Plan this query with its real parameter values. See {@link FindManyArgs.forceCustomPlan}. */
1405
1423
  forceCustomPlan?: boolean;
1406
1424
  }
1407
- /** Result type for aggregate queries */
1425
+ /**
1426
+ * Result type for aggregate queries.
1427
+ *
1428
+ * `_sum` / `_avg` are a `number` for int2 / int4 / float columns and a
1429
+ * `string` for int8 / bigint and numeric / decimal columns: PostgreSQL widens
1430
+ * SUM and AVG over those to arbitrary-precision `numeric`, and the exact text
1431
+ * is returned to avoid precision loss, the same policy under which a `numeric`
1432
+ * column value and an int8 value above 2^53 - 1 read back as strings. `null`
1433
+ * is an aggregate over zero rows.
1434
+ */
1408
1435
  export interface AggregateResult<T> {
1409
1436
  _count?: number | Record<string, number>;
1410
- _sum?: Partial<Record<keyof T & string, number | null>>;
1411
- _avg?: Partial<Record<keyof T & string, number | null>>;
1437
+ _sum?: Partial<Record<keyof T & string, number | string | null>>;
1438
+ _avg?: Partial<Record<keyof T & string, number | string | null>>;
1412
1439
  _min?: Partial<Record<keyof T & string, unknown>>;
1413
1440
  _max?: Partial<Record<keyof T & string, unknown>>;
1414
1441
  }
@@ -1618,10 +1645,37 @@ export interface JsonPathOrderBy {
1618
1645
  * - to-one (belongsTo / hasOne): `{ author: { name: 'asc' } }`, orders by a
1619
1646
  * correlated scalar subquery on the target column (an {@link OrderBySpec} with
1620
1647
  * `nulls` is accepted too).
1648
+ * - a CHAIN of to-one hops: `{ author: { organization: { name: 'asc' } } }`.
1649
+ * Each further hop is a JOIN inside that same subquery, so the chain costs
1650
+ * one subquery whatever its length. The type admits a head relation plus
1651
+ * ten chained hops, eleven relation keys in all, which is exactly where the
1652
+ * builder stops (`MAX_ORDER_BY_RELATION_HOPS` in query/relations.ts, the
1653
+ * nested-`with` cap; a twelfth hop is E007). The bound is a countdown
1654
+ * through a fixed table rather than an unbounded self reference, so `tsc`
1655
+ * never has to report an excessively deep instantiation.
1656
+ *
1657
+ * The chain is keyed by `string`, not by the target's relation map, so it
1658
+ * cannot tell a to-many hop from a to-one one: `{ author: { posts: { title } } }`
1659
+ * typechecks and is refused by the builder with E003 (a to-many relation has
1660
+ * no single value to order by; {@link RelationPickOrderBy} exists for that).
1661
+ * Everything else about the shape is checked: a value at any depth is a
1662
+ * direction, a `{ sort, nulls }` spec, or the next hop.
1621
1663
  */
1622
1664
  export type RelationOrderBy = {
1623
1665
  _count: OrderDirection;
1624
- } | Record<string, OrderDirection | OrderBySpec>;
1666
+ } | RelationOrderByChain<10>;
1667
+ /**
1668
+ * The to-one branch of {@link RelationOrderBy}: a map from a target column to
1669
+ * a direction or an {@link OrderBySpec}, or from a further to-one relation to
1670
+ * the next link, with `Hops` links still allowed beneath this one. At `0` the
1671
+ * next link is `never`, so only columns remain.
1672
+ */
1673
+ export type RelationOrderByChain<Hops extends number> = Record<string, OrderDirection | OrderBySpec | ([Hops] extends [0] ? never : RelationOrderByChain<RelationOrderByHopsLeft[Hops]>)>;
1674
+ /**
1675
+ * `RelationOrderByHopsLeft[n]` is `n - 1`. Index 0 holds `never` and is never
1676
+ * read: {@link RelationOrderByChain} stops before indexing it.
1677
+ */
1678
+ type RelationOrderByHopsLeft = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
1625
1679
  /**
1626
1680
  * The ordering value extracted from the picked row in a
1627
1681
  * {@link RelationPickOrderBy}: either a plain target column name (camelCase,
@@ -181,22 +181,28 @@ interface ColumnRefContext {
181
181
  /**
182
182
  * A table-scoped WHERE compilation context for a sub-where that is NOT the
183
183
  * top-level `this.tableMeta` clause. Both relation-filter `EXISTS` sub-wheres
184
- * (correlated against the bare target table, `"target".col`) and relation
185
- * `with`-clause `where` filters (against a per-subquery alias, `t0.col`) compile
186
- * an arbitrary target table's where against a column qualifier. They differ ONLY
187
- * in that qualifier, the correlation parent handed to `buildRelationFilter`, and
188
- * the unknown-column error wording, so a single scoped build/collect/fingerprint
189
- * trio, driven by the SAME canonical {@link walkWhere} the top level uses, serves
190
- * both. See `buildScopedWhere` / `collectScopedWhereParams` / `fingerprintScopedWhere`.
184
+ * (correlated against the subquery's FROM item, the bare target table
185
+ * `"target".col` or its `rf0.col` alias, see {@link buildRelationFilter}) and
186
+ * relation `with`-clause `where` filters (against a per-subquery alias, `t0.col`)
187
+ * compile an arbitrary target table's where against a column qualifier. They
188
+ * differ ONLY in that qualifier, the correlation parent handed to
189
+ * `buildRelationFilter`, and the unknown-column error wording, so a single
190
+ * scoped build/collect/fingerprint trio, driven by the SAME canonical
191
+ * {@link walkWhere} the top level uses, serves both. See `buildScopedWhere` /
192
+ * `collectScopedWhereParams` / `fingerprintScopedWhere`.
191
193
  */
192
194
  interface WhereScope {
193
195
  /** The target table's metadata (column map, relations, types). */
194
196
  meta: TableMetadata;
195
197
  /** The target table name (used for host binding + error messages). */
196
198
  table: string;
197
- /** SQL prefix before `q(col)`, `"target".` for EXISTS sub-wheres, `t0.` for aliases. */
199
+ /** SQL prefix before `q(col)`: the FROM item's reference (`"target".` / `rf0.` / `t0.`) plus a dot. */
198
200
  qualifier: string;
199
- /** The `parentTable` correlation argument for nested `buildRelationFilter` calls. */
201
+ /**
202
+ * The RENDERED parent reference handed to nested `buildRelationFilter` calls
203
+ * as `parentRef`: the same reference `qualifier` is built from, so a nested
204
+ * filter correlates to the row this scope is compiling against.
205
+ */
200
206
  relationParent: string;
201
207
  /** {@link WhereHost} bound to `meta`, so {@link walkWhere} enumerates this scope's keys. */
202
208
  host: WhereHost;
@@ -321,12 +327,12 @@ export declare function targetGlobalFilterAlias(qi: BuilderCtx, targetTable: str
321
327
  /** Param-collect mirror of {@link targetGlobalFilterAlias}. */
322
328
  export declare function collectTargetGlobalFilterAlias(qi: BuilderCtx, targetTable: string, params: unknown[]): void;
323
329
  /**
324
- * SQL clause for `targetTable`'s global filter rendered against the bare
325
- * (unaliased) table name, the form used inside relation-filter `EXISTS`
326
- * subqueries. Pushes its params; `''` when none. Mirror:
327
- * {@link collectTargetGlobalFilterExists}.
330
+ * SQL clause for `targetTable`'s global filter rendered against `targetRef`,
331
+ * the relation-filter `EXISTS` subquery's FROM item (the bare table name, or
332
+ * its alias when {@link buildRelationFilter} had to alias it). Pushes its
333
+ * params; `''` when none. Mirror: {@link collectTargetGlobalFilterExists}.
328
334
  */
329
- export declare function targetGlobalFilterExists(qi: BuilderCtx, targetTable: string, params: unknown[]): string;
335
+ export declare function targetGlobalFilterExists(qi: BuilderCtx, targetTable: string, targetRef: string, params: unknown[]): string;
330
336
  /** Param-collect mirror of {@link targetGlobalFilterExists}. */
331
337
  export declare function collectTargetGlobalFilterExists(qi: BuilderCtx, targetTable: string, params: unknown[]): void;
332
338
  /**
@@ -372,10 +378,47 @@ export declare function buildScalarClause(qi: BuilderCtx, key: string, value: un
372
378
  */
373
379
  export declare function emptyRelationsHost(qi: BuilderCtx, table: string): WhereHost;
374
380
  export declare function scopedWhereHost(qi: BuilderCtx, meta: TableMetadata): WhereHost;
375
- /** Build the scope for a relation-filter EXISTS sub-where over the bare target table. */
376
- export declare function relationWhereScope(qi: BuilderCtx, targetTable: string, meta: TableMetadata): WhereScope;
377
- /** Build the scope for a relation `with`-clause `where` compiled against `alias`. */
381
+ /**
382
+ * Build the scope for a relation-filter EXISTS sub-where. `targetRef` is the
383
+ * subquery's FROM item as the SQL refers to it: the quoted target table, or the
384
+ * alias {@link buildRelationFilter} gave it when the bare name would have been
385
+ * captured by the correlation. Every column the sub-where names, and every
386
+ * nested relation filter's correlation parent, is qualified by that same
387
+ * reference, so the two can never point at different rows.
388
+ */
389
+ export declare function relationWhereScope(qi: BuilderCtx, targetTable: string, meta: TableMetadata, targetRef: string): WhereScope;
390
+ /**
391
+ * Build the scope for a relation `with`-clause `where` compiled against
392
+ * `alias`. The alias is BARE (`t0`), never a rendered reference: this scope
393
+ * quotes it itself for the nested-relation correlation parent, so a caller
394
+ * that hands over an already-quoted name gets `"""posts"""` inside an EXISTS
395
+ * body and a 42P01 that names the table three times. That happened twice, on
396
+ * two independent paths, from the same one-parameter-two-meanings ambiguity,
397
+ * so the ambiguity is refused here rather than corrected at each call site.
398
+ * A caller that genuinely holds a rendered reference wants
399
+ * {@link renderedRefWhereScope}.
400
+ */
378
401
  export declare function aliasWhereScope(qi: BuilderCtx, targetTable: string, meta: TableMetadata, alias: string): WhereScope;
402
+ /**
403
+ * The scope for a `where` compiled against an ALREADY-RENDERED FROM-item
404
+ * reference: `"users"` inside `ON CONFLICT ... DO UPDATE`, or the quoted child
405
+ * table a batched follow-up selects from. One parameter with one meaning, used
406
+ * verbatim for the column qualifier AND for a nested relation filter's
407
+ * correlation parent, which is exactly the pair {@link aliasWhereScope} gets
408
+ * wrong when it is handed a rendered reference.
409
+ *
410
+ * It is {@link relationWhereScope} with the unknown-column wording of a plain
411
+ * `where`, because the filters compiled through it are `globalFilters` entries
412
+ * and a misconfigured one should read the same here as it does on the `update`
413
+ * path.
414
+ */
415
+ export declare function renderedRefWhereScope(qi: BuilderCtx, targetTable: string, meta: TableMetadata, targetRef: string): WhereScope;
416
+ /**
417
+ * Compile `where` against an already-rendered FROM-item reference. The seam
418
+ * both global-filter callers that hold one go through, so neither can reach
419
+ * {@link aliasWhereScope} by mistake again.
420
+ */
421
+ export declare function buildRenderedRefWhere(qi: BuilderCtx, targetTable: string, targetMeta: TableMetadata, targetRef: string, where: Record<string, unknown>, params: unknown[]): string | null;
379
422
  /**
380
423
  * Compile a scoped sub-where to SQL. Serves BOTH the relation-filter EXISTS
381
424
  * body ({@link buildSubWhereForRelation}) and the relation `with`-clause
@@ -415,8 +458,31 @@ export declare function fingerprintScopedWhere(qi: BuilderCtx, host: WhereHost,
415
458
  /**
416
459
  * Build relation filter SQL: WHERE EXISTS / NOT EXISTS subquery
417
460
  * Supports: some (EXISTS), every (NOT EXISTS ... NOT), none (NOT EXISTS)
461
+ *
462
+ * NAME CAPTURE, and the aliasing rule that closes it. The subquery correlates
463
+ * its FROM item to the parent row (`target.fk = parent.pk`). When the target
464
+ * is named bare and the parent reference is that same bare name, a
465
+ * self-referencing relation (`comments.parent_id -> comments.id`) compiles
466
+ * `"comments"."parent_id" = "comments"."id"`, and SQL resolves BOTH sides to
467
+ * the inner row: `some: {}` matched nothing, `none: {}` matched everything,
468
+ * `is: null` matched every row, and the defect propagated through nested
469
+ * filters and into the batched loader's follow-up query (which is a top-level
470
+ * findMany on the child table). So the FROM item is aliased EXACTLY when its
471
+ * quoted name equals `parentRef`, and the alias is then used for the
472
+ * correlation, the sub-where qualifier, the global-filter fragment and as the
473
+ * parent reference of nested filters. Every other relation filter keeps the
474
+ * bare-table template it always emitted, byte for byte. The rule is complete
475
+ * rather than a heuristic: the only reference the inner FROM item can capture
476
+ * is one that spells its own name, and a nested level whose parent is already
477
+ * an alias (`rf0`, `t0`, `ord0`) has no such reference to capture. The
478
+ * junction of a manyToMany branch gets the same treatment for the same reason.
479
+ *
480
+ * `parentRef` is the RENDERED reference of the row being correlated against:
481
+ * the quoted table at the top level (`undefined` here), an alias inside a
482
+ * relation `with` where (`"t0"`), or the enclosing filter's FROM reference for
483
+ * a nested relation filter.
418
484
  */
419
- export declare function buildRelationFilter(qi: BuilderCtx, _relName: string, relDef: RelationDef, filterObj: Record<string, unknown>, params: unknown[], parentTable?: string,
485
+ export declare function buildRelationFilter(qi: BuilderCtx, _relName: string, relDef: RelationDef, filterObj: Record<string, unknown>, params: unknown[], parentRef?: string,
420
486
  /**
421
487
  * Nesting depth of the WHERE walk that reached this relation filter. Each
422
488
  * relation descent is a level too: `{ posts: { some: { comments: { some:
@@ -428,7 +494,7 @@ depth?: number): string | null;
428
494
  * Build WHERE clause conditions for a relation filter subquery.
429
495
  * Uses the target table's column mapping to resolve field names.
430
496
  */
431
- export declare function buildSubWhereForRelation(qi: BuilderCtx, targetTable: string, subWhere: Record<string, unknown>, params: unknown[], depth?: number): string | null;
497
+ export declare function buildSubWhereForRelation(qi: BuilderCtx, targetTable: string, targetRef: string, subWhere: Record<string, unknown>, params: unknown[], depth?: number): string | null;
432
498
  /**
433
499
  * Resolve a column's Postgres type from an arbitrary table's metadata
434
500
  * (relation targets, not just `qi.table`).