turbine-orm 0.27.0 → 0.28.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.
- package/README.md +17 -13
- package/dist/cjs/cli/config.js +20 -3
- package/dist/cjs/cli/destructive.js +47 -31
- package/dist/cjs/cli/index.js +273 -71
- package/dist/cjs/cli/mcp.js +788 -0
- package/dist/cjs/cli/migrate.js +95 -20
- package/dist/cjs/cli/studio.js +3 -2
- package/dist/cjs/client.js +267 -34
- package/dist/cjs/dialect.js +2 -0
- package/dist/cjs/generate.js +171 -7
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/introspect.js +177 -4
- package/dist/cjs/query/batched-loader.js +148 -0
- package/dist/cjs/query/builder.js +714 -133
- package/dist/cjs/schema-builder.js +59 -4
- package/dist/cjs/schema-sql.js +315 -6
- package/dist/cjs/seed.js +66 -0
- package/dist/cli/config.d.ts +9 -2
- package/dist/cli/config.js +19 -3
- package/dist/cli/destructive.js +47 -31
- package/dist/cli/index.d.ts +52 -1
- package/dist/cli/index.js +272 -74
- package/dist/cli/mcp.d.ts +17 -0
- package/dist/cli/mcp.js +781 -0
- package/dist/cli/migrate.d.ts +37 -0
- package/dist/cli/migrate.js +92 -20
- package/dist/cli/studio.d.ts +3 -2
- package/dist/cli/studio.js +3 -2
- package/dist/client.d.ts +136 -1
- package/dist/client.js +267 -34
- package/dist/dialect.d.ts +17 -0
- package/dist/dialect.js +2 -0
- package/dist/generate.d.ts +17 -0
- package/dist/generate.js +171 -10
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -0
- package/dist/introspect.d.ts +20 -1
- package/dist/introspect.js +175 -4
- package/dist/query/batched-loader.d.ts +29 -2
- package/dist/query/batched-loader.js +148 -1
- package/dist/query/builder.d.ts +156 -8
- package/dist/query/builder.js +715 -134
- package/dist/query/index.d.ts +1 -1
- package/dist/query/types.d.ts +113 -8
- package/dist/schema-builder.d.ts +73 -8
- package/dist/schema-builder.js +59 -4
- package/dist/schema-sql.d.ts +67 -0
- package/dist/schema-sql.js +310 -6
- package/dist/schema.d.ts +53 -0
- package/dist/seed.d.ts +4 -0
- package/dist/seed.js +63 -0
- package/package.json +2 -3
package/dist/query/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* `import { … } from './query/index.js'` is a drop-in replacement for the
|
|
6
6
|
* former monolithic `import { … } from './query.js'`.
|
|
7
7
|
*/
|
|
8
|
-
export type { AggregateArgs, AggregateResult, ArrayFilter, ConnectOrCreateOp, CountArgs, CreateArgs, CreateDataInput, CreateManyArgs, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GroupByArgs, HavingClause, JsonFilter, NestedCreateOp, NestedUpdateOp, NestedUpdateOpItem, NestedUpsertOpItem, OmitResult, OrderByClause, OrderDirection, QueryResult, RelationDescriptor, RelationFilter, RelationLoadStrategy, SelectResult, TextSearchFilter, TypedWithClause, UpdateArgs, UpdateDataInput, UpdateInput, UpdateManyArgs, UpdateOperatorInput, UpsertArgs, VectorDistanceFilter, VectorFilter, VectorMetric, VectorOrderBy, VectorOrderByDistance, WhereClause, WhereOperator, WhereValue, WithClause, WithOptions, WithResult, } from './types.js';
|
|
8
|
+
export type { AggregateArgs, AggregateResult, ArrayFilter, ConnectOrCreateOp, CountArgs, CreateArgs, CreateDataInput, CreateManyArgs, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GlobalFilters, GroupByArgs, HavingClause, JsonFilter, NestedCreateOp, NestedUpdateOp, NestedUpdateOpItem, NestedUpsertOpItem, OmitResult, OrderByClause, OrderDirection, QueryResult, RelationDescriptor, RelationFilter, RelationLoadStrategy, SelectResult, SkipGlobalFilters, TextSearchFilter, TypedWithClause, UpdateArgs, UpdateDataInput, UpdateInput, UpdateManyArgs, UpdateOperatorInput, UpsertArgs, VectorDistanceFilter, VectorFilter, VectorMetric, VectorOrderBy, VectorOrderByDistance, WhereClause, WhereOperator, WhereValue, WithClause, WithOptions, WithResult, } from './types.js';
|
|
9
9
|
export type { BuiltStatement, BulkInsertStatementInput, ColumnDefinitionInput, ColumnTypeInput, CreateIndexStatementInput, CreateTableStatementInput, Dialect, InsertStatementInput, UpsertStatementInput, } from '../dialect.js';
|
|
10
10
|
export { postgresDialect } from '../dialect.js';
|
|
11
11
|
export type { SqlCacheEntry } from './utils.js';
|
package/dist/query/types.d.ts
CHANGED
|
@@ -79,9 +79,41 @@ export type WhereClause<T> = {
|
|
|
79
79
|
/** Relation filters — keyed by relation name, value is { some, every, none } */
|
|
80
80
|
[relationName: string]: unknown;
|
|
81
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* Client-level automatic WHERE filters, keyed by table accessor (the name used
|
|
84
|
+
* in `db[name]` / `client.table(name)`). Each value is AND-merged into the
|
|
85
|
+
* compiled WHERE of every read and mutation on that table, and into every
|
|
86
|
+
* relation subquery that targets it — the mechanism behind soft-delete and
|
|
87
|
+
* multi-tenancy. A function value is evaluated at query-build time (per query),
|
|
88
|
+
* so a closure over per-request state (e.g. the current tenant id) enables
|
|
89
|
+
* request-scoped filters. `create`/`createMany` are never filtered.
|
|
90
|
+
*/
|
|
91
|
+
export type GlobalFilters = {
|
|
92
|
+
[tableAccessor: string]: WhereClause<any> | (() => WhereClause<any>);
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Per-query opt-out of the configured {@link GlobalFilters}. `true` skips the
|
|
96
|
+
* global filter on the query's own table AND on every relation target it
|
|
97
|
+
* touches; an array skips only the named table accessors (own table and/or
|
|
98
|
+
* relation targets). Global filters never satisfy the empty-`where` guard for
|
|
99
|
+
* `update`/`delete` — that guard always checks the user-supplied `where`.
|
|
100
|
+
*/
|
|
101
|
+
export type SkipGlobalFilters = true | readonly string[];
|
|
102
|
+
/**
|
|
103
|
+
* Reserved key in a `with` clause that requests correlated relation counts.
|
|
104
|
+
* `_count: true` counts every to-many relation of the table; a record form
|
|
105
|
+
* (`_count: { posts: true }`) counts only the named to-many relations. Each
|
|
106
|
+
* selected relation resolves to a number on the result row's `_count` object.
|
|
107
|
+
*/
|
|
108
|
+
export type WithCount = true | Record<string, true>;
|
|
82
109
|
/**
|
|
83
110
|
* Unparameterized with clause — accepts any relation name.
|
|
84
111
|
* Used internally by the query builder at runtime.
|
|
112
|
+
*
|
|
113
|
+
* The reserved `_count` key (see {@link WithCount}) is also accepted at runtime;
|
|
114
|
+
* the builder reads it via a cast so the narrow `true | WithOptions` element
|
|
115
|
+
* type is preserved for the relation-subquery machinery. Typed callers get a
|
|
116
|
+
* fully-typed `_count` through {@link TypedWithClause}.
|
|
85
117
|
*/
|
|
86
118
|
export interface WithClause {
|
|
87
119
|
[relation: string]: true | WithOptions;
|
|
@@ -98,6 +130,11 @@ export interface WithClause {
|
|
|
98
130
|
*/
|
|
99
131
|
export type TypedWithClause<R extends object = {}> = [keyof R] extends [never] ? WithClause : {
|
|
100
132
|
[K in keyof R]?: true | WithOptions<RelationRelations<R[K]> & object>;
|
|
133
|
+
} & {
|
|
134
|
+
/** Reserved: correlated relation counts. `true` counts all to-many relations. */
|
|
135
|
+
_count?: true | {
|
|
136
|
+
[K in keyof R]?: true;
|
|
137
|
+
};
|
|
101
138
|
};
|
|
102
139
|
/**
|
|
103
140
|
* Options for an included relation.
|
|
@@ -111,7 +148,7 @@ export type TypedWithClause<R extends object = {}> = [keyof R] extends [never] ?
|
|
|
111
148
|
export interface WithOptions<NestedR extends object = {}> {
|
|
112
149
|
with?: TypedWithClause<NestedR>;
|
|
113
150
|
where?: Record<string, unknown>;
|
|
114
|
-
orderBy?: Record<string, OrderDirection>;
|
|
151
|
+
orderBy?: Record<string, OrderDirection | OrderBySpec>;
|
|
115
152
|
limit?: number;
|
|
116
153
|
/** Only include these fields from the relation */
|
|
117
154
|
select?: Record<string, boolean>;
|
|
@@ -194,11 +231,31 @@ type ApplyCardinality<Rel, Resolved> = Rel extends RelationDescriptor<infer _T,
|
|
|
194
231
|
* @typeParam W - The `with` clause the user passed (e.g. `{ posts: true }` or
|
|
195
232
|
* `{ posts: { with: { comments: true } } }`).
|
|
196
233
|
*/
|
|
197
|
-
export type WithResult<T, R extends object, W> = [keyof R] extends [never] ? T : W extends object ?
|
|
234
|
+
export type WithResult<T, R extends object, W> = [keyof R] extends [never] ? T : W extends object ? W extends {
|
|
235
|
+
_count: infer C;
|
|
236
|
+
} ? // `_count` requested — add the typed count object alongside any relations.
|
|
237
|
+
WithRelationAdditions<T, R, W> & {
|
|
238
|
+
_count: CountResult<C>;
|
|
239
|
+
} : WithRelationAdditions<T, R, W> : T;
|
|
240
|
+
/**
|
|
241
|
+
* The relation additions grafted onto `T` by a `with` clause (the `_count`
|
|
242
|
+
* reserved key is handled separately by {@link WithResult}). Kept as its own
|
|
243
|
+
* alias so the no-`_count` path stays byte-identical to the pre-`_count` type.
|
|
244
|
+
*/
|
|
245
|
+
type WithRelationAdditions<T, R extends object, W> = [keyof W & keyof R] extends [never] ? T : T & {
|
|
198
246
|
[K in keyof W & keyof R]: W[K] extends true ? ApplyCardinality<R[K], RelationTarget<R[K]>> : W[K] extends {
|
|
199
247
|
with?: infer NestedW;
|
|
200
248
|
} ? NestedW extends object ? ApplyCardinality<R[K], WithResult<RelationTarget<R[K]>, RelationRelations<R[K]> & object, NestedW>> : ApplyCardinality<R[K], RelationTarget<R[K]>> : ApplyCardinality<R[K], RelationTarget<R[K]>>;
|
|
201
|
-
}
|
|
249
|
+
};
|
|
250
|
+
/**
|
|
251
|
+
* Compute the shape of the `_count` object on a result row. `_count: true`
|
|
252
|
+
* counts every to-many relation (keys unknown at the type level → an open
|
|
253
|
+
* `Record<string, number>`); the record form (`_count: { posts: true }`) yields
|
|
254
|
+
* `{ [K in selected]: number }`.
|
|
255
|
+
*/
|
|
256
|
+
type CountResult<C> = C extends true ? Record<string, number> : C extends object ? {
|
|
257
|
+
[K in keyof C]: number;
|
|
258
|
+
} : Record<string, number>;
|
|
202
259
|
/** Extract keys from a boolean record where the value is `true`. */
|
|
203
260
|
type TrueKeys<S extends Record<string, boolean>> = {
|
|
204
261
|
[K in keyof S]: S[K] extends true ? K : never;
|
|
@@ -230,6 +287,8 @@ export interface FindUniqueArgs<T, R extends object = {}, W extends TypedWithCla
|
|
|
230
287
|
timeout?: number;
|
|
231
288
|
/** Override the client's relation-loading strategy for this query. See {@link RelationLoadStrategy}. */
|
|
232
289
|
relationLoadStrategy?: RelationLoadStrategy;
|
|
290
|
+
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
291
|
+
skipGlobalFilters?: SkipGlobalFilters;
|
|
233
292
|
}
|
|
234
293
|
export interface FindManyArgs<T, R extends object = {}, W extends TypedWithClause<R> = TypedWithClause<R>, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined> {
|
|
235
294
|
where?: WhereClause<T>;
|
|
@@ -249,6 +308,8 @@ export interface FindManyArgs<T, R extends object = {}, W extends TypedWithClaus
|
|
|
249
308
|
timeout?: number;
|
|
250
309
|
/** Override the client's relation-loading strategy for this query. See {@link RelationLoadStrategy}. */
|
|
251
310
|
relationLoadStrategy?: RelationLoadStrategy;
|
|
311
|
+
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
312
|
+
skipGlobalFilters?: SkipGlobalFilters;
|
|
252
313
|
}
|
|
253
314
|
export interface FindManyStreamArgs<T, R extends object = {}, W extends TypedWithClause<R> = TypedWithClause<R>, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined> extends FindManyArgs<T, R, W, S, O> {
|
|
254
315
|
/**
|
|
@@ -349,6 +410,8 @@ export interface UpdateArgs<T, R extends object = {}> {
|
|
|
349
410
|
field: keyof T & string;
|
|
350
411
|
expected: number;
|
|
351
412
|
};
|
|
413
|
+
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
414
|
+
skipGlobalFilters?: SkipGlobalFilters;
|
|
352
415
|
}
|
|
353
416
|
export interface UpdateManyArgs<T> {
|
|
354
417
|
where: WhereClause<T>;
|
|
@@ -357,6 +420,8 @@ export interface UpdateManyArgs<T> {
|
|
|
357
420
|
timeout?: number;
|
|
358
421
|
/** See {@link UpdateArgs.allowFullTableScan}. */
|
|
359
422
|
allowFullTableScan?: boolean;
|
|
423
|
+
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
424
|
+
skipGlobalFilters?: SkipGlobalFilters;
|
|
360
425
|
}
|
|
361
426
|
export interface DeleteArgs<T> {
|
|
362
427
|
where: WhereClause<T>;
|
|
@@ -364,6 +429,8 @@ export interface DeleteArgs<T> {
|
|
|
364
429
|
timeout?: number;
|
|
365
430
|
/** See {@link UpdateArgs.allowFullTableScan}. */
|
|
366
431
|
allowFullTableScan?: boolean;
|
|
432
|
+
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
433
|
+
skipGlobalFilters?: SkipGlobalFilters;
|
|
367
434
|
}
|
|
368
435
|
export interface DeleteManyArgs<T> {
|
|
369
436
|
where: WhereClause<T>;
|
|
@@ -371,6 +438,8 @@ export interface DeleteManyArgs<T> {
|
|
|
371
438
|
timeout?: number;
|
|
372
439
|
/** See {@link UpdateArgs.allowFullTableScan}. */
|
|
373
440
|
allowFullTableScan?: boolean;
|
|
441
|
+
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
442
|
+
skipGlobalFilters?: SkipGlobalFilters;
|
|
374
443
|
}
|
|
375
444
|
export interface UpsertArgs<T> {
|
|
376
445
|
where: WhereClause<T>;
|
|
@@ -378,6 +447,8 @@ export interface UpsertArgs<T> {
|
|
|
378
447
|
update: Partial<T>;
|
|
379
448
|
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
380
449
|
timeout?: number;
|
|
450
|
+
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
451
|
+
skipGlobalFilters?: SkipGlobalFilters;
|
|
381
452
|
}
|
|
382
453
|
/** `connectOrCreate` op: connect to the row matching `where`, or create it. */
|
|
383
454
|
export interface ConnectOrCreateOp<T, TR extends object = {}> {
|
|
@@ -434,6 +505,8 @@ export interface CountArgs<T> {
|
|
|
434
505
|
where?: WhereClause<T>;
|
|
435
506
|
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
436
507
|
timeout?: number;
|
|
508
|
+
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
509
|
+
skipGlobalFilters?: SkipGlobalFilters;
|
|
437
510
|
}
|
|
438
511
|
/**
|
|
439
512
|
* Numeric comparison operators usable inside a `having` filter. A bare number
|
|
@@ -503,10 +576,12 @@ export interface GroupByArgs<T> {
|
|
|
503
576
|
_max?: Partial<Record<keyof T & string, boolean>>;
|
|
504
577
|
/** Filter whole groups by their aggregate values (SQL HAVING). */
|
|
505
578
|
having?: HavingClause<T>;
|
|
506
|
-
/** Order groups */
|
|
507
|
-
orderBy?: Record<string, OrderDirection>;
|
|
579
|
+
/** Order groups (supports {@link OrderBySpec} for NULLS placement). */
|
|
580
|
+
orderBy?: Record<string, OrderDirection | OrderBySpec>;
|
|
508
581
|
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
509
582
|
timeout?: number;
|
|
583
|
+
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
584
|
+
skipGlobalFilters?: SkipGlobalFilters;
|
|
510
585
|
}
|
|
511
586
|
/** Arguments for the standalone aggregate method */
|
|
512
587
|
export interface AggregateArgs<T> {
|
|
@@ -523,6 +598,8 @@ export interface AggregateArgs<T> {
|
|
|
523
598
|
_max?: Partial<Record<keyof T & string, boolean>>;
|
|
524
599
|
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
525
600
|
timeout?: number;
|
|
601
|
+
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
602
|
+
skipGlobalFilters?: SkipGlobalFilters;
|
|
526
603
|
}
|
|
527
604
|
/** Result type for aggregate queries */
|
|
528
605
|
export interface AggregateResult<T> {
|
|
@@ -634,8 +711,36 @@ export interface VectorOrderBy {
|
|
|
634
711
|
distance: VectorOrderByDistance;
|
|
635
712
|
}
|
|
636
713
|
/**
|
|
637
|
-
*
|
|
638
|
-
* `'desc'
|
|
714
|
+
* Explicit ordering spec for a column: a direction plus an optional NULLS
|
|
715
|
+
* placement. `{ sort: 'desc', nulls: 'last' }` compiles to
|
|
716
|
+
* `ORDER BY "col" DESC NULLS LAST`. The plain `'asc' | 'desc'` shorthand is
|
|
717
|
+
* still accepted and unchanged. `nulls` is only emitted on engines that
|
|
718
|
+
* support the `NULLS FIRST/LAST` grammar (PostgreSQL, SQLite); requesting it
|
|
719
|
+
* elsewhere throws {@link UnsupportedFeatureError} (E017).
|
|
720
|
+
*/
|
|
721
|
+
export interface OrderBySpec {
|
|
722
|
+
sort: OrderDirection;
|
|
723
|
+
nulls?: 'first' | 'last';
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Ordering by a relation, keyed by the relation name in an {@link OrderByClause}:
|
|
727
|
+
*
|
|
728
|
+
* - to-many (hasMany / manyToMany): `{ posts: { _count: 'desc' } }` — orders by
|
|
729
|
+
* a correlated `COUNT(*)` of the related rows.
|
|
730
|
+
* - to-one (belongsTo / hasOne): `{ author: { name: 'asc' } }` — orders by a
|
|
731
|
+
* correlated scalar subquery on the target column (an {@link OrderBySpec} with
|
|
732
|
+
* `nulls` is accepted too).
|
|
733
|
+
*/
|
|
734
|
+
export type RelationOrderBy = {
|
|
735
|
+
_count: OrderDirection;
|
|
736
|
+
} | Record<string, OrderDirection | OrderBySpec>;
|
|
737
|
+
/**
|
|
738
|
+
* An orderBy clause maps each key to one of:
|
|
739
|
+
* - a plain direction (`'asc'` / `'desc'`),
|
|
740
|
+
* - an {@link OrderBySpec} (`{ sort, nulls }`) for NULLS placement,
|
|
741
|
+
* - for pgvector columns, a KNN distance ordering ({@link VectorOrderBy}),
|
|
742
|
+
* - for a relation name, a {@link RelationOrderBy} (`_count` for to-many, a
|
|
743
|
+
* target column for to-one).
|
|
639
744
|
*/
|
|
640
|
-
export type OrderByClause = Record<string, OrderDirection | VectorOrderBy>;
|
|
745
|
+
export type OrderByClause = Record<string, OrderDirection | OrderBySpec | VectorOrderBy | RelationOrderBy>;
|
|
641
746
|
export {};
|
package/dist/schema-builder.d.ts
CHANGED
|
@@ -22,9 +22,19 @@
|
|
|
22
22
|
* });
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
|
-
import type { SchemaMetadata } from './schema.js';
|
|
25
|
+
import type { ReferentialAction, SchemaMetadata } from './schema.js';
|
|
26
|
+
export type { ReferentialAction } from './schema.js';
|
|
26
27
|
/** Shorthand type names that map to Postgres column types */
|
|
27
|
-
export type ColumnTypeName = 'serial' | 'bigserial' | 'bigint' | 'integer' | 'smallint' | 'text' | 'varchar' | 'boolean' | 'timestamp' | 'timestamptz' | 'date' | 'json' | 'jsonb' | 'uuid' | 'real' | 'double' | 'numeric' | 'bytea';
|
|
28
|
+
export type ColumnTypeName = 'serial' | 'bigserial' | 'bigint' | 'integer' | 'smallint' | 'text' | 'varchar' | 'boolean' | 'timestamp' | 'timestamptz' | 'date' | 'json' | 'jsonb' | 'uuid' | 'real' | 'double' | 'numeric' | 'bytea' | 'enum' | 'vector';
|
|
29
|
+
/** Foreign key reference with optional referential actions. */
|
|
30
|
+
export interface ReferenceDef {
|
|
31
|
+
/** REFERENCES target in "table.column" form. */
|
|
32
|
+
target: string;
|
|
33
|
+
/** `ON DELETE` action. Omit for the Postgres default (`NO ACTION`). */
|
|
34
|
+
onDelete?: ReferentialAction;
|
|
35
|
+
/** `ON UPDATE` action. Omit for the Postgres default (`NO ACTION`). */
|
|
36
|
+
onUpdate?: ReferentialAction;
|
|
37
|
+
}
|
|
28
38
|
/** Column definition as a plain object. This is what users write. */
|
|
29
39
|
export interface ColumnDef {
|
|
30
40
|
/** Column type (required) */
|
|
@@ -39,13 +49,24 @@ export interface ColumnDef {
|
|
|
39
49
|
unique?: boolean;
|
|
40
50
|
/** DEFAULT expression (raw SQL, e.g. 'now()' or "'active'") */
|
|
41
51
|
default?: string;
|
|
42
|
-
/**
|
|
43
|
-
|
|
52
|
+
/**
|
|
53
|
+
* REFERENCES target. Either the "table.column" string form or an object with
|
|
54
|
+
* referential actions ({@link ReferenceDef}).
|
|
55
|
+
*/
|
|
56
|
+
references?: string | ReferenceDef;
|
|
44
57
|
/** Max length for varchar columns */
|
|
45
58
|
maxLength?: number;
|
|
59
|
+
/** Enum type name — required when `type: 'enum'`. */
|
|
60
|
+
enumName?: string;
|
|
61
|
+
/** pgvector dimension count — required when `type: 'vector'`. */
|
|
62
|
+
dimensions?: number;
|
|
63
|
+
/** When true, the column is an array of `type` (e.g. `text[]`). */
|
|
64
|
+
array?: boolean;
|
|
65
|
+
/** Column-level `CHECK` expression (raw SQL, e.g. `price >= 0`). */
|
|
66
|
+
check?: string;
|
|
46
67
|
}
|
|
47
68
|
/** Postgres-level column type (uppercase, as used in DDL) */
|
|
48
|
-
export type ColumnType = 'SERIAL' | 'BIGSERIAL' | 'BIGINT' | 'INTEGER' | 'SMALLINT' | 'TEXT' | 'BOOLEAN' | 'TIMESTAMPTZ' | 'JSONB' | 'UUID' | 'REAL' | 'DOUBLE PRECISION' | 'NUMERIC' | 'BYTEA' | 'DATE' | 'VARCHAR';
|
|
69
|
+
export type ColumnType = 'SERIAL' | 'BIGSERIAL' | 'BIGINT' | 'INTEGER' | 'SMALLINT' | 'TEXT' | 'BOOLEAN' | 'TIMESTAMPTZ' | 'JSONB' | 'UUID' | 'REAL' | 'DOUBLE PRECISION' | 'NUMERIC' | 'BYTEA' | 'DATE' | 'VARCHAR' | 'ENUM' | 'VECTOR';
|
|
49
70
|
export interface ColumnConfig {
|
|
50
71
|
type: ColumnType;
|
|
51
72
|
isPrimaryKey: boolean;
|
|
@@ -55,6 +76,18 @@ export interface ColumnConfig {
|
|
|
55
76
|
defaultValue: string | null;
|
|
56
77
|
referencesTarget: string | null;
|
|
57
78
|
maxLength: number | null;
|
|
79
|
+
/** FK `ON DELETE` action, or null for the Postgres default. */
|
|
80
|
+
onDelete: ReferentialAction | null;
|
|
81
|
+
/** FK `ON UPDATE` action, or null for the Postgres default. */
|
|
82
|
+
onUpdate: ReferentialAction | null;
|
|
83
|
+
/** Enum type name when `type === 'ENUM'`, else null. */
|
|
84
|
+
enumName: string | null;
|
|
85
|
+
/** pgvector dimensions when `type === 'VECTOR'`, else null. */
|
|
86
|
+
vectorDimensions: number | null;
|
|
87
|
+
/** Whether the column is an array of its base type. */
|
|
88
|
+
isArray: boolean;
|
|
89
|
+
/** Column-level `CHECK` expression, or null. */
|
|
90
|
+
check: string | null;
|
|
58
91
|
}
|
|
59
92
|
/**
|
|
60
93
|
* Explicit many-to-many relation declaration for the code-first schema.
|
|
@@ -86,6 +119,13 @@ export interface ManyToManyDef {
|
|
|
86
119
|
*/
|
|
87
120
|
references?: string | readonly string[];
|
|
88
121
|
}
|
|
122
|
+
/** A table-level named (or unnamed) `CHECK` constraint. */
|
|
123
|
+
export interface CheckDef {
|
|
124
|
+
/** Optional constraint name → `CONSTRAINT "name" CHECK (...)`. */
|
|
125
|
+
name?: string;
|
|
126
|
+
/** Raw SQL boolean expression, e.g. `price > cost`. */
|
|
127
|
+
expression: string;
|
|
128
|
+
}
|
|
89
129
|
export interface TableDef {
|
|
90
130
|
/**
|
|
91
131
|
* DDL-facing table name (snake_case). This is the name used when generating
|
|
@@ -117,6 +157,8 @@ export interface TableDef {
|
|
|
117
157
|
* {@link SchemaMetadata} with `manyToMany` {@link RelationDef}s.
|
|
118
158
|
*/
|
|
119
159
|
manyToMany?: readonly ManyToManyDef[];
|
|
160
|
+
/** Table-level `CHECK` constraints. */
|
|
161
|
+
checks?: readonly CheckDef[];
|
|
120
162
|
}
|
|
121
163
|
/**
|
|
122
164
|
* User-facing input shape for a single table when using the object format.
|
|
@@ -127,8 +169,10 @@ export interface TableInput {
|
|
|
127
169
|
primaryKey?: readonly string[];
|
|
128
170
|
/** Optional explicit many-to-many relations on this table */
|
|
129
171
|
manyToMany?: readonly ManyToManyDef[];
|
|
172
|
+
/** Optional table-level CHECK constraints */
|
|
173
|
+
checks?: readonly CheckDef[];
|
|
130
174
|
/** Column definitions keyed by camelCase field name */
|
|
131
|
-
[columnName: string]: ColumnDef | readonly string[] | readonly ManyToManyDef[] | undefined;
|
|
175
|
+
[columnName: string]: ColumnDef | readonly string[] | readonly ManyToManyDef[] | readonly CheckDef[] | undefined;
|
|
132
176
|
}
|
|
133
177
|
export interface SchemaDef {
|
|
134
178
|
/**
|
|
@@ -137,6 +181,22 @@ export interface SchemaDef {
|
|
|
137
181
|
* name is available as `tables[key].name`.
|
|
138
182
|
*/
|
|
139
183
|
tables: Record<string, TableDef>;
|
|
184
|
+
/**
|
|
185
|
+
* Schema-level enum declarations (`CREATE TYPE "<name>" AS ENUM (...)`),
|
|
186
|
+
* keyed by DDL enum type name → ordered labels. Consumed by `schema-sql.ts`
|
|
187
|
+
* to emit `CREATE TYPE` before the tables that reference them and by
|
|
188
|
+
* `generate.ts` for string-literal union codegen. Omitted when no enums are
|
|
189
|
+
* declared (back-compat with `{ tables }`-only consumers).
|
|
190
|
+
*/
|
|
191
|
+
enums?: Record<string, readonly string[]>;
|
|
192
|
+
}
|
|
193
|
+
/** Options accepted by {@link defineSchema}. */
|
|
194
|
+
export interface DefineSchemaOptions {
|
|
195
|
+
/**
|
|
196
|
+
* Schema-level enum declarations, keyed by DDL enum type name → ordered
|
|
197
|
+
* labels. Columns opt in via `{ type: 'enum', enumName: '<name>' }`.
|
|
198
|
+
*/
|
|
199
|
+
enums?: Record<string, readonly string[]>;
|
|
140
200
|
}
|
|
141
201
|
/** Input format: table name -> column defs (object format) or TableDef (legacy builder) */
|
|
142
202
|
type SchemaInput = Record<string, Record<string, ColumnDef> | TableDef | TableInput>;
|
|
@@ -159,7 +219,7 @@ type SchemaInput = Record<string, Record<string, ColumnDef> | TableDef | TableIn
|
|
|
159
219
|
* });
|
|
160
220
|
* ```
|
|
161
221
|
*/
|
|
162
|
-
export declare function defineSchema(input: SchemaInput): SchemaDef;
|
|
222
|
+
export declare function defineSchema(input: SchemaInput, options?: DefineSchemaOptions): SchemaDef;
|
|
163
223
|
export declare class ColumnBuilder {
|
|
164
224
|
private _config;
|
|
165
225
|
constructor();
|
|
@@ -186,7 +246,12 @@ export declare class ColumnBuilder {
|
|
|
186
246
|
nullable(): this;
|
|
187
247
|
unique(): this;
|
|
188
248
|
default(val: string): this;
|
|
189
|
-
references(target: string
|
|
249
|
+
references(target: string, opts?: {
|
|
250
|
+
onDelete?: ReferentialAction;
|
|
251
|
+
onUpdate?: ReferentialAction;
|
|
252
|
+
}): this;
|
|
253
|
+
check(expression: string): this;
|
|
254
|
+
array(): this;
|
|
190
255
|
build(): ColumnConfig;
|
|
191
256
|
}
|
|
192
257
|
/** @deprecated Use defineSchema() with plain objects instead */
|
package/dist/schema-builder.js
CHANGED
|
@@ -51,12 +51,34 @@ const TYPE_MAP = {
|
|
|
51
51
|
double: 'DOUBLE PRECISION',
|
|
52
52
|
numeric: 'NUMERIC',
|
|
53
53
|
bytea: 'BYTEA',
|
|
54
|
+
// Sentinels — the real DDL type is derived from `enumName` / `dimensions`
|
|
55
|
+
// in schema-sql.ts, never from these placeholders.
|
|
56
|
+
enum: 'ENUM',
|
|
57
|
+
vector: 'VECTOR',
|
|
54
58
|
};
|
|
55
59
|
/** Convert a user-facing ColumnDef to the internal ColumnConfig */
|
|
56
60
|
function resolveColumn(def) {
|
|
57
61
|
if (!(def.type in TYPE_MAP)) {
|
|
58
62
|
throw new Error(`Invalid column type "${def.type}". Valid types: ${Object.keys(TYPE_MAP).join(', ')}`);
|
|
59
63
|
}
|
|
64
|
+
if (def.type === 'enum' && !def.enumName) {
|
|
65
|
+
throw new Error(`Column of type "enum" requires an "enumName" (the CREATE TYPE name).`);
|
|
66
|
+
}
|
|
67
|
+
if (def.type === 'vector' && (def.dimensions == null || def.dimensions <= 0)) {
|
|
68
|
+
throw new Error(`Column of type "vector" requires a positive "dimensions" count.`);
|
|
69
|
+
}
|
|
70
|
+
// `references` is either the "table.column" string or a { target, onDelete, onUpdate } object.
|
|
71
|
+
let referencesTarget = null;
|
|
72
|
+
let onDelete = null;
|
|
73
|
+
let onUpdate = null;
|
|
74
|
+
if (typeof def.references === 'string') {
|
|
75
|
+
referencesTarget = def.references;
|
|
76
|
+
}
|
|
77
|
+
else if (def.references) {
|
|
78
|
+
referencesTarget = def.references.target;
|
|
79
|
+
onDelete = def.references.onDelete ?? null;
|
|
80
|
+
onUpdate = def.references.onUpdate ?? null;
|
|
81
|
+
}
|
|
60
82
|
return {
|
|
61
83
|
type: TYPE_MAP[def.type],
|
|
62
84
|
isPrimaryKey: def.primaryKey ?? false,
|
|
@@ -64,8 +86,14 @@ function resolveColumn(def) {
|
|
|
64
86
|
isNullable: def.nullable ?? false,
|
|
65
87
|
isUnique: def.unique ?? false,
|
|
66
88
|
defaultValue: def.default ?? null,
|
|
67
|
-
referencesTarget
|
|
89
|
+
referencesTarget,
|
|
68
90
|
maxLength: def.maxLength ?? null,
|
|
91
|
+
onDelete,
|
|
92
|
+
onUpdate,
|
|
93
|
+
enumName: def.enumName ?? null,
|
|
94
|
+
vectorDimensions: def.dimensions ?? null,
|
|
95
|
+
isArray: def.array ?? false,
|
|
96
|
+
check: def.check ?? null,
|
|
69
97
|
};
|
|
70
98
|
}
|
|
71
99
|
/** Check if a value is a TableDef (from legacy table() builder) */
|
|
@@ -91,7 +119,7 @@ function isTableDef(v) {
|
|
|
91
119
|
* });
|
|
92
120
|
* ```
|
|
93
121
|
*/
|
|
94
|
-
export function defineSchema(input) {
|
|
122
|
+
export function defineSchema(input, options) {
|
|
95
123
|
const tables = {};
|
|
96
124
|
for (const [accessor, value] of Object.entries(input)) {
|
|
97
125
|
// The user-facing key is the camelCase JS accessor; the DDL-facing
|
|
@@ -110,6 +138,7 @@ export function defineSchema(input) {
|
|
|
110
138
|
const columns = {};
|
|
111
139
|
let pk;
|
|
112
140
|
let m2m;
|
|
141
|
+
let checks;
|
|
113
142
|
for (const [fieldName, def] of Object.entries(raw)) {
|
|
114
143
|
if (fieldName === 'manyToMany') {
|
|
115
144
|
if (def !== undefined) {
|
|
@@ -120,6 +149,15 @@ export function defineSchema(input) {
|
|
|
120
149
|
}
|
|
121
150
|
continue;
|
|
122
151
|
}
|
|
152
|
+
if (fieldName === 'checks') {
|
|
153
|
+
if (def !== undefined) {
|
|
154
|
+
if (!Array.isArray(def)) {
|
|
155
|
+
throw new Error(`Table "${accessor}": "checks" must be an array of { name?, expression } objects`);
|
|
156
|
+
}
|
|
157
|
+
checks = def;
|
|
158
|
+
}
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
123
161
|
if (fieldName === 'primaryKey') {
|
|
124
162
|
// Top-level composite primary key declaration
|
|
125
163
|
if (def !== undefined) {
|
|
@@ -160,10 +198,11 @@ export function defineSchema(input) {
|
|
|
160
198
|
columns,
|
|
161
199
|
...(pk && pk.length > 0 ? { primaryKey: pk } : {}),
|
|
162
200
|
...(m2m && m2m.length > 0 ? { manyToMany: m2m } : {}),
|
|
201
|
+
...(checks && checks.length > 0 ? { checks } : {}),
|
|
163
202
|
};
|
|
164
203
|
}
|
|
165
204
|
}
|
|
166
|
-
return { tables };
|
|
205
|
+
return { tables, ...(options?.enums ? { enums: options.enums } : {}) };
|
|
167
206
|
}
|
|
168
207
|
/**
|
|
169
208
|
* Local copy of camelToSnake to avoid a circular import dependency at the
|
|
@@ -187,6 +226,12 @@ export class ColumnBuilder {
|
|
|
187
226
|
defaultValue: null,
|
|
188
227
|
referencesTarget: null,
|
|
189
228
|
maxLength: null,
|
|
229
|
+
onDelete: null,
|
|
230
|
+
onUpdate: null,
|
|
231
|
+
enumName: null,
|
|
232
|
+
vectorDimensions: null,
|
|
233
|
+
isArray: false,
|
|
234
|
+
check: null,
|
|
190
235
|
};
|
|
191
236
|
}
|
|
192
237
|
serial() {
|
|
@@ -283,8 +328,18 @@ export class ColumnBuilder {
|
|
|
283
328
|
this._config.defaultValue = val;
|
|
284
329
|
return this;
|
|
285
330
|
}
|
|
286
|
-
references(target) {
|
|
331
|
+
references(target, opts) {
|
|
287
332
|
this._config.referencesTarget = target;
|
|
333
|
+
this._config.onDelete = opts?.onDelete ?? null;
|
|
334
|
+
this._config.onUpdate = opts?.onUpdate ?? null;
|
|
335
|
+
return this;
|
|
336
|
+
}
|
|
337
|
+
check(expression) {
|
|
338
|
+
this._config.check = expression;
|
|
339
|
+
return this;
|
|
340
|
+
}
|
|
341
|
+
array() {
|
|
342
|
+
this._config.isArray = true;
|
|
288
343
|
return this;
|
|
289
344
|
}
|
|
290
345
|
build() {
|
package/dist/schema-sql.d.ts
CHANGED
|
@@ -5,11 +5,21 @@
|
|
|
5
5
|
* Also provides diff and push commands for syncing schema to a live database.
|
|
6
6
|
*/
|
|
7
7
|
import { type Dialect } from './dialect.js';
|
|
8
|
+
import { type ReferentialAction } from './schema.js';
|
|
8
9
|
import type { SchemaDef, TableDef } from './schema-builder.js';
|
|
9
10
|
export interface SchemaSqlOptions {
|
|
10
11
|
/** SQL dialect used for DDL generation. Defaults to PostgreSQL. */
|
|
11
12
|
dialect?: Dialect;
|
|
13
|
+
/**
|
|
14
|
+
* How to handle the pgvector extension when the schema contains a `vector`
|
|
15
|
+
* column. `'auto'` (default) prepends `CREATE EXTENSION IF NOT EXISTS vector;`
|
|
16
|
+
* — appropriate for `push`. `'manual'` emits a leading comment only, so the
|
|
17
|
+
* generated `.sql` migration doesn't silently require superuser privileges.
|
|
18
|
+
*/
|
|
19
|
+
extensions?: 'auto' | 'manual';
|
|
12
20
|
}
|
|
21
|
+
/** Map a {@link ReferentialAction} to its SQL keyword form. */
|
|
22
|
+
export declare function referentialActionToSql(action: ReferentialAction): string;
|
|
13
23
|
/**
|
|
14
24
|
* Convert a SchemaDef into an ordered array of SQL DDL statements.
|
|
15
25
|
*
|
|
@@ -44,7 +54,64 @@ export interface DiffResult {
|
|
|
44
54
|
statements: string[];
|
|
45
55
|
/** SQL statements to reverse the diff (DOWN direction, for migrations) */
|
|
46
56
|
reverseStatements: string[];
|
|
57
|
+
/**
|
|
58
|
+
* Human-readable warnings for changes the diff detected but refuses to apply
|
|
59
|
+
* automatically because they are destructive or otherwise unsafe (enum value
|
|
60
|
+
* removal/reorder, etc.). Never executed — surfaced for the operator.
|
|
61
|
+
*/
|
|
62
|
+
warnings?: string[];
|
|
47
63
|
}
|
|
64
|
+
/** A FK's current referential actions as read from the DB. */
|
|
65
|
+
export interface DbForeignKey {
|
|
66
|
+
constraintName: string;
|
|
67
|
+
column: string;
|
|
68
|
+
targetTable: string;
|
|
69
|
+
targetColumn: string;
|
|
70
|
+
onDelete: ReferentialAction;
|
|
71
|
+
onUpdate: ReferentialAction;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Build the `ADD CONSTRAINT ... FOREIGN KEY` statement for a FK with the given
|
|
75
|
+
* referential actions. Default (`no action`) clauses are omitted, matching how
|
|
76
|
+
* Postgres normalizes them, so re-diffing is stable.
|
|
77
|
+
*/
|
|
78
|
+
export declare function buildAddForeignKeyStatement(table: string, constraintName: string, column: string, targetTable: string, targetColumn: string, onDelete: ReferentialAction, onUpdate: ReferentialAction, dialect?: Dialect): string;
|
|
79
|
+
/**
|
|
80
|
+
* Decide whether a FK's referential actions changed. When they differ, returns
|
|
81
|
+
* the DROP + ADD CONSTRAINT statements (and their reverse) — Postgres has no
|
|
82
|
+
* `ALTER CONSTRAINT` for referential actions, so drop-and-recreate is the only
|
|
83
|
+
* path. Returns null when the actions already match.
|
|
84
|
+
*/
|
|
85
|
+
export declare function diffReferentialAction(table: string, db: DbForeignKey, schemaOnDelete: ReferentialAction, schemaOnUpdate: ReferentialAction, dialect?: Dialect): {
|
|
86
|
+
statements: string[];
|
|
87
|
+
reverseStatements: string[];
|
|
88
|
+
} | null;
|
|
89
|
+
/**
|
|
90
|
+
* Compute append-only enum value changes. Returns `ALTER TYPE ... ADD VALUE`
|
|
91
|
+
* statements for labels present in the schema but not the DB (in order), plus a
|
|
92
|
+
* destructive warning for any DB label the schema dropped or any reorder —
|
|
93
|
+
* Postgres cannot remove or reorder enum values without recreating the type.
|
|
94
|
+
*/
|
|
95
|
+
export declare function diffEnumValues(enumName: string, schemaLabels: readonly string[], dbLabels: readonly string[], dialect?: Dialect): {
|
|
96
|
+
statements: string[];
|
|
97
|
+
warnings: string[];
|
|
98
|
+
};
|
|
99
|
+
/** A check constraint as declared or read from the DB. */
|
|
100
|
+
export interface CheckSpec {
|
|
101
|
+
name: string;
|
|
102
|
+
expression: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Diff a table's CHECK constraints (matched by name). Adds constraints missing
|
|
106
|
+
* from the DB, drops DB constraints absent from the schema, and drop+adds when a
|
|
107
|
+
* same-named constraint's expression changed. Expression comparison is a naive
|
|
108
|
+
* whitespace-insensitive match — semantically-equal-but-different-spelled
|
|
109
|
+
* expressions may re-emit (documented; harmless drop+add).
|
|
110
|
+
*/
|
|
111
|
+
export declare function diffCheckConstraints(table: string, schemaChecks: readonly CheckSpec[], dbChecks: readonly CheckSpec[], dialect?: Dialect): {
|
|
112
|
+
statements: string[];
|
|
113
|
+
reverseStatements: string[];
|
|
114
|
+
};
|
|
48
115
|
/**
|
|
49
116
|
* Compare a SchemaDef against a live Postgres database and return the diff.
|
|
50
117
|
*
|