turbine-orm 0.19.1 → 0.21.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 +156 -24
- package/dist/adapters/cockroachdb.js +4 -9
- package/dist/cjs/adapters/cockroachdb.js +4 -9
- package/dist/cjs/cli/index.js +23 -12
- package/dist/cjs/cli/observe-ui.js +12 -2
- package/dist/cjs/cli/observe.js +6 -7
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/studio.js +6 -37
- package/dist/cjs/client.js +45 -33
- package/dist/cjs/dialect.js +116 -0
- package/dist/cjs/errors.js +19 -1
- package/dist/cjs/generate.js +4 -0
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/introspect.js +20 -0
- package/dist/cjs/mssql.js +1338 -0
- package/dist/cjs/mysql.js +1052 -0
- package/dist/cjs/query/builder.js +408 -122
- package/dist/cjs/query/utils.js +1 -0
- package/dist/cjs/sqlite.js +849 -0
- package/dist/cjs/typed-sql.js +9 -7
- package/dist/cli/index.js +23 -12
- package/dist/cli/migrate.d.ts +2 -2
- package/dist/cli/observe-ui.d.ts +1 -1
- package/dist/cli/observe-ui.js +12 -2
- package/dist/cli/observe.js +7 -8
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/studio.d.ts +0 -10
- package/dist/cli/studio.js +7 -37
- package/dist/client.d.ts +35 -14
- package/dist/client.js +46 -34
- package/dist/dialect.d.ts +258 -1
- package/dist/dialect.js +83 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +17 -0
- package/dist/generate.js +4 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/introspect.d.ts +18 -0
- package/dist/introspect.js +19 -0
- package/dist/mssql.d.ts +233 -0
- package/dist/mssql.js +1298 -0
- package/dist/mysql.d.ts +174 -0
- package/dist/mysql.js +1012 -0
- package/dist/query/builder.d.ts +105 -6
- package/dist/query/builder.js +409 -123
- package/dist/query/index.d.ts +2 -2
- package/dist/query/types.d.ts +62 -12
- package/dist/query/utils.js +1 -0
- package/dist/sqlite.d.ts +144 -0
- package/dist/sqlite.js +842 -0
- package/dist/typed-sql.d.ts +7 -5
- package/dist/typed-sql.js +9 -7
- package/package.json +32 -3
package/dist/dialect.d.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* PostgreSQL-native by default, but query generation now depends on this
|
|
6
6
|
* contract for the SQL primitives that vary across MySQL and SQLite.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import type { WithOptions } from './query/types.js';
|
|
9
|
+
import { type RelationDef, type SchemaMetadata, type TableMetadata } from './schema.js';
|
|
9
10
|
export type DialectName = 'postgresql' | 'mysql' | 'sqlite' | (string & {});
|
|
10
11
|
export interface InsertStatementInput {
|
|
11
12
|
/** SQL-ready quoted table name. */
|
|
@@ -86,9 +87,136 @@ export interface CreateIndexStatementInput {
|
|
|
86
87
|
/** SQL-ready quoted index columns. */
|
|
87
88
|
columns: string[];
|
|
88
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* How a dialect surfaces the row(s) produced by an INSERT/UPDATE/DELETE/upsert.
|
|
92
|
+
*
|
|
93
|
+
* - `'returning'` — a trailing `RETURNING *` clause returns the affected rows
|
|
94
|
+
* in the same statement (PostgreSQL, SQLite ≥ 3.35). The executor reads them
|
|
95
|
+
* directly from the statement result.
|
|
96
|
+
* - `'output'` — the statement itself emits the rows in a non-RETURNING shape
|
|
97
|
+
* (SQL Server `OUTPUT INSERTED.*`). Executed exactly like `'returning'`: the
|
|
98
|
+
* rows come back on the statement result.
|
|
99
|
+
* - `'reselect'` — the engine cannot return rows from a write (MySQL). The
|
|
100
|
+
* executor runs the write, then issues a follow-up `SELECT` (by primary
|
|
101
|
+
* key / unique / where predicate) to fetch the affected row(s). The build
|
|
102
|
+
* method supplies the {@link DeferredQuery} `reselect` plan that owns the
|
|
103
|
+
* statement ordering (write-then-select for create/update/upsert;
|
|
104
|
+
* select-then-write for delete, whose row is gone after the statement runs).
|
|
105
|
+
*/
|
|
106
|
+
export type ResultStrategy = 'returning' | 'output' | 'reselect';
|
|
107
|
+
/**
|
|
108
|
+
* Minimal connection surface needed to drive a server-side stream (cursor /
|
|
109
|
+
* driver iterator). `pg.PoolClient` and `PgCompatPoolClient` both satisfy it.
|
|
110
|
+
* Declared locally so {@link Dialect.openStream} stays free of an import cycle
|
|
111
|
+
* back into `client.ts`.
|
|
112
|
+
*/
|
|
113
|
+
export interface StreamableConnection {
|
|
114
|
+
query(text: string, values?: unknown[]): Promise<{
|
|
115
|
+
rows: Record<string, unknown>[];
|
|
116
|
+
}>;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Inputs for {@link Dialect.buildUpdateStatement} — full UPDATE assembly. Used by
|
|
120
|
+
* engines whose returning shape is injected MID-statement rather than as a trailing
|
|
121
|
+
* clause (SQL Server `OUTPUT INSERTED.*` lands between `SET …` and `WHERE …`).
|
|
122
|
+
*/
|
|
123
|
+
export interface UpdateStatementInput {
|
|
124
|
+
/** SQL-ready quoted table name. */
|
|
125
|
+
table: string;
|
|
126
|
+
/** SQL-ready `col = expr` assignments. */
|
|
127
|
+
setClauses: string[];
|
|
128
|
+
/** SQL-ready WHERE fragment INCLUDING the leading ` WHERE ` (or '' for none). */
|
|
129
|
+
whereSql: string;
|
|
130
|
+
/** SQL-ready returning selection (default `*`). */
|
|
131
|
+
returning?: string;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Inputs for {@link Dialect.buildDeleteStatement} — full DELETE assembly. SQL Server
|
|
135
|
+
* injects `OUTPUT DELETED.*` between `DELETE FROM <t>` and `WHERE …`.
|
|
136
|
+
*/
|
|
137
|
+
export interface DeleteStatementInput {
|
|
138
|
+
/** SQL-ready quoted table name. */
|
|
139
|
+
table: string;
|
|
140
|
+
/** SQL-ready WHERE fragment INCLUDING the leading ` WHERE ` (or '' for none). */
|
|
141
|
+
whereSql: string;
|
|
142
|
+
/** SQL-ready returning selection (default `*`). */
|
|
143
|
+
returning?: string;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Inputs for {@link Dialect.buildLimitOffset} — the trailing pagination clause of an
|
|
147
|
+
* outer SELECT. PostgreSQL/MySQL/SQLite use `LIMIT x [OFFSET y]`; SQL Server has no
|
|
148
|
+
* `LIMIT` and uses `[ORDER BY …] OFFSET y ROWS [FETCH NEXT x ROWS ONLY]` (which
|
|
149
|
+
* requires an ORDER BY — a stable default is injected when {@link hasOrderBy} is false).
|
|
150
|
+
*/
|
|
151
|
+
export interface LimitOffsetInput {
|
|
152
|
+
/** SQL-ready placeholder/literal for the row LIMIT, or undefined for no limit. */
|
|
153
|
+
limitPlaceholder?: string;
|
|
154
|
+
/** SQL-ready placeholder/literal for the OFFSET, or undefined for no offset. */
|
|
155
|
+
offsetPlaceholder?: string;
|
|
156
|
+
/** Whether the outer SELECT already carries an ORDER BY (so none must be injected). */
|
|
157
|
+
hasOrderBy: boolean;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Everything an engine needs to OVERRIDE nested-relation subquery generation, for
|
|
161
|
+
* dialects whose JSON-aggregation shape is fundamentally different from PostgreSQL's
|
|
162
|
+
* `json_agg(json_build_object(...))` (SQL Server's `FOR JSON PATH` expresses the
|
|
163
|
+
* object shape through the child SELECT's column ALIASES rather than an explicit
|
|
164
|
+
* `JSON_OBJECT`, so it cannot be assembled from {@link Dialect.buildJsonObject} /
|
|
165
|
+
* {@link Dialect.buildJsonArrayAgg} primitives — see {@link Dialect.buildRelationSubquery}).
|
|
166
|
+
*
|
|
167
|
+
* The query builder pre-resolves the parts that are engine-independent (alias,
|
|
168
|
+
* select/omit-resolved columns, recursion + param threading) and hands them to the
|
|
169
|
+
* dialect. **Param-push ordering contract:** the override MUST push values to
|
|
170
|
+
* {@link params} in the same order the builder's collect path expects so the SQL
|
|
171
|
+
* cache and pipeline batching stay in sync —
|
|
172
|
+
* - to-many with `limit`/`orderBy` (the "wrap" path) and manyToMany:
|
|
173
|
+
* `buildWhere(...)` → push `limit` → `recurse(...)` for each nested relation;
|
|
174
|
+
* - everything else (to-one, unordered/unlimited to-many): `recurse(...)` for each
|
|
175
|
+
* nested relation → `buildWhere(...)` (no limit param).
|
|
176
|
+
*/
|
|
177
|
+
export interface RelationSubqueryContext {
|
|
178
|
+
/** The relation being expanded (its `type`, `to`, `foreignKey`/`referenceKey`, `through`). */
|
|
179
|
+
relDef: RelationDef;
|
|
180
|
+
/** The `with` spec for this relation: `true`, or a {@link WithOptions} object. */
|
|
181
|
+
spec: true | WithOptions;
|
|
182
|
+
/** Shared parameter array — push BOUND values here in the order described above. */
|
|
183
|
+
params: unknown[];
|
|
184
|
+
/** Parent alias or table name (RAW identifier, not quoted) to correlate against. */
|
|
185
|
+
parentRef: string;
|
|
186
|
+
/** Pre-allocated unique alias for this relation's target rows (e.g. `t0`). */
|
|
187
|
+
alias: string;
|
|
188
|
+
/** Target table name (snake_case). */
|
|
189
|
+
targetTable: string;
|
|
190
|
+
/** Target table metadata. */
|
|
191
|
+
targetMeta: TableMetadata;
|
|
192
|
+
/** Resolved target columns (snake_case) honoring `select` / `omit`. */
|
|
193
|
+
targetColumns: string[];
|
|
194
|
+
/** Current recursion depth (for nested {@link recurse} calls, pass `depth + 1`). */
|
|
195
|
+
depth: number;
|
|
196
|
+
/** Breadcrumb path of relation/table names for circular-relation errors. */
|
|
197
|
+
path: string[];
|
|
198
|
+
/** Quote a RAW identifier through the active dialect. */
|
|
199
|
+
quote(name: string): string;
|
|
200
|
+
/**
|
|
201
|
+
* Build a WHERE fragment for `spec.where` against `whereAlias`, PUSHING its params
|
|
202
|
+
* to {@link params}. Returns '' when the spec has no `where`. Call exactly once.
|
|
203
|
+
*/
|
|
204
|
+
buildWhere(whereAlias: string): string;
|
|
205
|
+
/**
|
|
206
|
+
* Recurse to build a nested relation's subquery (PUSHING its params to
|
|
207
|
+
* {@link params}). Uses the shared alias counter, so nested aliases never collide.
|
|
208
|
+
*/
|
|
209
|
+
recurse(relDef: RelationDef, spec: true | WithOptions, parentRef: string, depth: number, path: string[]): string;
|
|
210
|
+
}
|
|
89
211
|
export interface Dialect {
|
|
90
212
|
/** Dialect identifier. */
|
|
91
213
|
readonly name: DialectName;
|
|
214
|
+
/**
|
|
215
|
+
* How write statements surface their affected rows. PostgreSQL uses
|
|
216
|
+
* `'returning'`; the executor branches on this so non-RETURNING engines
|
|
217
|
+
* (MySQL `'reselect'`, SQL Server `'output'`) can still return rows.
|
|
218
|
+
*/
|
|
219
|
+
readonly resultStrategy: ResultStrategy;
|
|
92
220
|
/** Parameter placeholder for the Nth value, using a 1-indexed public count. */
|
|
93
221
|
paramPlaceholder(index: number): string;
|
|
94
222
|
/** Quote a SQL identifier (table, column, cursor, alias). */
|
|
@@ -103,8 +231,42 @@ export interface Dialect {
|
|
|
103
231
|
buildJsonObject(pairs: [key: string, expr: string][]): string;
|
|
104
232
|
/** Build a JSON array aggregation expression with a dialect-specific empty-array fallback. */
|
|
105
233
|
buildJsonArrayAgg(jsonObjectExpr: string, orderBy?: string): string;
|
|
234
|
+
/**
|
|
235
|
+
* Whether the array-aggregate (`buildJsonArrayAgg`) can take an inline
|
|
236
|
+
* `ORDER BY` argument. PostgreSQL's `json_agg(... ORDER BY ...)` can, so this
|
|
237
|
+
* is `true`. Engines whose array aggregate has no ORDER BY argument
|
|
238
|
+
* (MySQL `JSON_ARRAYAGG`, SQLite `json_group_array`) set this `false` to force
|
|
239
|
+
* the inner-subquery rewrite for every ordered to-many relation.
|
|
240
|
+
*/
|
|
241
|
+
readonly aggSupportsInlineOrderBy: boolean;
|
|
242
|
+
/**
|
|
243
|
+
* Render `LIMIT` / `OFFSET` as inline integer literals instead of bound
|
|
244
|
+
* parameters. MySQL sets this `true`: mysql2's binary (prepared) protocol sends
|
|
245
|
+
* JS numbers as `DOUBLE`, which MySQL's `LIMIT`/`OFFSET` reject ("Incorrect
|
|
246
|
+
* arguments to mysqld_stmt_execute"). The values are Turbine-validated
|
|
247
|
+
* non-negative integers (never user strings), so inlining is injection-safe.
|
|
248
|
+
* PostgreSQL / SQLite / SQL Server leave this falsy and keep pagination
|
|
249
|
+
* parameterized, so their generated SQL stays byte-identical.
|
|
250
|
+
*/
|
|
251
|
+
readonly inlineLimitOffset?: boolean;
|
|
252
|
+
/**
|
|
253
|
+
* Wrap a correlated nested-relation subquery (and its empty/null fallback) for
|
|
254
|
+
* embedding as a JSON value in a parent `json_build_object`. PostgreSQL emits
|
|
255
|
+
* `COALESCE((subquery), fallback)`. SQLite must additionally `json(...)`-wrap
|
|
256
|
+
* the subquery (its `json_group_array` double-encodes nested objects);
|
|
257
|
+
* SQL Server uses `ISNULL((... FOR JSON PATH), '[]')`.
|
|
258
|
+
*/
|
|
259
|
+
wrapJsonSubresult(subquery: string, fallback: string): string;
|
|
106
260
|
/** Whether INSERT/UPDATE/DELETE support RETURNING rows. */
|
|
107
261
|
readonly supportsReturning: boolean;
|
|
262
|
+
/** Whether this dialect/engine supports pgvector distance ops (KNN / distance WHERE). */
|
|
263
|
+
readonly supportsVector: boolean;
|
|
264
|
+
/** Whether this dialect/engine supports LISTEN/NOTIFY realtime pub/sub. */
|
|
265
|
+
readonly supportsListenNotify: boolean;
|
|
266
|
+
/** Whether this dialect/engine supports row-level-security session GUCs (set_config). */
|
|
267
|
+
readonly supportsRLS: boolean;
|
|
268
|
+
/** Whether this dialect/engine supports advisory-lock-style migration locking. */
|
|
269
|
+
readonly supportsAdvisoryLock: boolean;
|
|
108
270
|
/** Build a dialect-specific RETURNING clause. Return an empty string when unsupported. */
|
|
109
271
|
buildReturningClause(selection?: string): string;
|
|
110
272
|
/** Build a single-row INSERT statement. Inputs are SQL-ready quoted fragments. */
|
|
@@ -127,6 +289,32 @@ export interface Dialect {
|
|
|
127
289
|
buildCorrelation(leftRef: string, leftColumns: string | string[], rightRef: string, rightColumns: string | string[]): string;
|
|
128
290
|
/** Optional type mapping hook for code generation/introspection. */
|
|
129
291
|
typeToTypeScript?(dialectType: string, nullable: boolean): string;
|
|
292
|
+
/**
|
|
293
|
+
* Cast an aggregate result expression to an integer or float SQL type.
|
|
294
|
+
* PostgreSQL uses the postfix casts `expr::int` / `expr::float`; portable
|
|
295
|
+
* engines (e.g. SQLite, which has no `::` cast operator) emit
|
|
296
|
+
* `CAST(expr AS INTEGER/REAL)`. Optional: when a dialect omits it, the query
|
|
297
|
+
* builder falls back to the PostgreSQL postfix cast, so dialects that predate
|
|
298
|
+
* this hook keep emitting byte-identical SQL.
|
|
299
|
+
*/
|
|
300
|
+
castAggregate?(expr: string, target: 'int' | 'float'): string;
|
|
301
|
+
/**
|
|
302
|
+
* Build a membership (`IN` / `NOT IN`) predicate from a column/expression and
|
|
303
|
+
* a single bound parameter reference. PostgreSQL binds the whole list as one
|
|
304
|
+
* array param (`expr = ANY($n)` / `expr != ALL($n)`), so the placeholder count
|
|
305
|
+
* stays independent of the list length and the SQL cache remains valid.
|
|
306
|
+
* Engines without array parameters (SQLite) override this together with
|
|
307
|
+
* {@link inClauseParam} to use a length-independent single-placeholder form
|
|
308
|
+
* (e.g. `expr IN (SELECT value FROM json_each(?))`). Optional: the query
|
|
309
|
+
* builder falls back to the PostgreSQL `ANY`/`ALL` form when absent.
|
|
310
|
+
*/
|
|
311
|
+
buildInClause?(expr: string, paramRef: string, negated: boolean): string;
|
|
312
|
+
/**
|
|
313
|
+
* The single bound value for an `IN` list (paired with {@link buildInClause}).
|
|
314
|
+
* PostgreSQL passes the array through unchanged; SQLite serializes it to a
|
|
315
|
+
* JSON string consumed by `json_each`. Optional: defaults to the array itself.
|
|
316
|
+
*/
|
|
317
|
+
inClauseParam?(values: unknown[]): unknown;
|
|
130
318
|
/** Optional array-cast hook for bulk insert implementations. */
|
|
131
319
|
arrayType?(baseType: string): string;
|
|
132
320
|
/** Map a schema-builder column type to dialect DDL. */
|
|
@@ -149,6 +337,68 @@ export interface Dialect {
|
|
|
149
337
|
buildMigrationInsertApplied(table: string): string;
|
|
150
338
|
/** Build the query that deletes a rolled-back migration record. */
|
|
151
339
|
buildMigrationDeleteApplied(table: string): string;
|
|
340
|
+
/** `BEGIN`, optionally with a SQL-ready isolation-level suffix. */
|
|
341
|
+
beginStatement(isolationLevel?: string): string;
|
|
342
|
+
/** Commit the current transaction. */
|
|
343
|
+
commitStatement(): string;
|
|
344
|
+
/** Roll back the current transaction. */
|
|
345
|
+
rollbackStatement(): string;
|
|
346
|
+
/** Establish a savepoint with the given (SQL-ready) name. */
|
|
347
|
+
savepointStatement(name: string): string;
|
|
348
|
+
/** Release the named savepoint. */
|
|
349
|
+
releaseSavepointStatement(name: string): string;
|
|
350
|
+
/** Roll back to the named savepoint. */
|
|
351
|
+
rollbackToSavepointStatement(name: string): string;
|
|
352
|
+
/**
|
|
353
|
+
* Build a transaction-local session-config (GUC) assignment for RLS /
|
|
354
|
+
* multi-tenant context. Both name and value are bound parameters.
|
|
355
|
+
*/
|
|
356
|
+
buildSetSessionConfig(name: string, value: string): BuiltStatement;
|
|
357
|
+
/**
|
|
358
|
+
* Drive a server-side stream of result rows on a single connection, yielding
|
|
359
|
+
* row batches of up to `batchSize`. PostgreSQL uses a `DECLARE CURSOR` /
|
|
360
|
+
* `FETCH` / `CLOSE` loop inside a transaction; other engines use their
|
|
361
|
+
* driver's native streaming iterator.
|
|
362
|
+
*/
|
|
363
|
+
openStream(connection: StreamableConnection, sql: string, params: unknown[], batchSize: number): AsyncGenerator<Record<string, unknown>[], void, undefined>;
|
|
364
|
+
/**
|
|
365
|
+
* Schema introspector for this engine. `introspect()` routes through it so
|
|
366
|
+
* engines can override the catalog SQL. PostgreSQL wraps the
|
|
367
|
+
* information_schema / pg_catalog reader in `introspect.ts`.
|
|
368
|
+
*/
|
|
369
|
+
readonly introspector?: DialectIntrospector;
|
|
370
|
+
/**
|
|
371
|
+
* Build the trailing pagination clause for an OUTER SELECT. Optional: when a
|
|
372
|
+
* dialect omits it, the query builder emits the PostgreSQL form
|
|
373
|
+
* (` LIMIT <ph>` and/or ` OFFSET <ph>`), so PG/MySQL/SQLite stay byte-identical.
|
|
374
|
+
* SQL Server has no `LIMIT`, so it implements this to emit
|
|
375
|
+
* `[ORDER BY (SELECT NULL)] OFFSET <off> ROWS [FETCH NEXT <lim> ROWS ONLY]`.
|
|
376
|
+
*/
|
|
377
|
+
buildLimitOffset?(input: LimitOffsetInput): string;
|
|
378
|
+
/**
|
|
379
|
+
* Assemble a full UPDATE statement. Optional: omitted by engines whose returning
|
|
380
|
+
* shape is a trailing clause (PG/SQLite `RETURNING`, MySQL none) — the builder
|
|
381
|
+
* falls back to `UPDATE <t> SET <set> <where><returningClause>`. SQL Server
|
|
382
|
+
* implements this to inject `OUTPUT INSERTED.*` between `SET …` and `WHERE …`.
|
|
383
|
+
*/
|
|
384
|
+
buildUpdateStatement?(input: UpdateStatementInput): string;
|
|
385
|
+
/**
|
|
386
|
+
* Assemble a full DELETE statement. Optional: see {@link buildUpdateStatement}.
|
|
387
|
+
* SQL Server injects `OUTPUT DELETED.*` between `DELETE FROM <t>` and `WHERE …`.
|
|
388
|
+
*/
|
|
389
|
+
buildDeleteStatement?(input: DeleteStatementInput): string;
|
|
390
|
+
/**
|
|
391
|
+
* OVERRIDE nested-relation subquery generation entirely. Optional: when a dialect
|
|
392
|
+
* omits it, the builder uses its native `json_agg(json_build_object(...))` path
|
|
393
|
+
* (PG) routed through {@link buildJsonObject} / {@link buildJsonArrayAgg} /
|
|
394
|
+
* {@link wrapJsonSubresult} — so MySQL and SQLite, which only swap those
|
|
395
|
+
* primitives, produce identical output and never define this hook. SQL Server
|
|
396
|
+
* defines it to emit `FOR JSON PATH` correlated subqueries (its JSON aggregate
|
|
397
|
+
* is expressed through the child SELECT's column aliases, which does not map onto
|
|
398
|
+
* the primitive hooks). See {@link RelationSubqueryContext} for the
|
|
399
|
+
* param-push-ordering contract the override must honor.
|
|
400
|
+
*/
|
|
401
|
+
buildRelationSubquery?(ctx: RelationSubqueryContext): string;
|
|
152
402
|
}
|
|
153
403
|
export interface DialectIntrospector {
|
|
154
404
|
introspect(options: IntrospectOptions): Promise<SchemaMetadata>;
|
|
@@ -159,6 +409,13 @@ export interface IntrospectOptions {
|
|
|
159
409
|
include?: string[];
|
|
160
410
|
exclude?: string[];
|
|
161
411
|
}
|
|
412
|
+
/**
|
|
413
|
+
* @deprecated Migration locking is owned by the {@link DatabaseAdapter} seam
|
|
414
|
+
* (`src/adapters/index.ts` — `acquireLock`/`releaseLock`/`statementTimeout`),
|
|
415
|
+
* which is the single canonical locking seam. This interface was never
|
|
416
|
+
* implemented or wired and is retained only for type back-compat; new engines
|
|
417
|
+
* MUST provide a `DatabaseAdapter` instead. Will be removed in a future major.
|
|
418
|
+
*/
|
|
162
419
|
export interface DialectMigrator {
|
|
163
420
|
acquireLock(lockId: number): Promise<boolean>;
|
|
164
421
|
releaseLock(lockId: number): Promise<void>;
|
package/dist/dialect.js
CHANGED
|
@@ -10,11 +10,17 @@ import { pgArrayType, pgTypeToTs } from './schema.js';
|
|
|
10
10
|
/** PostgreSQL implementation of the dialect contract. */
|
|
11
11
|
export const postgresDialect = {
|
|
12
12
|
name: 'postgresql',
|
|
13
|
+
resultStrategy: 'returning',
|
|
13
14
|
supportsReturning: true,
|
|
14
15
|
supportsILike: true,
|
|
15
16
|
jsonPathSupport: 'native',
|
|
16
17
|
emptyJsonArrayLiteral: "'[]'::json",
|
|
17
18
|
nullJsonLiteral: 'NULL',
|
|
19
|
+
aggSupportsInlineOrderBy: true,
|
|
20
|
+
supportsVector: true,
|
|
21
|
+
supportsListenNotify: true,
|
|
22
|
+
supportsRLS: true,
|
|
23
|
+
supportsAdvisoryLock: true,
|
|
18
24
|
paramPlaceholder(index) {
|
|
19
25
|
return `$${index}`;
|
|
20
26
|
},
|
|
@@ -32,6 +38,9 @@ export const postgresDialect = {
|
|
|
32
38
|
const suffix = orderBy ? ` ${orderBy}` : '';
|
|
33
39
|
return `COALESCE(json_agg(${jsonObjectExpr}${suffix}), ${this.emptyJsonArrayLiteral})`;
|
|
34
40
|
},
|
|
41
|
+
wrapJsonSubresult(subquery, fallback) {
|
|
42
|
+
return `COALESCE((${subquery}), ${fallback})`;
|
|
43
|
+
},
|
|
35
44
|
buildReturningClause(selection = '*') {
|
|
36
45
|
return ` RETURNING ${selection}`;
|
|
37
46
|
},
|
|
@@ -73,6 +82,15 @@ export const postgresDialect = {
|
|
|
73
82
|
typeToTypeScript(dialectType, nullable) {
|
|
74
83
|
return pgTypeToTs(dialectType, nullable);
|
|
75
84
|
},
|
|
85
|
+
castAggregate(expr, target) {
|
|
86
|
+
return `${expr}::${target}`;
|
|
87
|
+
},
|
|
88
|
+
buildInClause(expr, paramRef, negated) {
|
|
89
|
+
return negated ? `${expr} != ALL(${paramRef})` : `${expr} = ANY(${paramRef})`;
|
|
90
|
+
},
|
|
91
|
+
inClauseParam(values) {
|
|
92
|
+
return values;
|
|
93
|
+
},
|
|
76
94
|
arrayType(baseType) {
|
|
77
95
|
return pgArrayType(baseType);
|
|
78
96
|
},
|
|
@@ -128,5 +146,70 @@ export const postgresDialect = {
|
|
|
128
146
|
buildMigrationDeleteApplied(table) {
|
|
129
147
|
return `DELETE FROM ${table} WHERE name = ${this.paramPlaceholder(1)}`;
|
|
130
148
|
},
|
|
149
|
+
beginStatement(isolationLevel) {
|
|
150
|
+
return isolationLevel ? `BEGIN ISOLATION LEVEL ${isolationLevel}` : 'BEGIN';
|
|
151
|
+
},
|
|
152
|
+
commitStatement() {
|
|
153
|
+
return 'COMMIT';
|
|
154
|
+
},
|
|
155
|
+
rollbackStatement() {
|
|
156
|
+
return 'ROLLBACK';
|
|
157
|
+
},
|
|
158
|
+
savepointStatement(name) {
|
|
159
|
+
return `SAVEPOINT ${name}`;
|
|
160
|
+
},
|
|
161
|
+
releaseSavepointStatement(name) {
|
|
162
|
+
return `RELEASE SAVEPOINT ${name}`;
|
|
163
|
+
},
|
|
164
|
+
rollbackToSavepointStatement(name) {
|
|
165
|
+
return `ROLLBACK TO SAVEPOINT ${name}`;
|
|
166
|
+
},
|
|
167
|
+
buildSetSessionConfig(name, value) {
|
|
168
|
+
// set_config(name, value, is_local=true) — the parameterizable,
|
|
169
|
+
// transaction-local equivalent of `SET LOCAL` (which rejects bind params).
|
|
170
|
+
return {
|
|
171
|
+
sql: `SELECT set_config(${this.paramPlaceholder(1)}, ${this.paramPlaceholder(2)}, true)`,
|
|
172
|
+
params: [name, value],
|
|
173
|
+
};
|
|
174
|
+
},
|
|
175
|
+
async *openStream(connection, sql, params, batchSize) {
|
|
176
|
+
// Cursors require a single connection inside a transaction. Identical SQL
|
|
177
|
+
// sequence to the historical inline implementation: BEGIN → DECLARE … NO
|
|
178
|
+
// SCROLL CURSOR FOR → FETCH n (loop) → CLOSE → COMMIT; ROLLBACK on error.
|
|
179
|
+
const cursorName = `turbine_cursor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
180
|
+
const quotedCursor = this.quoteIdentifier(cursorName);
|
|
181
|
+
await connection.query(this.beginStatement());
|
|
182
|
+
try {
|
|
183
|
+
await connection.query(`DECLARE ${quotedCursor} NO SCROLL CURSOR FOR ${sql}`, params);
|
|
184
|
+
while (true) {
|
|
185
|
+
const batch = await connection.query(`FETCH ${batchSize} FROM ${quotedCursor}`);
|
|
186
|
+
if (batch.rows.length === 0)
|
|
187
|
+
break;
|
|
188
|
+
yield batch.rows;
|
|
189
|
+
if (batch.rows.length < batchSize)
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
await connection.query(`CLOSE ${quotedCursor}`);
|
|
193
|
+
await connection.query(this.commitStatement());
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
try {
|
|
197
|
+
await connection.query(this.rollbackStatement());
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// Connection may already be broken — ignore rollback error.
|
|
201
|
+
}
|
|
202
|
+
throw err;
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
// Postgres introspection wraps the information_schema / pg_catalog reader in
|
|
206
|
+
// introspect.ts. A dynamic import keeps the static graph acyclic (introspect.ts
|
|
207
|
+
// imports postgresDialect) and calls the raw catalog reader (never the router).
|
|
208
|
+
introspector: {
|
|
209
|
+
async introspect(options) {
|
|
210
|
+
const { introspectPostgresCatalog } = await import('./introspect.js');
|
|
211
|
+
return introspectPostgresCatalog(options);
|
|
212
|
+
},
|
|
213
|
+
},
|
|
131
214
|
};
|
|
132
215
|
//# sourceMappingURL=dialect.js.map
|
package/dist/errors.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export declare const TurbineErrorCode: {
|
|
|
22
22
|
readonly PIPELINE: "TURBINE_E014";
|
|
23
23
|
readonly OPTIMISTIC_LOCK: "TURBINE_E015";
|
|
24
24
|
readonly EXCLUSION_VIOLATION: "TURBINE_E016";
|
|
25
|
+
readonly UNSUPPORTED_FEATURE: "TURBINE_E017";
|
|
25
26
|
};
|
|
26
27
|
export type TurbineErrorCode = (typeof TurbineErrorCode)[keyof typeof TurbineErrorCode];
|
|
27
28
|
/** Base error class for all Turbine errors */
|
|
@@ -273,6 +274,17 @@ export declare class OptimisticLockError extends TurbineError {
|
|
|
273
274
|
expectedVersion: unknown;
|
|
274
275
|
});
|
|
275
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Thrown when a Postgres-only feature (pgvector distance ops, LISTEN/NOTIFY
|
|
279
|
+
* realtime, RLS session GUCs, advisory-lock migration locking, ...) is invoked
|
|
280
|
+
* on a dialect/engine whose capability flag reports it unsupported. Surfaces a
|
|
281
|
+
* clear `unsupported on <engine>` message instead of generating broken SQL.
|
|
282
|
+
*/
|
|
283
|
+
export declare class UnsupportedFeatureError extends TurbineError {
|
|
284
|
+
readonly feature: string;
|
|
285
|
+
readonly dialect: string;
|
|
286
|
+
constructor(feature: string, dialect: string, hint?: string);
|
|
287
|
+
}
|
|
276
288
|
/**
|
|
277
289
|
* Translate a pg driver error into a typed Turbine error.
|
|
278
290
|
* If the error doesn't match a known constraint code, returns it unchanged.
|
package/dist/errors.js
CHANGED
|
@@ -22,6 +22,7 @@ export const TurbineErrorCode = {
|
|
|
22
22
|
PIPELINE: 'TURBINE_E014',
|
|
23
23
|
OPTIMISTIC_LOCK: 'TURBINE_E015',
|
|
24
24
|
EXCLUSION_VIOLATION: 'TURBINE_E016',
|
|
25
|
+
UNSUPPORTED_FEATURE: 'TURBINE_E017',
|
|
25
26
|
};
|
|
26
27
|
/** Base error class for all Turbine errors */
|
|
27
28
|
export class TurbineError extends Error {
|
|
@@ -435,6 +436,22 @@ export class OptimisticLockError extends TurbineError {
|
|
|
435
436
|
this.expectedVersion = opts.expectedVersion;
|
|
436
437
|
}
|
|
437
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* Thrown when a Postgres-only feature (pgvector distance ops, LISTEN/NOTIFY
|
|
441
|
+
* realtime, RLS session GUCs, advisory-lock migration locking, ...) is invoked
|
|
442
|
+
* on a dialect/engine whose capability flag reports it unsupported. Surfaces a
|
|
443
|
+
* clear `unsupported on <engine>` message instead of generating broken SQL.
|
|
444
|
+
*/
|
|
445
|
+
export class UnsupportedFeatureError extends TurbineError {
|
|
446
|
+
feature;
|
|
447
|
+
dialect;
|
|
448
|
+
constructor(feature, dialect, hint) {
|
|
449
|
+
super(TurbineErrorCode.UNSUPPORTED_FEATURE, `[turbine] ${feature} is unsupported on "${dialect}".${hint ? ` ${hint}` : ''}`);
|
|
450
|
+
this.name = 'UnsupportedFeatureError';
|
|
451
|
+
this.feature = feature;
|
|
452
|
+
this.dialect = dialect;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
438
455
|
/**
|
|
439
456
|
* Parse column names out of a pg `detail` string like:
|
|
440
457
|
* "Key (email)=(foo@bar) already exists."
|
package/dist/generate.js
CHANGED
|
@@ -359,6 +359,10 @@ function generateMetadata(schema) {
|
|
|
359
359
|
lines.push(' },');
|
|
360
360
|
lines.push('};');
|
|
361
361
|
lines.push('');
|
|
362
|
+
// Back-compat lowercase alias. `SCHEMA` is the canonical export, but docs and
|
|
363
|
+
// users frequently import `schema`; emit both so either name resolves.
|
|
364
|
+
lines.push('export const schema = SCHEMA;');
|
|
365
|
+
lines.push('');
|
|
362
366
|
return lines.join('\n');
|
|
363
367
|
}
|
|
364
368
|
// ---------------------------------------------------------------------------
|
package/dist/index.d.ts
CHANGED
|
@@ -34,16 +34,16 @@
|
|
|
34
34
|
*/
|
|
35
35
|
export type { DatabaseAdapter, IntrospectionOverrides } from './adapters/index.js';
|
|
36
36
|
export { alloydb, cockroachdb, postgresql, timescale, yugabytedb } from './adapters/index.js';
|
|
37
|
-
export { type Middleware, type MiddlewareNext, type MiddlewareParams, type PgCompatPool, type PgCompatPoolClient, type PgCompatQueryResult, type RetryOptions, TransactionClient, type TransactionOptions, TurbineClient, type TurbineConfig, withRetry, } from './client.js';
|
|
38
|
-
export type { BuiltStatement, BulkInsertStatementInput, ColumnDefinitionInput, ColumnTypeInput, CreateIndexStatementInput, CreateTableStatementInput, Dialect, DialectIntrospector, DialectMigrator, DialectName, InsertStatementInput, IntrospectOptions as DialectIntrospectOptions, UpsertStatementInput, } from './dialect.js';
|
|
37
|
+
export { type Middleware, type MiddlewareNext, type MiddlewareParams, type PgCompatPool, type PgCompatPoolClient, type PgCompatQueryResult, type RetryOptions, TransactionClient, type TransactionOptions, TurbineClient, type TurbineConfig, type TurbineDriver, withRetry, } from './client.js';
|
|
38
|
+
export type { BuiltStatement, BulkInsertStatementInput, ColumnDefinitionInput, ColumnTypeInput, CreateIndexStatementInput, CreateTableStatementInput, Dialect, DialectIntrospector, DialectMigrator, DialectName, InsertStatementInput, IntrospectOptions as DialectIntrospectOptions, ResultStrategy, StreamableConnection, UpsertStatementInput, } from './dialect.js';
|
|
39
39
|
export { postgresDialect } from './dialect.js';
|
|
40
|
-
export { CheckConstraintError, CircularRelationError, ConnectionError, DeadlockError, type ErrorMessageMode, ExclusionConstraintError, ForeignKeyError, getErrorMessageMode, MigrationError, NotFoundError, NotNullViolationError, OptimisticLockError, PipelineError, type PipelineResultSlot, RelationError, SerializationFailureError, setErrorMessageMode, TimeoutError, TurbineError, TurbineErrorCode, UniqueConstraintError, ValidationError, wrapPgError, } from './errors.js';
|
|
40
|
+
export { CheckConstraintError, CircularRelationError, ConnectionError, DeadlockError, type ErrorMessageMode, ExclusionConstraintError, ForeignKeyError, getErrorMessageMode, MigrationError, NotFoundError, NotNullViolationError, OptimisticLockError, PipelineError, type PipelineResultSlot, RelationError, SerializationFailureError, setErrorMessageMode, TimeoutError, TurbineError, TurbineErrorCode, UniqueConstraintError, UnsupportedFeatureError, ValidationError, wrapPgError, } from './errors.js';
|
|
41
41
|
export { type GenerateOptions, generate } from './generate.js';
|
|
42
42
|
export { type IntrospectOptions, introspect } from './introspect.js';
|
|
43
43
|
export { executeNestedCreate, executeNestedUpdate, hasRelationFields, type NestedWriteContext, } from './nested-write.js';
|
|
44
44
|
export type { ObserveConfig, ObserveHandle } from './observe.js';
|
|
45
45
|
export { executePipeline, type PipelineOptions, type PipelineResults, pipelineSupported } from './pipeline.js';
|
|
46
|
-
export { type AggregateArgs, type AggregateResult, type ArrayFilter, type ConnectOrCreateOp, type CountArgs, type CreateArgs, type CreateManyArgs, type DeferredQuery, type DeleteArgs, type DeleteManyArgs, type FieldResult, type FindManyArgs, type FindManyStreamArgs, type FindUniqueArgs, type GroupByArgs, type HavingClause, type JsonFilter, type MiddlewareFn, type NestedCreateOp, type NestedUpdateOp, type OmitResult, type OrderByClause, type OrderDirection, type QueryEvent, type QueryEventListener, QueryInterface, type QueryResult, type RelationDescriptor, type RelationFilter, type SelectResult, type TextSearchFilter, type TypedWithClause, type UpdateArgs, type UpdateInput, type UpdateManyArgs, type UpdateOperatorInput, type UpsertArgs, type VectorDistanceFilter, type VectorFilter, type VectorMetric, type VectorOrderBy, type VectorOrderByDistance, type WhereClause, type WhereOperator, type WhereValue, type WithClause, type WithOptions, type WithResult, } from './query/index.js';
|
|
46
|
+
export { type AggregateArgs, type AggregateResult, type ArrayFilter, type ConnectOrCreateOp, type CountArgs, type CreateArgs, type CreateDataInput, type CreateManyArgs, type DeferredQuery, type DeleteArgs, type DeleteManyArgs, type FieldResult, type FindManyArgs, type FindManyStreamArgs, type FindUniqueArgs, type GroupByArgs, type HavingClause, type JsonFilter, type MiddlewareFn, type NestedCreateOp, type NestedUpdateOp, type NestedUpdateOpItem, type NestedUpsertOpItem, type OmitResult, type OrderByClause, type OrderDirection, type QueryEvent, type QueryEventListener, QueryInterface, type QueryResult, type RelationDescriptor, type RelationFilter, type SelectResult, type TextSearchFilter, type TypedWithClause, type UpdateArgs, type UpdateDataInput, type UpdateInput, type UpdateManyArgs, type UpdateOperatorInput, type UpsertArgs, type VectorDistanceFilter, type VectorFilter, type VectorMetric, type VectorOrderBy, type VectorOrderByDistance, type WhereClause, type WhereOperator, type WhereValue, type WithClause, type WithOptions, type WithResult, } from './query/index.js';
|
|
47
47
|
export { type ActiveSubscription, type NotificationHandler, type Subscription, validateChannel } from './realtime.js';
|
|
48
48
|
export type { ColumnMetadata, IndexMetadata, RelationDef, SchemaMetadata, TableMetadata, } from './schema.js';
|
|
49
49
|
export { camelToSnake, isDateType, normalizeKeyColumns, pgArrayType, pgTypeToTs, singularize, snakeToCamel, snakeToPascal, } from './schema.js';
|
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ export { alloydb, cockroachdb, postgresql, timescale, yugabytedb } from './adapt
|
|
|
37
37
|
export { TransactionClient, TurbineClient, withRetry, } from './client.js';
|
|
38
38
|
export { postgresDialect } from './dialect.js';
|
|
39
39
|
// Error types
|
|
40
|
-
export { CheckConstraintError, CircularRelationError, ConnectionError, DeadlockError, ExclusionConstraintError, ForeignKeyError, getErrorMessageMode, MigrationError, NotFoundError, NotNullViolationError, OptimisticLockError, PipelineError, RelationError, SerializationFailureError, setErrorMessageMode, TimeoutError, TurbineError, TurbineErrorCode, UniqueConstraintError, ValidationError, wrapPgError, } from './errors.js';
|
|
40
|
+
export { CheckConstraintError, CircularRelationError, ConnectionError, DeadlockError, ExclusionConstraintError, ForeignKeyError, getErrorMessageMode, MigrationError, NotFoundError, NotNullViolationError, OptimisticLockError, PipelineError, RelationError, SerializationFailureError, setErrorMessageMode, TimeoutError, TurbineError, TurbineErrorCode, UniqueConstraintError, UnsupportedFeatureError, ValidationError, wrapPgError, } from './errors.js';
|
|
41
41
|
// Code generation
|
|
42
42
|
export { generate } from './generate.js';
|
|
43
43
|
// Introspection
|
package/dist/introspect.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* This is the foundation of `npx turbine generate`.
|
|
9
9
|
*/
|
|
10
|
+
import { type Dialect } from './dialect.js';
|
|
10
11
|
import { type SchemaMetadata } from './schema.js';
|
|
11
12
|
export interface IntrospectOptions {
|
|
12
13
|
/** Postgres connection string */
|
|
@@ -17,6 +18,23 @@ export interface IntrospectOptions {
|
|
|
17
18
|
include?: string[];
|
|
18
19
|
/** Tables to exclude (default: none). Applied after include. */
|
|
19
20
|
exclude?: string[];
|
|
21
|
+
/**
|
|
22
|
+
* Dialect whose {@link Dialect.introspector} drives the catalog reads.
|
|
23
|
+
* Defaults to {@link postgresDialect}. Engines plug their own introspector
|
|
24
|
+
* here so `introspect()` works across databases.
|
|
25
|
+
*/
|
|
26
|
+
dialect?: Dialect;
|
|
20
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Introspect a database into {@link SchemaMetadata}, routing through the active
|
|
30
|
+
* dialect's {@link Dialect.introspector} so each engine can override the catalog
|
|
31
|
+
* SQL. PostgreSQL is driven by {@link introspectPostgresCatalog}.
|
|
32
|
+
*/
|
|
21
33
|
export declare function introspect(options: IntrospectOptions): Promise<SchemaMetadata>;
|
|
34
|
+
/**
|
|
35
|
+
* PostgreSQL catalog introspector: reads information_schema + pg_catalog and
|
|
36
|
+
* produces {@link SchemaMetadata}. This is the implementation wrapped by
|
|
37
|
+
* `postgresDialect.introspector`; call {@link introspect} for dialect routing.
|
|
38
|
+
*/
|
|
39
|
+
export declare function introspectPostgresCatalog(options: IntrospectOptions): Promise<SchemaMetadata>;
|
|
22
40
|
//# sourceMappingURL=introspect.d.ts.map
|
package/dist/introspect.js
CHANGED
|
@@ -94,7 +94,26 @@ const SQL_ENUMS = `
|
|
|
94
94
|
// ---------------------------------------------------------------------------
|
|
95
95
|
// Main introspection function
|
|
96
96
|
// ---------------------------------------------------------------------------
|
|
97
|
+
/**
|
|
98
|
+
* Introspect a database into {@link SchemaMetadata}, routing through the active
|
|
99
|
+
* dialect's {@link Dialect.introspector} so each engine can override the catalog
|
|
100
|
+
* SQL. PostgreSQL is driven by {@link introspectPostgresCatalog}.
|
|
101
|
+
*/
|
|
97
102
|
export async function introspect(options) {
|
|
103
|
+
const dialect = options.dialect ?? postgresDialect;
|
|
104
|
+
const introspector = dialect.introspector;
|
|
105
|
+
if (introspector) {
|
|
106
|
+
return introspector.introspect(options);
|
|
107
|
+
}
|
|
108
|
+
// Dialects without an introspector fall back to the Postgres catalog reader.
|
|
109
|
+
return introspectPostgresCatalog(options);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* PostgreSQL catalog introspector: reads information_schema + pg_catalog and
|
|
113
|
+
* produces {@link SchemaMetadata}. This is the implementation wrapped by
|
|
114
|
+
* `postgresDialect.introspector`; call {@link introspect} for dialect routing.
|
|
115
|
+
*/
|
|
116
|
+
export async function introspectPostgresCatalog(options) {
|
|
98
117
|
const schema = options.schema ?? 'public';
|
|
99
118
|
const dialect = postgresDialect;
|
|
100
119
|
const pool = new pg.Pool({
|