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-builder.js
CHANGED
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
* });
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
|
+
// Type-only import, erased at runtime, so it cannot introduce a circular
|
|
26
|
+
// runtime dependency (the local `camelToSnakeLocal` copy avoids that for the
|
|
27
|
+
// value-level helper).
|
|
28
|
+
import { ValidationError } from './errors.js';
|
|
29
|
+
import { suggestKey } from './query/utils.js';
|
|
25
30
|
/** Maps shorthand names to actual Postgres type strings */
|
|
26
31
|
const TYPE_MAP = {
|
|
27
32
|
// `serial` maps to SERIAL (int4). Its values fit in a JS `number` and pg
|
|
@@ -56,16 +61,42 @@ const TYPE_MAP = {
|
|
|
56
61
|
enum: 'ENUM',
|
|
57
62
|
vector: 'VECTOR',
|
|
58
63
|
};
|
|
64
|
+
/** What a value is, for a message: `an array`, `null`, `a string`. */
|
|
65
|
+
function describeKind(value) {
|
|
66
|
+
if (value === null)
|
|
67
|
+
return 'null';
|
|
68
|
+
if (Array.isArray(value))
|
|
69
|
+
return 'an array';
|
|
70
|
+
return typeof value === 'object' ? 'an object' : `a ${typeof value}`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A declared DEFAULT as the SQL text the DDL generators take. A string is raw
|
|
74
|
+
* SQL, kept as written. A finite number or a boolean is the literal it is,
|
|
75
|
+
* where it used to reach the generator as a non-string and crash it with a
|
|
76
|
+
* TypeError. `undefined` and `null` mean no default.
|
|
77
|
+
*/
|
|
78
|
+
function declaredDefault(value, where) {
|
|
79
|
+
if (value === undefined || value === null)
|
|
80
|
+
return null;
|
|
81
|
+
if (typeof value === 'string')
|
|
82
|
+
return value;
|
|
83
|
+
if (typeof value === 'boolean')
|
|
84
|
+
return value ? 'true' : 'false';
|
|
85
|
+
if ((typeof value === 'number' && Number.isFinite(value)) || typeof value === 'bigint')
|
|
86
|
+
return String(value);
|
|
87
|
+
throw new ValidationError(`Invalid default for ${where}: received ${describeKind(value)}${typeof value === 'number' ? ` (${value})` : ''}. ` +
|
|
88
|
+
`Write it as a string of raw SQL such as 'now()' or "'active'", or as a finite number or a boolean.`);
|
|
89
|
+
}
|
|
59
90
|
/** Convert a user-facing ColumnDef to the internal ColumnConfig */
|
|
60
|
-
function resolveColumn(def) {
|
|
61
|
-
if (!(def.type
|
|
62
|
-
throw new
|
|
91
|
+
function resolveColumn(def, where = 'a column') {
|
|
92
|
+
if (!Object.hasOwn(TYPE_MAP, def.type)) {
|
|
93
|
+
throw new ValidationError(`Invalid column type "${def.type}". Valid types: ${Object.keys(TYPE_MAP).join(', ')}`);
|
|
63
94
|
}
|
|
64
95
|
if (def.type === 'enum' && !def.enumName) {
|
|
65
|
-
throw new
|
|
96
|
+
throw new ValidationError(`Column of type "enum" requires an "enumName" (the CREATE TYPE name).`);
|
|
66
97
|
}
|
|
67
98
|
if (def.type === 'vector' && (def.dimensions == null || def.dimensions <= 0)) {
|
|
68
|
-
throw new
|
|
99
|
+
throw new ValidationError(`Column of type "vector" requires a positive "dimensions" count.`);
|
|
69
100
|
}
|
|
70
101
|
// `references` is either the "table.column" string or a { target, onDelete, onUpdate } object.
|
|
71
102
|
let referencesTarget = null;
|
|
@@ -74,7 +105,11 @@ function resolveColumn(def) {
|
|
|
74
105
|
if (typeof def.references === 'string') {
|
|
75
106
|
referencesTarget = def.references;
|
|
76
107
|
}
|
|
77
|
-
else if (def.references) {
|
|
108
|
+
else if (def.references !== undefined && def.references !== null) {
|
|
109
|
+
if (typeof def.references !== 'object' || typeof def.references.target !== 'string') {
|
|
110
|
+
throw new ValidationError(`defineSchema: ${where} has a "references" that is neither a "table.column" string nor ` +
|
|
111
|
+
`{ target: "table.column", onDelete?, onUpdate? }.`);
|
|
112
|
+
}
|
|
78
113
|
referencesTarget = def.references.target;
|
|
79
114
|
onDelete = def.references.onDelete ?? null;
|
|
80
115
|
onUpdate = def.references.onUpdate ?? null;
|
|
@@ -85,7 +120,7 @@ function resolveColumn(def) {
|
|
|
85
120
|
isNotNull: def.notNull ?? false,
|
|
86
121
|
isNullable: def.nullable ?? false,
|
|
87
122
|
isUnique: def.unique ?? false,
|
|
88
|
-
defaultValue: def.default
|
|
123
|
+
defaultValue: declaredDefault(def.default, where),
|
|
89
124
|
referencesTarget,
|
|
90
125
|
maxLength: def.maxLength ?? null,
|
|
91
126
|
onDelete,
|
|
@@ -96,15 +131,306 @@ function resolveColumn(def) {
|
|
|
96
131
|
check: def.check ?? null,
|
|
97
132
|
pii: def.pii ?? false,
|
|
98
133
|
updatedAt: def.updatedAt ?? false,
|
|
134
|
+
tsType: checkedTsType(def.tsType),
|
|
99
135
|
};
|
|
100
136
|
}
|
|
137
|
+
/** Characters a declared type may not contain (the generator refuses the same set). */
|
|
138
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: refusing control characters is this pattern's purpose
|
|
139
|
+
const UNSAFE_TS_TYPE_TEXT = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;
|
|
140
|
+
/** Characters a type expression uses outside a string literal and a bracket. */
|
|
141
|
+
const TS_TYPE_CHAR = /^[\p{L}\p{N}_$.:?|&!+\-= \t]$/u;
|
|
142
|
+
const TS_TYPE_CLOSER = { '(': ')', '[': ']', '{': '}', '<': '>' };
|
|
143
|
+
/**
|
|
144
|
+
* Why `text` cannot be written into a generated file as a TypeScript type, or
|
|
145
|
+
* null when it can. The text lands verbatim as a member type or an alias body,
|
|
146
|
+
* so what has to hold is that it stays ONE type: brackets balance, nothing at
|
|
147
|
+
* the top level ends the member (`;` or `,`), and it holds no comment, template
|
|
148
|
+
* literal or character a type does not use. A `//` would swallow the rest of the
|
|
149
|
+
* line and a stray `}` would close the interface and start a statement.
|
|
150
|
+
*
|
|
151
|
+
* It does not check that the type is well formed or that its names exist; the
|
|
152
|
+
* compiler of the generated file does that.
|
|
153
|
+
*/
|
|
154
|
+
export function tsTypeTextProblem(text) {
|
|
155
|
+
if (text.trim() === '')
|
|
156
|
+
return 'it is empty';
|
|
157
|
+
if (UNSAFE_TS_TYPE_TEXT.test(text))
|
|
158
|
+
return 'it contains a control or line-separator character';
|
|
159
|
+
const chars = Array.from(text);
|
|
160
|
+
const open = [];
|
|
161
|
+
for (let i = 0; i < chars.length; i++) {
|
|
162
|
+
const c = chars[i];
|
|
163
|
+
if (c === "'" || c === '"') {
|
|
164
|
+
let end = i + 1;
|
|
165
|
+
while (end < chars.length && chars[end] !== c)
|
|
166
|
+
end += chars[end] === '\\' ? 2 : 1;
|
|
167
|
+
if (end >= chars.length)
|
|
168
|
+
return `a ${c} string literal is not closed`;
|
|
169
|
+
if (chars.slice(i, end).join('').includes('*/'))
|
|
170
|
+
return 'a string literal contains "*/"';
|
|
171
|
+
i = end;
|
|
172
|
+
}
|
|
173
|
+
else if (c === '=' && chars[i + 1] === '>') {
|
|
174
|
+
i++;
|
|
175
|
+
}
|
|
176
|
+
else if (TS_TYPE_CLOSER[c] !== undefined) {
|
|
177
|
+
open.push(TS_TYPE_CLOSER[c]);
|
|
178
|
+
}
|
|
179
|
+
else if (c === ')' || c === ']' || c === '}' || c === '>') {
|
|
180
|
+
if (open.pop() !== c)
|
|
181
|
+
return `"${c}" closes a bracket that was never opened`;
|
|
182
|
+
}
|
|
183
|
+
else if (c === ';' || c === ',') {
|
|
184
|
+
if (open.length === 0)
|
|
185
|
+
return `"${c}" outside any brackets would end the type`;
|
|
186
|
+
}
|
|
187
|
+
else if (c === '`') {
|
|
188
|
+
return 'a template literal type is not supported here, name it with a type alias and use import()';
|
|
189
|
+
}
|
|
190
|
+
else if (!TS_TYPE_CHAR.test(c)) {
|
|
191
|
+
return `"${c}" cannot appear in a type${c === '/' ? ' (no comments)' : ''}`;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return open.length > 0 ? 'a bracket is not closed' : null;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* A `tsType` is emitted verbatim into generated source, so it must be one type
|
|
198
|
+
* on one line (see {@link tsTypeTextProblem}).
|
|
199
|
+
*/
|
|
200
|
+
function checkedTsType(tsType) {
|
|
201
|
+
if (tsType === undefined)
|
|
202
|
+
return null;
|
|
203
|
+
const problem = typeof tsType === 'string' ? tsTypeTextProblem(tsType) : 'it is not a string';
|
|
204
|
+
if (problem !== null) {
|
|
205
|
+
throw new ValidationError(`Invalid column "tsType" ${JSON.stringify(tsType)}: ${problem}. ` +
|
|
206
|
+
`Give one TypeScript type on one line, for example '{ theme: string }' or "import('../src/types.js').Settings".`);
|
|
207
|
+
}
|
|
208
|
+
return tsType;
|
|
209
|
+
}
|
|
101
210
|
/** Type guard: is this index declaration a doc-field expression index? */
|
|
102
211
|
export function isDocFieldIndexDef(idx) {
|
|
103
212
|
return 'docField' in idx && typeof idx.docField === 'string';
|
|
104
213
|
}
|
|
105
|
-
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
// Known keys: one table per declaration shape
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
//
|
|
218
|
+
// A schema is static code, and a key `defineSchema` does not know is not
|
|
219
|
+
// harmless: it is an option the caller meant and did not get (`defaultValue`
|
|
220
|
+
// for `default` emits a column with no DEFAULT, `uniq` for `unique` a
|
|
221
|
+
// non-unique index). So an unknown key is refused, at compile time in a
|
|
222
|
+
// literal and at runtime for JSON or plain JS.
|
|
223
|
+
//
|
|
224
|
+
// Each table is a `Record<keyof Shape, true>`: adding an option to the shape
|
|
225
|
+
// without listing it here fails the build, and listing a key the shape does
|
|
226
|
+
// not have fails as an excess property. The compile-time check types below
|
|
227
|
+
// and the runtime refusal both read these, so they cannot disagree.
|
|
228
|
+
const COLUMN_KEYS = {
|
|
229
|
+
type: true,
|
|
230
|
+
primaryKey: true,
|
|
231
|
+
notNull: true,
|
|
232
|
+
nullable: true,
|
|
233
|
+
unique: true,
|
|
234
|
+
default: true,
|
|
235
|
+
references: true,
|
|
236
|
+
maxLength: true,
|
|
237
|
+
enumName: true,
|
|
238
|
+
dimensions: true,
|
|
239
|
+
array: true,
|
|
240
|
+
check: true,
|
|
241
|
+
pii: true,
|
|
242
|
+
updatedAt: true,
|
|
243
|
+
tsType: true,
|
|
244
|
+
};
|
|
245
|
+
const REFERENCE_KEYS = { target: true, onDelete: true, onUpdate: true };
|
|
246
|
+
const CHECK_KEYS = { name: true, expression: true };
|
|
247
|
+
const MANY_TO_MANY_KEYS = {
|
|
248
|
+
name: true,
|
|
249
|
+
target: true,
|
|
250
|
+
through: true,
|
|
251
|
+
sourceKey: true,
|
|
252
|
+
targetKey: true,
|
|
253
|
+
references: true,
|
|
254
|
+
};
|
|
255
|
+
const INDEX_KEYS = {
|
|
256
|
+
columns: true,
|
|
257
|
+
docField: true,
|
|
258
|
+
path: true,
|
|
259
|
+
unique: true,
|
|
260
|
+
name: true,
|
|
261
|
+
};
|
|
262
|
+
const TABLE_OPTION_KEYS = {
|
|
263
|
+
primaryKey: true,
|
|
264
|
+
manyToMany: true,
|
|
265
|
+
checks: true,
|
|
266
|
+
indexes: true,
|
|
267
|
+
};
|
|
268
|
+
const SCHEMA_OPTION_KEYS = { enums: true };
|
|
269
|
+
/**
|
|
270
|
+
* Refuse every key of `value` that `known` does not list (E003), naming where
|
|
271
|
+
* it sits and the key it most likely meant.
|
|
272
|
+
*/
|
|
273
|
+
function assertKnownKeys(value, known, where) {
|
|
274
|
+
for (const key of Object.keys(value)) {
|
|
275
|
+
if (Object.hasOwn(known, key))
|
|
276
|
+
continue;
|
|
277
|
+
// suggestKey covers typos and the metadata spellings (`defaultValue`,
|
|
278
|
+
// `isUnique`) alike: the second pass matches a key that adds words.
|
|
279
|
+
const suggestion = suggestKey(key, Object.keys(known));
|
|
280
|
+
throw new ValidationError(`defineSchema: unknown key "${key}" in ${where}.` +
|
|
281
|
+
(suggestion ? ` Did you mean "${suggestion}"?` : '') +
|
|
282
|
+
` Valid keys: ${Object.keys(known).join(', ')}.`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/** Each element of an option array, checked as `what` (non-objects are left to the consumer's own checks). */
|
|
286
|
+
function assertKnownElementKeys(list, known, where) {
|
|
287
|
+
list.forEach((item, i) => {
|
|
288
|
+
if (typeof item === 'object' && item !== null)
|
|
289
|
+
assertKnownKeys(item, known, `${where}[${i}]`);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Refuse `__proto__` as a table or column name. `JSON.parse` can produce it as
|
|
294
|
+
* an own key, but assigning to it on a record sets the record's prototype
|
|
295
|
+
* instead of adding a key, so the column would vanish without a word.
|
|
296
|
+
*/
|
|
297
|
+
function assertOrdinaryName(name, what) {
|
|
298
|
+
if (name === '__proto__') {
|
|
299
|
+
throw new ValidationError(`defineSchema: ${what} is named "__proto__", which JavaScript treats as the prototype setter rather than a ` +
|
|
300
|
+
`property name, so it could not be stored or queried. Rename it.`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* An index is either a column index (`columns`) or a document-field index
|
|
305
|
+
* (`docField` and `path`), never both. A declaration carrying both was taken
|
|
306
|
+
* for the document-field kind, which only PowDB creates, so the SQL engines
|
|
307
|
+
* skipped it without saying so.
|
|
308
|
+
*/
|
|
309
|
+
function assertIndexKinds(list, accessor) {
|
|
310
|
+
list.forEach((item, i) => {
|
|
311
|
+
if (typeof item === 'object' && item !== null && 'columns' in item && 'docField' in item) {
|
|
312
|
+
throw new ValidationError(`defineSchema: index ${i} of table "${accessor}" declares both "columns" and "docField". ` +
|
|
313
|
+
`Use "columns" for a column index, or "docField" with "path" for an index into a json document. ` +
|
|
314
|
+
`Declare two indexes if you need both.`);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Is this value a TableDef (the legacy `table()` builder's output)?
|
|
320
|
+
*
|
|
321
|
+
* A TableDef carries a STRING `name` and a `columns` record of column configs.
|
|
322
|
+
* In the object format a key called `name` or `columns` is a column, and a
|
|
323
|
+
* column definition is an object, so the string is what tells the two apart: a
|
|
324
|
+
* table with columns named `name` and `columns` used to be read as a TableDef
|
|
325
|
+
* and emitted as a different table. A table that has a string `name` but is not
|
|
326
|
+
* a TableDef falls through to the object format, which refuses the string.
|
|
327
|
+
*/
|
|
106
328
|
function isTableDef(v) {
|
|
107
|
-
|
|
329
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v))
|
|
330
|
+
return false;
|
|
331
|
+
const t = v;
|
|
332
|
+
if (typeof t.name !== 'string')
|
|
333
|
+
return false;
|
|
334
|
+
const columns = t.columns;
|
|
335
|
+
if (typeof columns !== 'object' || columns === null || Array.isArray(columns))
|
|
336
|
+
return false;
|
|
337
|
+
return Object.values(columns).every((c) => typeof c === 'object' && c !== null);
|
|
338
|
+
}
|
|
339
|
+
/** A name list for a message: `a, b, c`, or `(none)`. */
|
|
340
|
+
function nameList(names) {
|
|
341
|
+
const list = [...names];
|
|
342
|
+
return list.length > 0 ? list.join(', ') : '(none)';
|
|
343
|
+
}
|
|
344
|
+
/** The DDL column name of a field: what `CREATE TABLE` and every index write for it. */
|
|
345
|
+
const ddlColumnName = camelToSnakeLocal;
|
|
346
|
+
/**
|
|
347
|
+
* Refuse a schema that names something it does not contain. Every one of these
|
|
348
|
+
* used to be accepted and then went wrong somewhere else, or nowhere:
|
|
349
|
+
*
|
|
350
|
+
* - `userId` and `user_id` in one table are the SAME column to the database, so
|
|
351
|
+
* the DDL declared it twice.
|
|
352
|
+
* - A `references` that is not `table.column` was dropped (no foreign key, no
|
|
353
|
+
* error), and one naming a column its target table does not have became a
|
|
354
|
+
* `REFERENCES` clause the database rejects at push time. A target table that
|
|
355
|
+
* is not in this schema is left alone, since it may be owned elsewhere.
|
|
356
|
+
* - An index on a column the table does not have failed the same way.
|
|
357
|
+
*
|
|
358
|
+
* It runs once, after every table is built, because a reference may name a
|
|
359
|
+
* table declared later.
|
|
360
|
+
*/
|
|
361
|
+
function assertSchemaIntegrity(tables) {
|
|
362
|
+
const byDdlName = new Map();
|
|
363
|
+
const byName = new Map();
|
|
364
|
+
for (const [accessor, table] of Object.entries(tables)) {
|
|
365
|
+
const owner = byDdlName.get(table.name);
|
|
366
|
+
if (owner !== undefined) {
|
|
367
|
+
throw new ValidationError(`defineSchema: tables "${owner}" and "${accessor}" are both the table "${table.name}" in the database ` +
|
|
368
|
+
`(a camelCase name is written in snake_case). Keep one, or rename one.`);
|
|
369
|
+
}
|
|
370
|
+
byDdlName.set(table.name, accessor);
|
|
371
|
+
byName.set(accessor, table);
|
|
372
|
+
byName.set(table.name, table);
|
|
373
|
+
}
|
|
374
|
+
for (const [accessor, table] of Object.entries(tables)) {
|
|
375
|
+
const columnOf = new Map();
|
|
376
|
+
for (const field of Object.keys(table.columns)) {
|
|
377
|
+
const ddl = ddlColumnName(field);
|
|
378
|
+
const other = columnOf.get(ddl);
|
|
379
|
+
if (other !== undefined) {
|
|
380
|
+
throw new ValidationError(`defineSchema: table "${accessor}" has columns "${other}" and "${field}", which are the same column ` +
|
|
381
|
+
`"${ddl}" in the database. Keep one.`);
|
|
382
|
+
}
|
|
383
|
+
columnOf.set(ddl, field);
|
|
384
|
+
}
|
|
385
|
+
const hasColumn = (name) => Object.hasOwn(table.columns, name) || columnOf.has(name);
|
|
386
|
+
const columnNames = () => Object.keys(table.columns);
|
|
387
|
+
for (const [i, idx] of (table.indexes ?? []).entries()) {
|
|
388
|
+
if (!isDocFieldIndexDef(idx) && !Array.isArray(idx.columns)) {
|
|
389
|
+
throw new ValidationError(`defineSchema: index ${i} of table "${accessor}" declares neither "columns" (an array of column names) ` +
|
|
390
|
+
`nor "docField" with "path".`);
|
|
391
|
+
}
|
|
392
|
+
const named = isDocFieldIndexDef(idx) ? [idx.docField] : idx.columns;
|
|
393
|
+
for (const name of named) {
|
|
394
|
+
if (typeof name === 'string' && hasColumn(name))
|
|
395
|
+
continue;
|
|
396
|
+
const suggestion = typeof name === 'string' ? suggestKey(name, columnNames()) : null;
|
|
397
|
+
throw new ValidationError(`defineSchema: index ${i} of table "${accessor}" names ${typeof name === 'string' ? `column "${name}"` : `a ${describeKind(name)} instead of a column name`}, ` +
|
|
398
|
+
`which the table does not have.${suggestion ? ` Did you mean "${suggestion}"?` : ''} ` +
|
|
399
|
+
`Columns: ${nameList(columnNames())}.`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
for (const [field, config] of Object.entries(table.columns)) {
|
|
403
|
+
if (config.referencesTarget === null || config.referencesTarget === undefined)
|
|
404
|
+
continue;
|
|
405
|
+
const where = `column "${accessor}.${field}"`;
|
|
406
|
+
const target = config.referencesTarget;
|
|
407
|
+
const parts = typeof target === 'string' ? target.split('.') : [];
|
|
408
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
409
|
+
throw new ValidationError(`defineSchema: ${where} has references ${JSON.stringify(target)}, which is not in "table.column" form, so no foreign ` +
|
|
410
|
+
`key would be created. Write it as "users.id".` +
|
|
411
|
+
(parts.length > 2 ? ' A schema-qualified name ("auth.users.id") is not supported.' : ''));
|
|
412
|
+
}
|
|
413
|
+
const [refTable, refColumn] = parts;
|
|
414
|
+
// A table outside this schema is allowed, since something else may own
|
|
415
|
+
// it. Its columns cannot be checked here.
|
|
416
|
+
const targetTable = byName.get(refTable);
|
|
417
|
+
if (!targetTable)
|
|
418
|
+
continue;
|
|
419
|
+
const targetDdl = new Set(Object.keys(targetTable.columns).map(ddlColumnName));
|
|
420
|
+
const isField = Object.hasOwn(targetTable.columns, refColumn);
|
|
421
|
+
if (!isField && !targetDdl.has(refColumn)) {
|
|
422
|
+
const suggestion = suggestKey(refColumn, Object.keys(targetTable.columns));
|
|
423
|
+
throw new ValidationError(`defineSchema: ${where} references "${target}", but table "${refTable}" has no column "${refColumn}".` +
|
|
424
|
+
(suggestion ? ` Did you mean "${suggestion}"?` : '') +
|
|
425
|
+
` Columns: ${nameList(Object.keys(targetTable.columns))}.`);
|
|
426
|
+
}
|
|
427
|
+
// The DDL names the column as written, so a field name (`displayName`)
|
|
428
|
+
// becomes the column it is (`display_name`) here, where the table is known.
|
|
429
|
+
if (isField && ddlColumnName(refColumn) !== refColumn) {
|
|
430
|
+
config.referencesTarget = `${refTable}.${ddlColumnName(refColumn)}`;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
108
434
|
}
|
|
109
435
|
/**
|
|
110
436
|
* Define the full database schema using plain objects.
|
|
@@ -127,7 +453,10 @@ function isTableDef(v) {
|
|
|
127
453
|
*/
|
|
128
454
|
export function defineSchema(input, options) {
|
|
129
455
|
const tables = {};
|
|
456
|
+
if (options !== undefined)
|
|
457
|
+
assertKnownKeys(options, SCHEMA_OPTION_KEYS, 'the defineSchema options');
|
|
130
458
|
for (const [accessor, value] of Object.entries(input)) {
|
|
459
|
+
assertOrdinaryName(accessor, `table "${accessor}"`);
|
|
131
460
|
// The user-facing key is the camelCase JS accessor; the DDL-facing
|
|
132
461
|
// table name is its snake_case form (e.g. `postTags` → `post_tags`).
|
|
133
462
|
const dbName = camelToSnakeLocal(accessor);
|
|
@@ -150,8 +479,10 @@ export function defineSchema(input, options) {
|
|
|
150
479
|
if (fieldName === 'indexes') {
|
|
151
480
|
if (def !== undefined) {
|
|
152
481
|
if (!Array.isArray(def)) {
|
|
153
|
-
throw new
|
|
482
|
+
throw new ValidationError(`Table "${accessor}": "indexes" must be an array of index declarations`);
|
|
154
483
|
}
|
|
484
|
+
assertKnownElementKeys(def, INDEX_KEYS, `table "${accessor}" indexes`);
|
|
485
|
+
assertIndexKinds(def, accessor);
|
|
155
486
|
indexes = def;
|
|
156
487
|
}
|
|
157
488
|
continue;
|
|
@@ -159,8 +490,9 @@ export function defineSchema(input, options) {
|
|
|
159
490
|
if (fieldName === 'manyToMany') {
|
|
160
491
|
if (def !== undefined) {
|
|
161
492
|
if (!Array.isArray(def)) {
|
|
162
|
-
throw new
|
|
493
|
+
throw new ValidationError(`Table "${accessor}": "manyToMany" must be an array of relation declarations`);
|
|
163
494
|
}
|
|
495
|
+
assertKnownElementKeys(def, MANY_TO_MANY_KEYS, `table "${accessor}" manyToMany`);
|
|
164
496
|
m2m = def;
|
|
165
497
|
}
|
|
166
498
|
continue;
|
|
@@ -168,8 +500,9 @@ export function defineSchema(input, options) {
|
|
|
168
500
|
if (fieldName === 'checks') {
|
|
169
501
|
if (def !== undefined) {
|
|
170
502
|
if (!Array.isArray(def)) {
|
|
171
|
-
throw new
|
|
503
|
+
throw new ValidationError(`Table "${accessor}": "checks" must be an array of { name?, expression } objects`);
|
|
172
504
|
}
|
|
505
|
+
assertKnownElementKeys(def, CHECK_KEYS, `table "${accessor}" checks`);
|
|
173
506
|
checks = def;
|
|
174
507
|
}
|
|
175
508
|
continue;
|
|
@@ -178,14 +511,35 @@ export function defineSchema(input, options) {
|
|
|
178
511
|
// Top-level composite primary key declaration
|
|
179
512
|
if (def !== undefined) {
|
|
180
513
|
if (!Array.isArray(def)) {
|
|
181
|
-
throw new
|
|
514
|
+
throw new ValidationError(`Table "${accessor}": top-level "primaryKey" must be an array of column names (string[])`);
|
|
182
515
|
}
|
|
183
516
|
pk = def;
|
|
184
517
|
}
|
|
185
518
|
continue;
|
|
186
519
|
}
|
|
187
|
-
// Anything else is a ColumnDef
|
|
188
|
-
|
|
520
|
+
// Anything else is a ColumnDef. An array here is a table option under a
|
|
521
|
+
// name defineSchema does not know (`primarykey`, `index`): refuse it by
|
|
522
|
+
// that name rather than as a column with no type.
|
|
523
|
+
if (Array.isArray(def)) {
|
|
524
|
+
const suggestion = suggestKey(fieldName, Object.keys(TABLE_OPTION_KEYS));
|
|
525
|
+
throw new ValidationError(`defineSchema: table "${accessor}" has an array under "${fieldName}", which is not a table option ` +
|
|
526
|
+
`(${Object.keys(TABLE_OPTION_KEYS).join(', ')}) and cannot be a column.` +
|
|
527
|
+
(suggestion ? ` Did you mean "${suggestion}"?` : ''));
|
|
528
|
+
}
|
|
529
|
+
assertOrdinaryName(fieldName, `column "${accessor}.${fieldName}"`);
|
|
530
|
+
// An `undefined` column is absent, like an `undefined` option anywhere else.
|
|
531
|
+
if (def === undefined)
|
|
532
|
+
continue;
|
|
533
|
+
if (typeof def !== 'object' || def === null) {
|
|
534
|
+
throw new ValidationError(`defineSchema: column "${accessor}.${fieldName}" must be an object such as { type: 'text' }, ` +
|
|
535
|
+
`received ${describeKind(def)}.`);
|
|
536
|
+
}
|
|
537
|
+
assertKnownKeys(def, COLUMN_KEYS, `column "${accessor}.${fieldName}"`);
|
|
538
|
+
const ref = def.references;
|
|
539
|
+
if (typeof ref === 'object' && ref !== null) {
|
|
540
|
+
assertKnownKeys(ref, REFERENCE_KEYS, `the references of column "${accessor}.${fieldName}"`);
|
|
541
|
+
}
|
|
542
|
+
columns[fieldName] = resolveColumn(def, `column "${accessor}.${fieldName}"`);
|
|
189
543
|
}
|
|
190
544
|
// Validate composite PK references real columns and clear column-level PKs
|
|
191
545
|
// for those columns so we don't double-emit `PRIMARY KEY` clauses.
|
|
@@ -194,7 +548,7 @@ export function defineSchema(input, options) {
|
|
|
194
548
|
if (pk && pk.length > 0) {
|
|
195
549
|
for (const colName of pk) {
|
|
196
550
|
if (!(colName in columns)) {
|
|
197
|
-
throw new
|
|
551
|
+
throw new ValidationError(`Table "${accessor}": composite primaryKey references unknown column "${colName}". ` +
|
|
198
552
|
`Known columns: ${Object.keys(columns).join(', ') || '(none)'}`);
|
|
199
553
|
}
|
|
200
554
|
// A composite PK at the table level supersedes any column-level
|
|
@@ -219,6 +573,7 @@ export function defineSchema(input, options) {
|
|
|
219
573
|
};
|
|
220
574
|
}
|
|
221
575
|
}
|
|
576
|
+
assertSchemaIntegrity(tables);
|
|
222
577
|
return { tables, ...(options?.enums ? { enums: options.enums } : {}) };
|
|
223
578
|
}
|
|
224
579
|
/**
|
|
@@ -251,6 +606,7 @@ export class ColumnBuilder {
|
|
|
251
606
|
check: null,
|
|
252
607
|
pii: false,
|
|
253
608
|
updatedAt: false,
|
|
609
|
+
tsType: null,
|
|
254
610
|
};
|
|
255
611
|
}
|
|
256
612
|
serial() {
|
|
@@ -344,10 +700,12 @@ export class ColumnBuilder {
|
|
|
344
700
|
return this;
|
|
345
701
|
}
|
|
346
702
|
default(val) {
|
|
347
|
-
this._config.defaultValue = val;
|
|
703
|
+
this._config.defaultValue = declaredDefault(val, 'ColumnBuilder.default()');
|
|
348
704
|
return this;
|
|
349
705
|
}
|
|
350
706
|
references(target, opts) {
|
|
707
|
+
if (opts !== undefined)
|
|
708
|
+
assertKnownKeys(opts, { onDelete: true, onUpdate: true }, `references("${target}") options`);
|
|
351
709
|
this._config.referencesTarget = target;
|
|
352
710
|
this._config.onDelete = opts?.onDelete ?? null;
|
|
353
711
|
this._config.onUpdate = opts?.onUpdate ?? null;
|
|
@@ -366,6 +724,11 @@ export class ColumnBuilder {
|
|
|
366
724
|
this._config.updatedAt = true;
|
|
367
725
|
return this;
|
|
368
726
|
}
|
|
727
|
+
/** The TypeScript type `turbine generate` emits for this column. See {@link ColumnDef.tsType}. */
|
|
728
|
+
tsType(type) {
|
|
729
|
+
this._config.tsType = checkedTsType(type);
|
|
730
|
+
return this;
|
|
731
|
+
}
|
|
369
732
|
array() {
|
|
370
733
|
this._config.isArray = true;
|
|
371
734
|
return this;
|
|
@@ -406,7 +769,7 @@ export const column = new Proxy({}, {
|
|
|
406
769
|
};
|
|
407
770
|
}
|
|
408
771
|
return () => {
|
|
409
|
-
throw new
|
|
772
|
+
throw new ValidationError(`Unknown column type: ${prop}`);
|
|
410
773
|
};
|
|
411
774
|
},
|
|
412
775
|
});
|
|
@@ -45,7 +45,27 @@
|
|
|
45
45
|
* ```
|
|
46
46
|
*/
|
|
47
47
|
import { type SchemaMetadata } from './schema.js';
|
|
48
|
-
import { type SchemaDef } from './schema-builder.js';
|
|
48
|
+
import { type ColumnConfig, type SchemaDef } from './schema-builder.js';
|
|
49
|
+
/** The code-first-only flags a column can declare. */
|
|
50
|
+
export interface CodeFirstColumnTags {
|
|
51
|
+
pii?: true;
|
|
52
|
+
updatedAt?: true;
|
|
53
|
+
maxLength?: number;
|
|
54
|
+
/**
|
|
55
|
+
* The declared TypeScript type, WITHOUT nullability. On metadata built by
|
|
56
|
+
* {@link schemaDefToMetadata} it is the column's `declaredTsType`, never its
|
|
57
|
+
* `tsType`; `turbine generate` passes it to the generator as a `columnTypes`
|
|
58
|
+
* entry.
|
|
59
|
+
*/
|
|
60
|
+
tsType?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The tags a column declares, as the fields they take on {@link ColumnMetadata}.
|
|
64
|
+
* The one place the tag set is listed, so `schemaDefToMetadata` and the CLI's
|
|
65
|
+
* overlay of these tags onto an introspected schema (`cli/code-first-tags.ts`)
|
|
66
|
+
* cannot disagree about what counts as one.
|
|
67
|
+
*/
|
|
68
|
+
export declare function declaredTagsOf(config: ColumnConfig): CodeFirstColumnTags;
|
|
49
69
|
/**
|
|
50
70
|
* Convert a code-first {@link SchemaDef} into runtime {@link SchemaMetadata}.
|
|
51
71
|
*
|
package/dist/schema-metadata.js
CHANGED
|
@@ -157,6 +157,20 @@ function mapIndexes(tableDef, declared) {
|
|
|
157
157
|
}
|
|
158
158
|
return out;
|
|
159
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* The tags a column declares, as the fields they take on {@link ColumnMetadata}.
|
|
162
|
+
* The one place the tag set is listed, so `schemaDefToMetadata` and the CLI's
|
|
163
|
+
* overlay of these tags onto an introspected schema (`cli/code-first-tags.ts`)
|
|
164
|
+
* cannot disagree about what counts as one.
|
|
165
|
+
*/
|
|
166
|
+
export function declaredTagsOf(config) {
|
|
167
|
+
return {
|
|
168
|
+
...(config.pii ? { pii: true } : {}),
|
|
169
|
+
...(config.updatedAt ? { updatedAt: true } : {}),
|
|
170
|
+
...(config.maxLength != null ? { maxLength: config.maxLength } : {}),
|
|
171
|
+
...(config.tsType != null ? { tsType: config.tsType } : {}),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
160
174
|
// ---------------------------------------------------------------------------
|
|
161
175
|
// The converter
|
|
162
176
|
// ---------------------------------------------------------------------------
|
|
@@ -325,23 +339,30 @@ export function schemaDefToMetadata(def) {
|
|
|
325
339
|
const serial = isSerialType(config.type);
|
|
326
340
|
// PK members and serials are NOT NULL in Postgres regardless of flags.
|
|
327
341
|
const nullable = !(config.isPrimaryKey || config.isNotNull || serial);
|
|
342
|
+
const { tsType: declaredTsType, ...declaredTags } = declaredTagsOf(config);
|
|
328
343
|
const col = {
|
|
329
344
|
name,
|
|
330
345
|
field,
|
|
331
346
|
dialectType: wireType,
|
|
332
347
|
pgType: wireType,
|
|
333
|
-
tsType: pgTypeToTs(wireType, nullable),
|
|
334
348
|
nullable,
|
|
335
349
|
hasDefault: config.defaultValue != null || serial,
|
|
350
|
+
// The expression itself, as written in the schema (`now()`, `'draft'`).
|
|
351
|
+
// Engines with no column defaults (PowDB) apply it client-side on
|
|
352
|
+
// create, which they cannot do from `hasDefault` alone.
|
|
353
|
+
...(config.defaultValue != null ? { defaultValue: config.defaultValue } : {}),
|
|
336
354
|
isArray: config.isArray,
|
|
337
355
|
arrayType: pgArrayType(base),
|
|
338
356
|
pgArrayType: pgArrayType(base),
|
|
339
357
|
...(serial ? { isGenerated: true } : {}),
|
|
340
|
-
// PII
|
|
341
|
-
//
|
|
342
|
-
// Only set when
|
|
343
|
-
|
|
344
|
-
...
|
|
358
|
+
// PII, updatedAt and maxLength are code-first declarations; carry them
|
|
359
|
+
// through so query-layer projection, the updatedAt stamp and Studio
|
|
360
|
+
// redaction can honor them. Only set when declared, keeping output
|
|
361
|
+
// byte-stable for untagged schemas.
|
|
362
|
+
...declaredTags,
|
|
363
|
+
// Always the mapped type. A declared `tsType` rides along separately.
|
|
364
|
+
tsType: pgTypeToTs(wireType, nullable),
|
|
365
|
+
...(declaredTsType !== undefined ? { declaredTsType } : {}),
|
|
345
366
|
};
|
|
346
367
|
columns.push(col);
|
|
347
368
|
columnMap[field] = name;
|
|
@@ -370,7 +391,7 @@ export function schemaDefToMetadata(def) {
|
|
|
370
391
|
allColumns,
|
|
371
392
|
primaryKey: pk,
|
|
372
393
|
uniqueColumns,
|
|
373
|
-
relations: relationsByTable.get(tableDef.name)
|
|
394
|
+
relations: { ...relationsByTable.get(tableDef.name) },
|
|
374
395
|
// Declared `indexes` (plain column + doc-field expression) carry through;
|
|
375
396
|
// an undeclared table gets `[]`, which keeps schemaHasIndexInfo() false so
|
|
376
397
|
// the index advisor stays silent. Doc-field (docPath) indexes are ignored
|