turbine-orm 0.81.0 → 0.90.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 +26 -25
- package/dist/checkout.d.ts +27 -2
- package/dist/checkout.js +75 -4
- package/dist/cjs/checkout.d.ts +27 -2
- package/dist/cjs/checkout.js +76 -3
- package/dist/cjs/cli/code-first-tags.d.ts +62 -0
- package/dist/cjs/cli/code-first-tags.js +124 -0
- package/dist/cjs/cli/compile-query.js +9 -0
- package/dist/cjs/cli/config.d.ts +35 -6
- package/dist/cjs/cli/config.js +17 -1
- package/dist/cjs/cli/destructive.d.ts +7 -4
- package/dist/cjs/cli/destructive.js +224 -20
- package/dist/cjs/cli/error-catalog.js +36 -0
- package/dist/cjs/cli/error-text.d.ts +15 -0
- package/dist/cjs/cli/error-text.js +40 -0
- package/dist/cjs/cli/index.d.ts +62 -4
- package/dist/cjs/cli/index.js +568 -108
- package/dist/cjs/cli/local-server-guard.d.ts +53 -0
- package/dist/cjs/cli/local-server-guard.js +119 -0
- package/dist/cjs/cli/local-server-output.d.ts +41 -0
- package/dist/cjs/cli/local-server-output.js +103 -0
- package/dist/cjs/cli/mcp.d.ts +8 -0
- package/dist/cjs/cli/mcp.js +121 -32
- package/dist/cjs/cli/migrate.d.ts +113 -5
- package/dist/cjs/cli/migrate.js +270 -21
- package/dist/cjs/cli/observe.d.ts +6 -1
- package/dist/cjs/cli/observe.js +85 -55
- package/dist/cjs/cli/pii-predicate-guard.d.ts +43 -23
- package/dist/cjs/cli/pii-predicate-guard.js +98 -37
- package/dist/cjs/cli/prisma-resolve.js +17 -13
- package/dist/cjs/cli/prisma-schema.js +12 -4
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/studio.d.ts +14 -2
- package/dist/cjs/cli/studio.js +244 -69
- package/dist/cjs/cli/ui.d.ts +1 -1
- package/dist/cjs/client.d.ts +558 -85
- package/dist/cjs/client.js +1270 -395
- package/dist/cjs/connection-guard.d.ts +13 -0
- package/dist/cjs/connection-guard.js +28 -0
- package/dist/cjs/connection-url.d.ts +51 -16
- package/dist/cjs/connection-url.js +67 -34
- package/dist/cjs/diagnostics.d.ts +81 -0
- package/dist/cjs/diagnostics.js +147 -0
- package/dist/cjs/dialect.d.ts +449 -20
- package/dist/cjs/dialect.js +117 -76
- package/dist/cjs/engine-config.d.ts +7 -6
- package/dist/cjs/errors.d.ts +126 -6
- package/dist/cjs/errors.js +256 -13
- package/dist/cjs/generate.d.ts +57 -7
- package/dist/cjs/generate.js +635 -279
- package/dist/cjs/global-filters-config.d.ts +59 -0
- package/dist/cjs/global-filters-config.js +367 -0
- package/dist/cjs/index.d.ts +9 -4
- package/dist/cjs/index.js +10 -2
- package/dist/cjs/introspect.d.ts +31 -6
- package/dist/cjs/introspect.js +108 -50
- package/dist/cjs/log-codes.d.ts +12 -0
- package/dist/cjs/log-codes.js +77 -0
- package/dist/cjs/logger.d.ts +87 -0
- package/dist/cjs/logger.js +98 -0
- package/dist/cjs/mssql.d.ts +8 -5
- package/dist/cjs/mssql.js +352 -65
- package/dist/cjs/mysql.d.ts +3 -5
- package/dist/cjs/mysql.js +420 -70
- package/dist/cjs/nested-write.d.ts +3 -0
- package/dist/cjs/nested-write.js +179 -114
- package/dist/cjs/pipeline-submittable.d.ts +12 -0
- package/dist/cjs/pipeline-submittable.js +118 -44
- package/dist/cjs/pipeline.js +21 -26
- package/dist/cjs/powdb-introspect.d.ts +5 -2
- package/dist/cjs/powdb-introspect.js +20 -15
- package/dist/cjs/powdb-shared.d.ts +36 -5
- package/dist/cjs/powdb-shared.js +67 -8
- package/dist/cjs/powdb.d.ts +4 -0
- package/dist/cjs/powdb.js +183 -51
- package/dist/cjs/powql.d.ts +394 -51
- package/dist/cjs/powql.js +1829 -284
- package/dist/cjs/prisma-compat.d.ts +23 -15
- package/dist/cjs/prisma-compat.js +507 -372
- package/dist/cjs/query/aggregates.d.ts +17 -1
- package/dist/cjs/query/aggregates.js +261 -29
- package/dist/cjs/query/batched-loader.d.ts +25 -6
- package/dist/cjs/query/batched-loader.js +103 -20
- package/dist/cjs/query/builder.d.ts +325 -184
- package/dist/cjs/query/builder.js +1452 -490
- package/dist/cjs/query/column-ref.d.ts +61 -0
- package/dist/cjs/query/column-ref.js +73 -0
- package/dist/cjs/query/compound-unique.d.ts +21 -0
- package/dist/cjs/query/compound-unique.js +98 -18
- package/dist/cjs/query/deferred.d.ts +53 -16
- package/dist/cjs/query/expr.d.ts +217 -0
- package/dist/cjs/query/expr.js +859 -0
- package/dist/cjs/query/filters.d.ts +155 -53
- package/dist/cjs/query/filters.js +613 -76
- package/dist/cjs/query/index.d.ts +2 -0
- package/dist/cjs/query/index.js +7 -1
- package/dist/cjs/query/json-value.d.ts +43 -0
- package/dist/cjs/query/json-value.js +59 -0
- package/dist/cjs/query/normalize-args.d.ts +87 -0
- package/dist/cjs/query/normalize-args.js +527 -0
- package/dist/cjs/query/option-surface.d.ts +16 -4
- package/dist/cjs/query/option-surface.js +57 -14
- package/dist/cjs/query/relation-names.d.ts +14 -0
- package/dist/cjs/query/relation-names.js +77 -18
- package/dist/cjs/query/relations.d.ts +30 -3
- package/dist/cjs/query/relations.js +219 -63
- package/dist/cjs/query/row-lock.d.ts +49 -0
- package/dist/cjs/query/row-lock.js +138 -0
- package/dist/cjs/query/sql-comment.d.ts +161 -0
- package/dist/cjs/query/sql-comment.js +381 -0
- package/dist/cjs/query/stream-idle.d.ts +132 -0
- package/dist/cjs/query/stream-idle.js +212 -0
- package/dist/cjs/query/table-client.d.ts +101 -0
- package/dist/cjs/query/table-client.js +74 -0
- package/dist/cjs/query/types.d.ts +677 -298
- package/dist/cjs/query/utils.d.ts +105 -6
- package/dist/cjs/query/utils.js +426 -19
- package/dist/cjs/query/warn-registry.d.ts +18 -0
- package/dist/cjs/query/warn-registry.js +18 -0
- package/dist/cjs/query/where-compile.d.ts +138 -14
- package/dist/cjs/query/where-compile.js +513 -29
- package/dist/cjs/query/where.d.ts +159 -59
- package/dist/cjs/query/where.js +716 -246
- package/dist/cjs/query/writes.d.ts +112 -50
- package/dist/cjs/query/writes.js +1498 -200
- package/dist/cjs/query-events.d.ts +4 -2
- package/dist/cjs/query-events.js +3 -1
- package/dist/cjs/realtime.d.ts +7 -3
- package/dist/cjs/realtime.js +13 -2
- package/dist/cjs/schema-builder.d.ts +55 -4
- package/dist/cjs/schema-builder.js +382 -18
- package/dist/cjs/schema-metadata.d.ts +21 -1
- package/dist/cjs/schema-metadata.js +29 -7
- package/dist/cjs/schema-sql.d.ts +75 -5
- package/dist/cjs/schema-sql.js +497 -45
- package/dist/cjs/schema.d.ts +26 -5
- package/dist/cjs/schema.js +110 -13
- package/dist/cjs/sql-placeholders.d.ts +56 -0
- package/dist/cjs/sql-placeholders.js +204 -0
- package/dist/cjs/sqlite.d.ts +75 -20
- package/dist/cjs/sqlite.js +593 -95
- package/dist/cjs/supplied-pool-types.d.ts +48 -0
- package/dist/cjs/supplied-pool-types.js +256 -0
- package/dist/cjs/transaction-control.d.ts +51 -0
- package/dist/cjs/transaction-control.js +316 -0
- package/dist/cjs/transaction-state.d.ts +342 -0
- package/dist/cjs/transaction-state.js +707 -0
- package/dist/cjs/typed-sql.d.ts +37 -3
- package/dist/cjs/typed-sql.js +102 -8
- package/dist/cli/code-first-tags.d.ts +62 -0
- package/dist/cli/code-first-tags.js +120 -0
- package/dist/cli/compile-query.js +9 -0
- package/dist/cli/config.d.ts +35 -6
- package/dist/cli/config.js +16 -1
- package/dist/cli/destructive.d.ts +7 -4
- package/dist/cli/destructive.js +224 -20
- package/dist/cli/error-catalog.js +36 -0
- package/dist/cli/error-text.d.ts +15 -0
- package/dist/cli/error-text.js +37 -0
- package/dist/cli/index.d.ts +62 -4
- package/dist/cli/index.js +569 -112
- package/dist/cli/local-server-guard.d.ts +53 -0
- package/dist/cli/local-server-guard.js +113 -0
- package/dist/cli/local-server-output.d.ts +41 -0
- package/dist/cli/local-server-output.js +98 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +121 -34
- package/dist/cli/migrate.d.ts +113 -5
- package/dist/cli/migrate.js +261 -21
- package/dist/cli/observe.d.ts +6 -1
- package/dist/cli/observe.js +84 -54
- package/dist/cli/pii-predicate-guard.d.ts +43 -23
- package/dist/cli/pii-predicate-guard.js +100 -35
- package/dist/cli/prisma-resolve.js +15 -11
- package/dist/cli/prisma-schema.js +12 -4
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/studio.d.ts +14 -2
- package/dist/cli/studio.js +246 -71
- package/dist/cli/ui.d.ts +1 -1
- package/dist/client.d.ts +558 -85
- package/dist/client.js +1275 -400
- package/dist/connection-guard.d.ts +13 -0
- package/dist/connection-guard.js +27 -0
- package/dist/connection-url.d.ts +51 -16
- package/dist/connection-url.js +63 -34
- package/dist/diagnostics.d.ts +81 -0
- package/dist/diagnostics.js +144 -0
- package/dist/dialect.d.ts +449 -20
- package/dist/dialect.js +115 -43
- package/dist/engine-config.d.ts +7 -6
- package/dist/errors.d.ts +126 -6
- package/dist/errors.js +252 -12
- package/dist/generate.d.ts +57 -7
- package/dist/generate.js +635 -281
- package/dist/global-filters-config.d.ts +59 -0
- package/dist/global-filters-config.js +361 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.js +4 -2
- package/dist/introspect.d.ts +31 -6
- package/dist/introspect.js +106 -50
- package/dist/log-codes.d.ts +12 -0
- package/dist/log-codes.js +74 -0
- package/dist/logger.d.ts +87 -0
- package/dist/logger.js +93 -0
- package/dist/mssql.d.ts +8 -5
- package/dist/mssql.js +354 -67
- package/dist/mysql.d.ts +3 -5
- package/dist/mysql.js +423 -73
- package/dist/nested-write.d.ts +3 -0
- package/dist/nested-write.js +177 -112
- package/dist/pipeline-submittable.d.ts +12 -0
- package/dist/pipeline-submittable.js +117 -44
- package/dist/pipeline.js +21 -26
- package/dist/powdb-introspect.d.ts +5 -2
- package/dist/powdb-introspect.js +21 -16
- package/dist/powdb-shared.d.ts +36 -5
- package/dist/powdb-shared.js +65 -8
- package/dist/powdb.d.ts +4 -0
- package/dist/powdb.js +183 -51
- package/dist/powql.d.ts +394 -51
- package/dist/powql.js +1836 -291
- package/dist/prisma-compat.d.ts +23 -15
- package/dist/prisma-compat.js +508 -373
- package/dist/query/aggregates.d.ts +17 -1
- package/dist/query/aggregates.js +263 -32
- package/dist/query/batched-loader.d.ts +25 -6
- package/dist/query/batched-loader.js +101 -21
- package/dist/query/builder.d.ts +325 -184
- package/dist/query/builder.js +1458 -495
- package/dist/query/column-ref.d.ts +61 -0
- package/dist/query/column-ref.js +66 -0
- package/dist/query/compound-unique.d.ts +21 -0
- package/dist/query/compound-unique.js +97 -19
- package/dist/query/deferred.d.ts +53 -16
- package/dist/query/expr.d.ts +217 -0
- package/dist/query/expr.js +848 -0
- package/dist/query/filters.d.ts +155 -53
- package/dist/query/filters.js +597 -72
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +4 -0
- package/dist/query/json-value.d.ts +43 -0
- package/dist/query/json-value.js +54 -0
- package/dist/query/normalize-args.d.ts +87 -0
- package/dist/query/normalize-args.js +517 -0
- package/dist/query/option-surface.d.ts +16 -4
- package/dist/query/option-surface.js +56 -13
- package/dist/query/relation-names.d.ts +14 -0
- package/dist/query/relation-names.js +77 -19
- package/dist/query/relations.d.ts +30 -3
- package/dist/query/relations.js +220 -67
- package/dist/query/row-lock.d.ts +49 -0
- package/dist/query/row-lock.js +131 -0
- package/dist/query/sql-comment.d.ts +161 -0
- package/dist/query/sql-comment.js +374 -0
- package/dist/query/stream-idle.d.ts +132 -0
- package/dist/query/stream-idle.js +203 -0
- package/dist/query/table-client.d.ts +101 -0
- package/dist/query/table-client.js +70 -0
- package/dist/query/types.d.ts +677 -298
- package/dist/query/utils.d.ts +105 -6
- package/dist/query/utils.js +414 -18
- package/dist/query/warn-registry.d.ts +18 -0
- package/dist/query/warn-registry.js +18 -0
- package/dist/query/where-compile.d.ts +138 -14
- package/dist/query/where-compile.js +508 -31
- package/dist/query/where.d.ts +159 -59
- package/dist/query/where.js +711 -248
- package/dist/query/writes.d.ts +112 -50
- package/dist/query/writes.js +1496 -202
- package/dist/query-events.d.ts +4 -2
- package/dist/query-events.js +3 -1
- package/dist/realtime.d.ts +7 -3
- package/dist/realtime.js +13 -2
- package/dist/schema-builder.d.ts +55 -4
- package/dist/schema-builder.js +381 -18
- package/dist/schema-metadata.d.ts +21 -1
- package/dist/schema-metadata.js +28 -7
- package/dist/schema-sql.d.ts +75 -5
- package/dist/schema-sql.js +494 -44
- package/dist/schema.d.ts +26 -5
- package/dist/schema.js +109 -13
- package/dist/sql-placeholders.d.ts +56 -0
- package/dist/sql-placeholders.js +200 -0
- package/dist/sqlite.d.ts +75 -20
- package/dist/sqlite.js +597 -99
- package/dist/supplied-pool-types.d.ts +48 -0
- package/dist/supplied-pool-types.js +247 -0
- package/dist/transaction-control.d.ts +51 -0
- package/dist/transaction-control.js +311 -0
- package/dist/transaction-state.d.ts +342 -0
- package/dist/transaction-state.js +696 -0
- package/dist/typed-sql.d.ts +37 -3
- package/dist/typed-sql.js +100 -8
- package/package.json +7 -4
- package/skills/turbine-orm/SKILL.md +56 -6
package/dist/schema.d.ts
CHANGED
|
@@ -79,10 +79,24 @@ export interface ColumnMetadata {
|
|
|
79
79
|
pgTypeSchema?: string;
|
|
80
80
|
/** TypeScript type string (e.g. 'number', 'string', 'Date') */
|
|
81
81
|
tsType: string;
|
|
82
|
+
/**
|
|
83
|
+
* The TypeScript type a `defineSchema()` file declared for this column, WITHOUT
|
|
84
|
+
* `| null`. Only `turbine generate` reads it. Every engine branches on
|
|
85
|
+
* {@link tsType}, which stays the type mapped from the database type (MySQL
|
|
86
|
+
* boolean normalization and PowDB column types depend on that). Absent for
|
|
87
|
+
* introspected metadata and for columns that declare no type.
|
|
88
|
+
*/
|
|
89
|
+
declaredTsType?: string;
|
|
82
90
|
/** Whether the column allows NULL */
|
|
83
91
|
nullable: boolean;
|
|
84
92
|
/** Whether the column has a DEFAULT, is serial, or is generated */
|
|
85
93
|
hasDefault: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* The column's declared DEFAULT expression as SQL text (for example `now()`, `'active'`, `0`), when the
|
|
96
|
+
* metadata source knows it. defineSchema records it; engines with no server-side defaults (PowDB) apply it
|
|
97
|
+
* client-side. Absent for introspected metadata.
|
|
98
|
+
*/
|
|
99
|
+
defaultValue?: string | null;
|
|
86
100
|
/**
|
|
87
101
|
* Whether the **database server** generates this column's value on insert -
|
|
88
102
|
* a `serial`/`BIGSERIAL` sequence, an `IDENTITY` column, or PowDB's `auto`
|
|
@@ -114,10 +128,10 @@ export interface ColumnMetadata {
|
|
|
114
128
|
* True when this column holds personally identifiable information (PII).
|
|
115
129
|
* Tagged in `defineSchema` (`pii: true`) and carried through generated
|
|
116
130
|
* metadata. A PII column is EXCLUDED from default projections: it comes back
|
|
117
|
-
* only
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* PII cells by default.
|
|
131
|
+
* only from a read that passes `includePii: UNSAFE` (the imported sentinel
|
|
132
|
+
* symbol, since a literal `true` is what a stray JSON body deserializes to).
|
|
133
|
+
* Naming it in `select` without that flag throws (E003), and a write never
|
|
134
|
+
* returns it. Studio redacts PII cells by default.
|
|
121
135
|
* Optional / defaults to `false`; untagged schemas behave exactly as before.
|
|
122
136
|
* Introspection never auto-tags PII (it is a code-first declaration).
|
|
123
137
|
*/
|
|
@@ -228,8 +242,15 @@ export interface IndexMetadata {
|
|
|
228
242
|
*/
|
|
229
243
|
declared?: boolean;
|
|
230
244
|
}
|
|
231
|
-
/** Map a Postgres type to
|
|
245
|
+
/** Map a Postgres type to the TypeScript type a read returns for it. */
|
|
232
246
|
export declare function pgTypeToTs(pgType: string, nullable: boolean): string;
|
|
247
|
+
/**
|
|
248
|
+
* Map a Postgres type to the TypeScript type the write path accepts for it
|
|
249
|
+
* (the generated `*Create` / `*Update` field types). Wider than
|
|
250
|
+
* {@link pgTypeToTs} where the runtime takes more than one spelling, narrower
|
|
251
|
+
* where the parsed read value cannot be written back (see `PG_TO_TS_INPUT`).
|
|
252
|
+
*/
|
|
253
|
+
export declare function pgTypeToTsInput(pgType: string, nullable: boolean): string;
|
|
233
254
|
/** Check if a Postgres type is a date/timestamp that needs Date parsing */
|
|
234
255
|
export declare function isDateType(pgType: string): boolean;
|
|
235
256
|
/**
|
package/dist/schema.js
CHANGED
|
@@ -14,6 +14,17 @@ export function normalizeKeyColumns(key) {
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
// Type mapping: Postgres → TypeScript
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
|
+
/**
|
|
18
|
+
* The TypeScript row types below describe what a READ returns, which is what
|
|
19
|
+
* the pg driver's text parsers produce (plus the parsers TurbineClient
|
|
20
|
+
* registers), not what the type is called. A nested relation read reproduces
|
|
21
|
+
* the same value through `JSON_WIRE_COERCION_OIDS` in query/utils.ts.
|
|
22
|
+
* `src/test/types-runtime-parity.integration.test.ts` reads every type live and
|
|
23
|
+
* compares it with this table.
|
|
24
|
+
*/
|
|
25
|
+
const PG_INTERVAL_TS = '{ years?: number; months?: number; days?: number; hours?: number; minutes?: number; seconds?: number; milliseconds?: number }';
|
|
26
|
+
const PG_POINT_TS = '{ x: number; y: number }';
|
|
27
|
+
const PG_CIRCLE_TS = '{ x: number; y: number; radius: number }';
|
|
17
28
|
const PG_TO_TS = {
|
|
18
29
|
// Integers
|
|
19
30
|
int2: 'number',
|
|
@@ -45,7 +56,9 @@ const PG_TO_TS = {
|
|
|
45
56
|
date: 'Date',
|
|
46
57
|
time: 'string',
|
|
47
58
|
timetz: 'string',
|
|
48
|
-
interval
|
|
59
|
+
// The driver parses an interval into an object (postgres-interval). Zero
|
|
60
|
+
// fields are absent, so every field is optional.
|
|
61
|
+
interval: PG_INTERVAL_TS,
|
|
49
62
|
// JSON
|
|
50
63
|
json: 'unknown',
|
|
51
64
|
jsonb: 'unknown',
|
|
@@ -55,17 +68,34 @@ const PG_TO_TS = {
|
|
|
55
68
|
inet: 'string',
|
|
56
69
|
cidr: 'string',
|
|
57
70
|
macaddr: 'string',
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
macaddr8: 'string',
|
|
72
|
+
// Geometric: the driver parses point and circle; the others stay text.
|
|
73
|
+
point: PG_POINT_TS,
|
|
60
74
|
line: 'string',
|
|
61
75
|
lseg: 'string',
|
|
62
76
|
box: 'string',
|
|
63
77
|
path: 'string',
|
|
64
78
|
polygon: 'string',
|
|
65
|
-
circle:
|
|
79
|
+
circle: PG_CIRCLE_TS,
|
|
80
|
+
// Bit strings come back as their text form ('1010').
|
|
81
|
+
bit: 'string',
|
|
82
|
+
varbit: 'string',
|
|
66
83
|
// TSVector
|
|
67
84
|
tsvector: 'string',
|
|
68
85
|
tsquery: 'string',
|
|
86
|
+
// Ranges and multiranges come back as their text form ('[1,5)').
|
|
87
|
+
int4range: 'string',
|
|
88
|
+
int8range: 'string',
|
|
89
|
+
numrange: 'string',
|
|
90
|
+
tsrange: 'string',
|
|
91
|
+
tstzrange: 'string',
|
|
92
|
+
daterange: 'string',
|
|
93
|
+
int4multirange: 'string',
|
|
94
|
+
int8multirange: 'string',
|
|
95
|
+
nummultirange: 'string',
|
|
96
|
+
tsmultirange: 'string',
|
|
97
|
+
tstzmultirange: 'string',
|
|
98
|
+
datemultirange: 'string',
|
|
69
99
|
// pgvector, embeddings. Mapped to `number[]` for DX (the natural shape an app
|
|
70
100
|
// passes when inserting / comparing embeddings). NOTE: like `numeric` above,
|
|
71
101
|
// there is a runtime caveat, pg has no built-in parser for the `vector` type,
|
|
@@ -77,6 +107,35 @@ const PG_TO_TS = {
|
|
|
77
107
|
// param, so writing/comparing is unaffected by the read-side caveat.
|
|
78
108
|
vector: 'number[]',
|
|
79
109
|
};
|
|
110
|
+
/**
|
|
111
|
+
* Array types whose element type is NOT the scalar's row type. The driver's
|
|
112
|
+
* `bigint[]` parser returns every element as a string (the scalar int8 parser
|
|
113
|
+
* TurbineClient registers does not apply to arrays), so `bigint[]` reads as
|
|
114
|
+
* `string[]` while `bigint` reads as `number`. The driver has no `circle[]`
|
|
115
|
+
* parser, so its elements stay text (`'<(1,2),3>'`) where a scalar circle is an
|
|
116
|
+
* object.
|
|
117
|
+
*/
|
|
118
|
+
const PG_ARRAY_TO_TS = {
|
|
119
|
+
_int8: 'string[]',
|
|
120
|
+
_circle: 'string[]',
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Types whose WRITE input differs from the row type. Each entry is what the
|
|
124
|
+
* write path accepts, checked live:
|
|
125
|
+
* - int8: a `number`, a decimal string (what a large value reads back as) or
|
|
126
|
+
* a `bigint`.
|
|
127
|
+
* - numeric: the exact string, or a `number`.
|
|
128
|
+
* - interval / point / circle: the text form. The parsed object a read
|
|
129
|
+
* returns is not accepted for point and circle, and a plain object is read
|
|
130
|
+
* as an update operator, so the object form is not part of the input type.
|
|
131
|
+
*/
|
|
132
|
+
const PG_TO_TS_INPUT = {
|
|
133
|
+
int8: 'number | string | bigint',
|
|
134
|
+
numeric: 'string | number',
|
|
135
|
+
interval: 'string',
|
|
136
|
+
point: 'string',
|
|
137
|
+
circle: 'string',
|
|
138
|
+
};
|
|
80
139
|
const DATE_TYPES = new Set(['timestamptz', 'timestamp', 'date']);
|
|
81
140
|
/**
|
|
82
141
|
* Postgres type → the array cast `createMany`'s `UNNEST(ARRAY[...]::<type>)`
|
|
@@ -154,21 +213,53 @@ const PG_TO_ARRAY = {
|
|
|
154
213
|
// pgvector
|
|
155
214
|
vector: 'vector[]',
|
|
156
215
|
};
|
|
157
|
-
/** Map a Postgres type to
|
|
216
|
+
/** Map a Postgres type to the TypeScript type a read returns for it. */
|
|
158
217
|
export function pgTypeToTs(pgType, nullable) {
|
|
159
|
-
|
|
218
|
+
let tsType;
|
|
219
|
+
if (Object.hasOwn(PG_ARRAY_TO_TS, pgType)) {
|
|
220
|
+
tsType = PG_ARRAY_TO_TS[pgType];
|
|
221
|
+
}
|
|
222
|
+
else if (pgType.startsWith('_')) {
|
|
223
|
+
// Array types: udt_name starts with '_'
|
|
224
|
+
tsType = arrayOf(pgTypeToTs(pgType.slice(1), false));
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
tsType = Object.hasOwn(PG_TO_TS, pgType) ? PG_TO_TS[pgType] : 'unknown';
|
|
228
|
+
}
|
|
229
|
+
return nullable ? `${tsType} | null` : tsType;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Map a Postgres type to the TypeScript type the write path accepts for it
|
|
233
|
+
* (the generated `*Create` / `*Update` field types). Wider than
|
|
234
|
+
* {@link pgTypeToTs} where the runtime takes more than one spelling, narrower
|
|
235
|
+
* where the parsed read value cannot be written back (see `PG_TO_TS_INPUT`).
|
|
236
|
+
*/
|
|
237
|
+
export function pgTypeToTsInput(pgType, nullable) {
|
|
238
|
+
let tsType;
|
|
160
239
|
if (pgType.startsWith('_')) {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
240
|
+
const element = pgType.slice(1);
|
|
241
|
+
tsType = Object.hasOwn(PG_TO_TS_INPUT, element) ? arrayOf(PG_TO_TS_INPUT[element]) : pgTypeToTs(pgType, false);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
tsType = Object.hasOwn(PG_TO_TS_INPUT, pgType) ? PG_TO_TS_INPUT[pgType] : pgTypeToTs(pgType, false);
|
|
164
245
|
}
|
|
165
|
-
const tsType = PG_TO_TS[pgType] ?? 'unknown';
|
|
166
246
|
return nullable ? `${tsType} | null` : tsType;
|
|
167
247
|
}
|
|
248
|
+
/** `T[]`, parenthesized when `T` is a union. */
|
|
249
|
+
function arrayOf(element) {
|
|
250
|
+
return element.includes('|') ? `(${element})[]` : `${element}[]`;
|
|
251
|
+
}
|
|
168
252
|
/** Check if a Postgres type is a date/timestamp that needs Date parsing */
|
|
169
253
|
export function isDateType(pgType) {
|
|
170
254
|
return DATE_TYPES.has(pgType);
|
|
171
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* A precision suffix with the whitespace around it: `(6)` in `time(6)` or
|
|
258
|
+
* `time (6) with time zone`. Every quantifier is bounded, because a leading
|
|
259
|
+
* `\s*` retried from each position of a long whitespace run is quadratic in
|
|
260
|
+
* the type name's length; no real type name comes near the bounds.
|
|
261
|
+
*/
|
|
262
|
+
const TYPE_PRECISION = /\s{0,32}\(\s{0,32}\d{1,9}\s{0,32}\)\s{0,32}/;
|
|
172
263
|
/**
|
|
173
264
|
* Classify a column type as a TIME-OF-DAY type (`time` / `timetz`), or `null`
|
|
174
265
|
* for anything else.
|
|
@@ -194,7 +285,7 @@ export function timeOfDayKind(dbType) {
|
|
|
194
285
|
.toLowerCase()
|
|
195
286
|
// Precision can sit at the end (`time(6)`) or mid-spelling
|
|
196
287
|
// (`time(6) with time zone`), so strip it wherever it appears.
|
|
197
|
-
.replace(
|
|
288
|
+
.replace(TYPE_PRECISION, ' ')
|
|
198
289
|
.trim();
|
|
199
290
|
if (t === 'time' || t === 'time without time zone')
|
|
200
291
|
return 'time';
|
|
@@ -229,7 +320,7 @@ export function localDateTimeKind(dbType) {
|
|
|
229
320
|
.toLowerCase()
|
|
230
321
|
// Precision can sit at the end (`timestamp(3)`) or mid-spelling
|
|
231
322
|
// (`timestamp(3) without time zone`), so strip it wherever it appears.
|
|
232
|
-
.replace(
|
|
323
|
+
.replace(TYPE_PRECISION, ' ')
|
|
233
324
|
.trim();
|
|
234
325
|
if (t === 'date')
|
|
235
326
|
return 'date';
|
|
@@ -239,13 +330,18 @@ export function localDateTimeKind(dbType) {
|
|
|
239
330
|
}
|
|
240
331
|
/** Get the Postgres array cast type for UNNEST batch inserts */
|
|
241
332
|
export function pgArrayType(pgType) {
|
|
242
|
-
return PG_TO_ARRAY[pgType]
|
|
333
|
+
return Object.hasOwn(PG_TO_ARRAY, pgType) ? PG_TO_ARRAY[pgType] : 'text[]';
|
|
243
334
|
}
|
|
244
335
|
// ---------------------------------------------------------------------------
|
|
245
336
|
// Name conversion utilities
|
|
246
337
|
// ---------------------------------------------------------------------------
|
|
247
338
|
/** snake_case → camelCase */
|
|
248
339
|
export function snakeToCamel(s) {
|
|
340
|
+
// The pattern needs an underscore, so a name without one is returned as-is
|
|
341
|
+
// without running the regex. Hot: every where/orderBy key goes through
|
|
342
|
+
// resolveRelation, which calls this for each non-relation key.
|
|
343
|
+
if (s.indexOf('_') === -1)
|
|
344
|
+
return s;
|
|
249
345
|
return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
250
346
|
}
|
|
251
347
|
/** camelCase → snake_case */
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the bind placeholders are in a statement, read with the lexical rules
|
|
3
|
+
* of the engine that will run it.
|
|
4
|
+
*
|
|
5
|
+
* The SQLite, MySQL and SQL Server shims rewrite placeholders before handing a
|
|
6
|
+
* statement to their driver: a `$n` written in raw SQL becomes the engine's own
|
|
7
|
+
* spelling, and MySQL expands an `in` list into one placeholder per element. A
|
|
8
|
+
* rewrite that lands inside a string literal, a quoted identifier or a comment
|
|
9
|
+
* changes what the statement means, so the scan has to agree with the ENGINE
|
|
10
|
+
* about which characters are code. That is why this is not the migration
|
|
11
|
+
* tokenizer in `cli/sql-statements.ts`: that one copies PostgreSQL's lexer
|
|
12
|
+
* (dollar quoting, `E''` strings, nested comments) and knows nothing of
|
|
13
|
+
* backtick or bracket identifiers, `#` comments or MySQL's backslash escapes,
|
|
14
|
+
* each of which would put a rewrite inside quoted text here.
|
|
15
|
+
*
|
|
16
|
+
* Where a reading is ambiguous the scan treats more text as quoted, never less:
|
|
17
|
+
* a placeholder it fails to see reaches the driver unrewritten and fails
|
|
18
|
+
* loudly, while one it invents inside a literal silently changes stored data.
|
|
19
|
+
* MySQL's backslash escapes are the case in point. A pool Turbine builds turns
|
|
20
|
+
* them off (`NO_BACKSLASH_ESCAPES`), an injected pool may not, and treating
|
|
21
|
+
* `\'` as an escape is the reading that can only hide a placeholder.
|
|
22
|
+
*
|
|
23
|
+
* Zero imports: every engine entry shares it.
|
|
24
|
+
*/
|
|
25
|
+
/** Which engine's lexical rules to apply. */
|
|
26
|
+
export type PlaceholderSyntax = 'mysql' | 'sqlite' | 'mssql';
|
|
27
|
+
/** One placeholder found in code (never inside a literal, a quoted identifier or a comment). */
|
|
28
|
+
export interface PlaceholderToken {
|
|
29
|
+
/** `$n` (the portable positional spelling), a named `:name` (MySQL only), or a bare `?`. */
|
|
30
|
+
readonly kind: 'positional' | 'named' | 'question';
|
|
31
|
+
/** Offset of the first character (`$` or `:`). */
|
|
32
|
+
readonly start: number;
|
|
33
|
+
/** Offset just past the last character. */
|
|
34
|
+
readonly end: number;
|
|
35
|
+
/** The digits of a `$n`, the name of a `:name`, or `?`. */
|
|
36
|
+
readonly name: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Every placeholder in `sql` that the engine would read as code, in order.
|
|
40
|
+
*
|
|
41
|
+
* `$n` is reported only where the engine would see a token of its own: not
|
|
42
|
+
* when the `$` continues a word (`price$1`), and not when the digits run on
|
|
43
|
+
* into one (`$1abc`, which MySQL reads as an identifier).
|
|
44
|
+
*/
|
|
45
|
+
export declare function scanPlaceholders(sql: string, syntax: PlaceholderSyntax): PlaceholderToken[];
|
|
46
|
+
/**
|
|
47
|
+
* Rewrite every `$n` in `sql` to the engine's spelling of value `n`, checked
|
|
48
|
+
* against the number of values the statement was given. Returns `sql` itself
|
|
49
|
+
* when it holds no `$n`. A statement given no values is never rewritten: on
|
|
50
|
+
* MySQL `$1` is a legal identifier, and with nothing to bind there is nothing
|
|
51
|
+
* a placeholder could mean.
|
|
52
|
+
*
|
|
53
|
+
* `refuse` throws the caller's error for a `$n` that names no value, and for a
|
|
54
|
+
* bare `?`: these shims bind values by name, so a `?` could never receive one.
|
|
55
|
+
*/
|
|
56
|
+
export declare function rewritePositionalPlaceholders(sql: string, syntax: PlaceholderSyntax, valueCount: number, spell: (index: number) => string, refuse: (message: string) => never): string;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the bind placeholders are in a statement, read with the lexical rules
|
|
3
|
+
* of the engine that will run it.
|
|
4
|
+
*
|
|
5
|
+
* The SQLite, MySQL and SQL Server shims rewrite placeholders before handing a
|
|
6
|
+
* statement to their driver: a `$n` written in raw SQL becomes the engine's own
|
|
7
|
+
* spelling, and MySQL expands an `in` list into one placeholder per element. A
|
|
8
|
+
* rewrite that lands inside a string literal, a quoted identifier or a comment
|
|
9
|
+
* changes what the statement means, so the scan has to agree with the ENGINE
|
|
10
|
+
* about which characters are code. That is why this is not the migration
|
|
11
|
+
* tokenizer in `cli/sql-statements.ts`: that one copies PostgreSQL's lexer
|
|
12
|
+
* (dollar quoting, `E''` strings, nested comments) and knows nothing of
|
|
13
|
+
* backtick or bracket identifiers, `#` comments or MySQL's backslash escapes,
|
|
14
|
+
* each of which would put a rewrite inside quoted text here.
|
|
15
|
+
*
|
|
16
|
+
* Where a reading is ambiguous the scan treats more text as quoted, never less:
|
|
17
|
+
* a placeholder it fails to see reaches the driver unrewritten and fails
|
|
18
|
+
* loudly, while one it invents inside a literal silently changes stored data.
|
|
19
|
+
* MySQL's backslash escapes are the case in point. A pool Turbine builds turns
|
|
20
|
+
* them off (`NO_BACKSLASH_ESCAPES`), an injected pool may not, and treating
|
|
21
|
+
* `\'` as an escape is the reading that can only hide a placeholder.
|
|
22
|
+
*
|
|
23
|
+
* Zero imports: every engine entry shares it.
|
|
24
|
+
*/
|
|
25
|
+
const DIGIT = /[0-9]/;
|
|
26
|
+
/** Characters that continue an unquoted identifier or number on every engine here. */
|
|
27
|
+
const WORD = /[A-Za-z0-9_$\u0080-]/;
|
|
28
|
+
/** SQL Server also continues an identifier with `@` and `#` (`@p1`, `#temp`). */
|
|
29
|
+
const MSSQL_WORD = /[A-Za-z0-9_$@#\u0080-]/;
|
|
30
|
+
/** mysql2's named placeholder: `:` then digits, or a letter and word characters. */
|
|
31
|
+
const MYSQL_NAMED = /^:([0-9]+|[A-Za-z][A-Za-z0-9_]*)/;
|
|
32
|
+
/**
|
|
33
|
+
* Offset just past a quoted run that opened at `i`. `close` doubled (`''`,
|
|
34
|
+
* `""`, `` ` ` ``, and `]]` on SQL Server) is an escaped quote, not the end;
|
|
35
|
+
* with `backslash`, a backslash escapes the next character too.
|
|
36
|
+
*/
|
|
37
|
+
function skipQuoted(sql, i, close, doubled, backslash) {
|
|
38
|
+
const n = sql.length;
|
|
39
|
+
let j = i + 1;
|
|
40
|
+
while (j < n) {
|
|
41
|
+
const ch = sql[j];
|
|
42
|
+
if (backslash && ch === '\\') {
|
|
43
|
+
j += 2;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (ch === close) {
|
|
47
|
+
if (doubled && sql[j + 1] === close) {
|
|
48
|
+
j += 2;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
return j + 1;
|
|
52
|
+
}
|
|
53
|
+
j++;
|
|
54
|
+
}
|
|
55
|
+
return n;
|
|
56
|
+
}
|
|
57
|
+
/** Offset just past a block comment opening at `i`. SQL Server nests them; MySQL and SQLite do not. */
|
|
58
|
+
function skipBlockComment(sql, i, nests) {
|
|
59
|
+
const n = sql.length;
|
|
60
|
+
let depth = 1;
|
|
61
|
+
let j = i + 2;
|
|
62
|
+
while (j < n) {
|
|
63
|
+
if (nests && sql[j] === '/' && sql[j + 1] === '*') {
|
|
64
|
+
depth++;
|
|
65
|
+
j += 2;
|
|
66
|
+
}
|
|
67
|
+
else if (sql[j] === '*' && sql[j + 1] === '/') {
|
|
68
|
+
j += 2;
|
|
69
|
+
if (--depth === 0)
|
|
70
|
+
return j;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
j++;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return n;
|
|
77
|
+
}
|
|
78
|
+
/** True when `--` at `i` opens a comment. MySQL needs whitespace (or the end) after the dashes. */
|
|
79
|
+
function opensLineComment(sql, i, syntax) {
|
|
80
|
+
if (sql[i] !== '-' || sql[i + 1] !== '-')
|
|
81
|
+
return false;
|
|
82
|
+
if (syntax !== 'mysql')
|
|
83
|
+
return true;
|
|
84
|
+
const after = sql[i + 2];
|
|
85
|
+
return after === undefined || /\s/.test(after) || after.charCodeAt(0) < 32;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Every placeholder in `sql` that the engine would read as code, in order.
|
|
89
|
+
*
|
|
90
|
+
* `$n` is reported only where the engine would see a token of its own: not
|
|
91
|
+
* when the `$` continues a word (`price$1`), and not when the digits run on
|
|
92
|
+
* into one (`$1abc`, which MySQL reads as an identifier).
|
|
93
|
+
*/
|
|
94
|
+
export function scanPlaceholders(sql, syntax) {
|
|
95
|
+
const out = [];
|
|
96
|
+
const n = sql.length;
|
|
97
|
+
const word = syntax === 'mssql' ? MSSQL_WORD : WORD;
|
|
98
|
+
const backslash = syntax === 'mysql';
|
|
99
|
+
let i = 0;
|
|
100
|
+
while (i < n) {
|
|
101
|
+
const ch = sql[i];
|
|
102
|
+
if (opensLineComment(sql, i, syntax) || (syntax === 'mysql' && ch === '#')) {
|
|
103
|
+
while (i < n && sql[i] !== '\n')
|
|
104
|
+
i++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (ch === '/' && sql[i + 1] === '*') {
|
|
108
|
+
i = skipBlockComment(sql, i, syntax === 'mssql');
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (ch === "'") {
|
|
112
|
+
i = skipQuoted(sql, i, "'", true, backslash);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (ch === '"') {
|
|
116
|
+
// A string on MySQL, an identifier on SQLite and SQL Server: skipped either way.
|
|
117
|
+
i = skipQuoted(sql, i, '"', true, backslash);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (ch === '`' && syntax !== 'mssql') {
|
|
121
|
+
i = skipQuoted(sql, i, '`', true, false);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (ch === '[' && syntax !== 'mysql') {
|
|
125
|
+
// SQLite ends a bracket identifier at the first `]`; SQL Server escapes it as `]]`.
|
|
126
|
+
i = skipQuoted(sql, i, ']', syntax === 'mssql', false);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (ch === '$' && DIGIT.test(sql[i + 1] ?? '')) {
|
|
130
|
+
let j = i + 1;
|
|
131
|
+
while (j < n && DIGIT.test(sql[j]))
|
|
132
|
+
j++;
|
|
133
|
+
if (j < n && word.test(sql[j])) {
|
|
134
|
+
// `$1abc`: one word to the engine, not a placeholder.
|
|
135
|
+
while (j < n && word.test(sql[j]))
|
|
136
|
+
j++;
|
|
137
|
+
i = j;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
out.push({ kind: 'positional', start: i, end: j, name: sql.slice(i + 1, j) });
|
|
141
|
+
i = j;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (ch === '?') {
|
|
145
|
+
out.push({ kind: 'question', start: i, end: i + 1, name: '?' });
|
|
146
|
+
i++;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (ch === ':' && syntax === 'mysql') {
|
|
150
|
+
const m = MYSQL_NAMED.exec(sql.slice(i, i + 64));
|
|
151
|
+
if (m) {
|
|
152
|
+
out.push({ kind: 'named', start: i, end: i + m[0].length, name: m[1] });
|
|
153
|
+
i += m[0].length;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (word.test(ch)) {
|
|
158
|
+
// A whole identifier, keyword or number, so a `$` or `:` inside it is never a placeholder.
|
|
159
|
+
let j = i + 1;
|
|
160
|
+
while (j < n && word.test(sql[j]))
|
|
161
|
+
j++;
|
|
162
|
+
i = j;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
i++;
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Rewrite every `$n` in `sql` to the engine's spelling of value `n`, checked
|
|
171
|
+
* against the number of values the statement was given. Returns `sql` itself
|
|
172
|
+
* when it holds no `$n`. A statement given no values is never rewritten: on
|
|
173
|
+
* MySQL `$1` is a legal identifier, and with nothing to bind there is nothing
|
|
174
|
+
* a placeholder could mean.
|
|
175
|
+
*
|
|
176
|
+
* `refuse` throws the caller's error for a `$n` that names no value, and for a
|
|
177
|
+
* bare `?`: these shims bind values by name, so a `?` could never receive one.
|
|
178
|
+
*/
|
|
179
|
+
export function rewritePositionalPlaceholders(sql, syntax, valueCount, spell, refuse) {
|
|
180
|
+
if (valueCount === 0 || !/\$[0-9]|\?/.test(sql))
|
|
181
|
+
return sql;
|
|
182
|
+
const tokens = scanPlaceholders(sql, syntax).filter((t) => t.kind !== 'named');
|
|
183
|
+
if (tokens.length === 0)
|
|
184
|
+
return sql;
|
|
185
|
+
let out = '';
|
|
186
|
+
let last = 0;
|
|
187
|
+
for (const t of tokens) {
|
|
188
|
+
if (t.kind === 'question') {
|
|
189
|
+
refuse('A `?` placeholder cannot be bound here: write the values as $1, $2, and so on.');
|
|
190
|
+
}
|
|
191
|
+
const index = Number(t.name);
|
|
192
|
+
if (!Number.isSafeInteger(index) || index < 1 || index > valueCount) {
|
|
193
|
+
refuse(`$${t.name} names no value: the statement was given ${valueCount} ` +
|
|
194
|
+
`value${valueCount === 1 ? '' : 's'}, numbered $1 to $${valueCount}. Pass one value per placeholder.`);
|
|
195
|
+
}
|
|
196
|
+
out += sql.slice(last, t.start) + spell(index);
|
|
197
|
+
last = t.end;
|
|
198
|
+
}
|
|
199
|
+
return out + sql.slice(last);
|
|
200
|
+
}
|
package/dist/sqlite.d.ts
CHANGED
|
@@ -19,18 +19,24 @@
|
|
|
19
19
|
* - `RETURNING` + `ON CONFLICT … DO UPDATE` (SQLite ≥ 3.35) → create / upsert /
|
|
20
20
|
* update / delete return real rows in a single statement (`resultStrategy =
|
|
21
21
|
* 'returning'`, same as Postgres).
|
|
22
|
-
* - **Single-writer:** one connection / one
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* - **Single-writer:** one connection / one transaction at a time. On one
|
|
23
|
+
* client, statements from other callers wait (FIFO, up to `busyTimeoutMs`)
|
|
24
|
+
* while a transaction is open instead of running inside it; another
|
|
25
|
+
* process's writer gets `SQLITE_BUSY`. Both surface as the retryable
|
|
26
|
+
* `SerializationFailureError`. `journal_mode = WAL` is enabled for file
|
|
27
|
+
* databases to allow concurrent readers across connections.
|
|
25
28
|
* - **Unsupported (throw `UnsupportedFeatureError`):** pgvector distance ops,
|
|
26
29
|
* LISTEN/NOTIFY (`$listen` / `$notify`), RLS `sessionContext`. Advisory-lock
|
|
27
30
|
* migration locking is unavailable, SQLite is single-writer, so migrations
|
|
28
31
|
* serialize naturally.
|
|
29
|
-
* - **Type affinity caveats:** SQLite has no native `BOOLEAN` (0/1 integers)
|
|
30
|
-
* `DATE` (TEXT/INTEGER). Booleans bind as 1/0; `Date` values
|
|
31
|
-
* text;
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* - **Type affinity caveats:** SQLite has no native `BOOLEAN` (0/1 integers),
|
|
33
|
+
* `JSON` (text) or `DATE` (TEXT/INTEGER). Booleans bind as 1/0; `Date` values
|
|
34
|
+
* bind as ISO-8601 text; an integral JS number binds as INTEGER. A column the
|
|
35
|
+
* schema declares boolean or json/jsonb reads back as a boolean or a parsed
|
|
36
|
+
* value on every path; date/time columns are coerced back to `Date`.
|
|
37
|
+
* Integers wider than `Number.MAX_SAFE_INTEGER` come back as strings (the
|
|
38
|
+
* same safe-int policy Turbine uses for Postgres `int8`). `numeric` has no
|
|
39
|
+
* exact storage: it reads back as a JS number.
|
|
34
40
|
* - **Case-insensitive matching** uses `COLLATE NOCASE`, which is **ASCII-only**
|
|
35
41
|
* (no Unicode case folding).
|
|
36
42
|
*
|
|
@@ -68,7 +74,7 @@
|
|
|
68
74
|
*
|
|
69
75
|
* const db = turbineSqlite(':memory:', schemaDefToMetadata(schema));
|
|
70
76
|
* for (const stmt of schemaToSQL(schema, { dialect: sqliteDialect })) {
|
|
71
|
-
* await db.
|
|
77
|
+
* await db.rawUnsafe(stmt);
|
|
72
78
|
* }
|
|
73
79
|
*
|
|
74
80
|
* // `db.table(...)`, not `db.users`: the typed property accessors are emitted
|
|
@@ -88,27 +94,75 @@ type QueryArg = string | {
|
|
|
88
94
|
text: string;
|
|
89
95
|
values?: unknown[];
|
|
90
96
|
};
|
|
97
|
+
/** Options for {@link SqlitePool}. */
|
|
98
|
+
export interface SqlitePoolOptions {
|
|
99
|
+
/**
|
|
100
|
+
* How long, in ms, a statement waits for a transaction another caller has
|
|
101
|
+
* open on this handle before failing with a retryable
|
|
102
|
+
* `SerializationFailureError`. Default: 5000, the same bound as
|
|
103
|
+
* `PRAGMA busy_timeout`.
|
|
104
|
+
*/
|
|
105
|
+
busyTimeoutMs?: number;
|
|
106
|
+
/**
|
|
107
|
+
* The client's schema. A column it declares boolean or json/jsonb is read
|
|
108
|
+
* back as a boolean or a parsed value, whatever its SQLite declared type.
|
|
109
|
+
* `turbineSqlite` passes the schema it was given.
|
|
110
|
+
*/
|
|
111
|
+
schema?: SchemaMetadata;
|
|
112
|
+
}
|
|
91
113
|
/**
|
|
92
114
|
* A `PgCompatPool` backed by a single `node:sqlite` `DatabaseSync` connection.
|
|
93
115
|
* SQLite is single-connection by nature (a `:memory:` database is per-handle),
|
|
94
|
-
* so `connect()` hands back a client over the **same** handle
|
|
95
|
-
*
|
|
96
|
-
*
|
|
116
|
+
* so `connect()` hands back a client over the **same** handle.
|
|
117
|
+
*
|
|
118
|
+
* One handle carries one transaction, so the pool decides who may use it. A
|
|
119
|
+
* caller whose statement opens a transaction OWNS the handle until that
|
|
120
|
+
* transaction ends; every other caller's statements, plain queries included,
|
|
121
|
+
* wait their turn in FIFO order (bounded by `busyTimeoutMs`) instead of running
|
|
122
|
+
* inside it. Without that, a write issued outside an open transaction joined
|
|
123
|
+
* it, reported success, and was rolled back with it, and reads saw its
|
|
124
|
+
* uncommitted rows. A checkout that does not open a transaction (a stream, a
|
|
125
|
+
* connectivity check) holds nothing, so other callers keep running beside it.
|
|
97
126
|
*/
|
|
98
127
|
export declare class SqlitePool implements PgCompatPool {
|
|
99
128
|
/** The underlying `node:sqlite` handle, exposed as an escape hatch (seed/DDL). */
|
|
100
129
|
readonly db: DatabaseSync;
|
|
101
130
|
private closed;
|
|
102
131
|
/**
|
|
103
|
-
* `sql ->
|
|
132
|
+
* `sql -> result columns to decode` (or `null` for none). Bounded like
|
|
104
133
|
* the query builder's SQL-template cache, since a caller can generate
|
|
105
134
|
* unbounded distinct statements (raw SQL, an IN-list that inlines).
|
|
106
135
|
*/
|
|
107
|
-
private readonly
|
|
108
|
-
|
|
136
|
+
private readonly decoderCache;
|
|
137
|
+
/** Schema columns whose stored form is not their declared JS type (see {@link declaredDecoders}). */
|
|
138
|
+
private readonly declared;
|
|
139
|
+
private readonly busyTimeoutMs;
|
|
140
|
+
/** Who may run statements on the handle right now; `null` when nobody holds it. */
|
|
141
|
+
private owner;
|
|
142
|
+
private readonly waiters;
|
|
143
|
+
/** The owner for statements sent through `pool.query()` rather than a checkout. */
|
|
144
|
+
private readonly poolOwner;
|
|
145
|
+
/** Transaction state when the driver cannot report it (`isTransaction` arrived in Node 22.16). */
|
|
146
|
+
private trackedTransaction;
|
|
147
|
+
constructor(db: DatabaseSync, options?: SqlitePoolOptions);
|
|
109
148
|
query(text: QueryArg, values?: unknown[]): Promise<any>;
|
|
110
149
|
connect(): Promise<PgCompatPoolClient>;
|
|
111
150
|
end(): Promise<void>;
|
|
151
|
+
/** Is the handle inside a transaction right now? */
|
|
152
|
+
private inTransaction;
|
|
153
|
+
/** Run one statement for `owner`, waiting first if another caller's transaction holds the handle. */
|
|
154
|
+
private runAs;
|
|
155
|
+
/** True when the current async context is the callback of the transaction that owns the handle. */
|
|
156
|
+
private calledFromOwnersCallback;
|
|
157
|
+
private waitForHandle;
|
|
158
|
+
/** Give the handle to the next waiter, which runs its statement as soon as it resumes. */
|
|
159
|
+
private handOff;
|
|
160
|
+
/**
|
|
161
|
+
* A checkout ended. If it still holds an open transaction (a timed-out or
|
|
162
|
+
* abandoned callback), roll it back first, so the next caller does not
|
|
163
|
+
* start inside it.
|
|
164
|
+
*/
|
|
165
|
+
private releaseOwner;
|
|
112
166
|
}
|
|
113
167
|
/**
|
|
114
168
|
* Map a SQLite declared column type (type affinity) to a TypeScript type.
|
|
@@ -131,10 +185,7 @@ export declare const sqliteDialect: Dialect;
|
|
|
131
185
|
* Exposed directly so callers (and tests) can introspect an in-process
|
|
132
186
|
* `:memory:` database without round-tripping through a file path.
|
|
133
187
|
*/
|
|
134
|
-
export declare function introspectSqliteDatabase(db: DatabaseSync, options?: {
|
|
135
|
-
include?: string[];
|
|
136
|
-
exclude?: string[];
|
|
137
|
-
}): {
|
|
188
|
+
export declare function introspectSqliteDatabase(db: DatabaseSync, options?: Pick<IntrospectOptions, 'include' | 'exclude' | 'logger'>): {
|
|
138
189
|
tables: Record<string, TableMetadata>;
|
|
139
190
|
enums: {};
|
|
140
191
|
};
|
|
@@ -159,7 +210,11 @@ export interface TurbineSqliteOptions extends EngineClientConfig {
|
|
|
159
210
|
* Ignored for `':memory:'`. Default: `true`.
|
|
160
211
|
*/
|
|
161
212
|
wal?: boolean;
|
|
162
|
-
/**
|
|
213
|
+
/**
|
|
214
|
+
* How long, in ms, a statement waits for the database: `PRAGMA busy_timeout`
|
|
215
|
+
* for another process's lock, and the wait behind a transaction another
|
|
216
|
+
* caller has open on this client. Default: 5000.
|
|
217
|
+
*/
|
|
163
218
|
busyTimeoutMs?: number;
|
|
164
219
|
/** Enable `PRAGMA foreign_keys` enforcement. Default: `true`. */
|
|
165
220
|
foreignKeys?: boolean;
|