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