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/cjs/generate.js
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* Output goes to the specified directory (default: ./generated/turbine/).
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.columnTsType = columnTsType;
|
|
14
|
+
exports.resolveColumnTypeOverrides = resolveColumnTypeOverrides;
|
|
13
15
|
exports.generate = generate;
|
|
14
16
|
exports.resolveImportExtension = resolveImportExtension;
|
|
15
17
|
exports.detectTsconfigExtension = detectTsconfigExtension;
|
|
@@ -26,18 +28,28 @@ const node_fs_1 = require("node:fs");
|
|
|
26
28
|
const node_path_1 = require("node:path");
|
|
27
29
|
const errors_js_1 = require("./errors.js");
|
|
28
30
|
const introspect_js_1 = require("./introspect.js");
|
|
31
|
+
const logger_js_1 = require("./logger.js");
|
|
32
|
+
const utils_js_1 = require("./query/utils.js");
|
|
29
33
|
const schema_js_1 = require("./schema.js");
|
|
34
|
+
const schema_builder_js_1 = require("./schema-builder.js");
|
|
30
35
|
/** Get the TypeScript type name for a table (singularized PascalCase) */
|
|
31
36
|
function entityName(tableName) {
|
|
32
37
|
return (0, schema_js_1.snakeToPascal)((0, schema_js_1.singularize)(tableName));
|
|
33
38
|
}
|
|
39
|
+
/** The override for one column, or `undefined`. */
|
|
40
|
+
function overrideFor(overrides, table, column) {
|
|
41
|
+
return overrides?.get(table)?.get(column);
|
|
42
|
+
}
|
|
34
43
|
/**
|
|
35
44
|
* Resolve the TypeScript type for a column, mapping enum-typed columns to their
|
|
36
45
|
* generated string-literal union (PascalCase enum name) instead of the
|
|
37
|
-
* `unknown` that {@link pgTypeToTs} yields for user-defined types.
|
|
38
|
-
* the
|
|
46
|
+
* `unknown` that {@link pgTypeToTs} yields for user-defined types. A
|
|
47
|
+
* `columnTypes` override replaces the type outright (nullability is still
|
|
48
|
+
* added). Falls back to the introspected `col.tsType` for everything else.
|
|
39
49
|
*/
|
|
40
|
-
function columnTsType(col, enums) {
|
|
50
|
+
function columnTsType(col, enums, override) {
|
|
51
|
+
if (override !== undefined)
|
|
52
|
+
return col.nullable ? `${override} | null` : override;
|
|
41
53
|
const dt = col.dialectType ?? col.pgType;
|
|
42
54
|
const isArray = col.isArray || dt.startsWith('_');
|
|
43
55
|
const base = isArray && dt.startsWith('_') ? dt.slice(1) : dt;
|
|
@@ -51,27 +63,78 @@ function columnTsType(col, enums) {
|
|
|
51
63
|
}
|
|
52
64
|
/**
|
|
53
65
|
* Resolve the TypeScript type a column accepts on the WRITE path (the `*Create`
|
|
54
|
-
* / `*Update` input types)
|
|
55
|
-
*
|
|
66
|
+
* / `*Update` input types). It differs from the row type where the runtime
|
|
67
|
+
* accepts more than one spelling or cannot take the parsed read value back
|
|
68
|
+
* ({@link pgTypeToTsInput}), and for time-of-day columns.
|
|
56
69
|
*
|
|
57
70
|
* A `time` / `timetz` column reads back as a `string` (`'09:00:00'`), so the
|
|
58
71
|
* row type stays `string`. On write the runtime also accepts a JS `Date` and
|
|
59
72
|
* narrows it to its UTC time of day (see `coerceWriteValue` in
|
|
60
73
|
* query/writes.ts), matching Prisma, which types a `DateTime @db.Time(6)`
|
|
61
|
-
* field as a `Date`.
|
|
62
|
-
* type has to admit them. Every other column is unchanged.
|
|
74
|
+
* field as a `Date`.
|
|
63
75
|
*/
|
|
64
|
-
function writeColumnTsType(col, enums) {
|
|
65
|
-
const tsType = columnTsType(col, enums);
|
|
76
|
+
function writeColumnTsType(col, enums, override) {
|
|
77
|
+
const tsType = columnTsType(col, enums, override);
|
|
78
|
+
if (override !== undefined)
|
|
79
|
+
return tsType;
|
|
66
80
|
const dt = col.dialectType ?? col.pgType;
|
|
67
81
|
const isArray = col.isArray || dt.startsWith('_');
|
|
68
82
|
const base = isArray && dt.startsWith('_') ? dt.slice(1) : dt;
|
|
69
|
-
if (
|
|
83
|
+
if (Object.hasOwn(enums, base))
|
|
70
84
|
return tsType;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
85
|
+
if ((0, schema_js_1.timeOfDayKind)(base)) {
|
|
86
|
+
// A `time[]` column accepts a Date per element (the bind rewrite runs
|
|
87
|
+
// element-wise, see coerceTemporalValue in query/utils.ts).
|
|
88
|
+
const widened = isArray ? '(string | Date)[]' : 'string | Date';
|
|
89
|
+
return col.nullable ? `${widened} | null` : widened;
|
|
90
|
+
}
|
|
91
|
+
// Only rewrite a type the read map produced: hand-built or engine metadata
|
|
92
|
+
// carrying its own tsType keeps it.
|
|
93
|
+
const pgType = isArray && !dt.startsWith('_') ? `_${dt}` : dt;
|
|
94
|
+
const read = (0, schema_js_1.pgTypeToTs)(pgType, col.nullable);
|
|
95
|
+
const input = (0, schema_js_1.pgTypeToTsInput)(pgType, col.nullable);
|
|
96
|
+
return input !== read && tsType === read ? input : tsType;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Validate {@link GenerateOptions.columnTypes} against the schema and index it.
|
|
100
|
+
* A key naming no table or column is a `ValidationError`, not a silent no-op,
|
|
101
|
+
* since an override that applies to nothing leaves the column typed `unknown`.
|
|
102
|
+
*/
|
|
103
|
+
function resolveColumnTypeOverrides(schema, columnTypes) {
|
|
104
|
+
if (columnTypes === undefined)
|
|
105
|
+
return undefined;
|
|
106
|
+
const out = new Map();
|
|
107
|
+
for (const [key, tsType] of Object.entries(columnTypes)) {
|
|
108
|
+
const dot = key.lastIndexOf('.');
|
|
109
|
+
const tableName = dot > 0 ? key.slice(0, dot) : '';
|
|
110
|
+
const columnName = dot > 0 ? key.slice(dot + 1) : '';
|
|
111
|
+
if (!tableName || !columnName) {
|
|
112
|
+
throw new errors_js_1.ValidationError(`columnTypes key ${(0, introspect_js_1.printableCatalogName)(JSON.stringify(key))} must be "<table>.<column>", using database names, ` +
|
|
113
|
+
'for example "users.settings".');
|
|
114
|
+
}
|
|
115
|
+
const table = Object.hasOwn(schema.tables, tableName) ? schema.tables[tableName] : undefined;
|
|
116
|
+
if (!table) {
|
|
117
|
+
const hint = (0, utils_js_1.closestName)(tableName, Object.keys(schema.tables));
|
|
118
|
+
throw new errors_js_1.ValidationError(`columnTypes key ${(0, introspect_js_1.printableCatalogName)(JSON.stringify(key))} names no table${hint ? `. Did you mean "${(0, introspect_js_1.printableCatalogName)(hint)}"?` : '.'}`);
|
|
119
|
+
}
|
|
120
|
+
if (!table.columns.some((c) => c.name === columnName)) {
|
|
121
|
+
const hint = (0, utils_js_1.closestName)(columnName, table.columns.map((c) => c.name));
|
|
122
|
+
throw new errors_js_1.ValidationError(`columnTypes key ${(0, introspect_js_1.printableCatalogName)(JSON.stringify(key))} names no column of "${(0, introspect_js_1.printableCatalogName)(tableName)}"` +
|
|
123
|
+
`${hint ? `. Did you mean "${(0, introspect_js_1.printableCatalogName)(`${tableName}.${hint}`)}"?` : '.'}`);
|
|
124
|
+
}
|
|
125
|
+
const problem = typeof tsType === 'string' ? (0, schema_builder_js_1.tsTypeTextProblem)(tsType) : 'it is not a string';
|
|
126
|
+
if (problem !== null) {
|
|
127
|
+
throw new errors_js_1.ValidationError(`columnTypes[${(0, introspect_js_1.printableCatalogName)(JSON.stringify(key))}] must be one TypeScript type on one line, for example ` +
|
|
128
|
+
`"import('../src/types.js').Settings", but ${problem}: ${(0, introspect_js_1.printableCatalogName)(JSON.stringify(tsType))}.`);
|
|
129
|
+
}
|
|
130
|
+
let perTable = out.get(tableName);
|
|
131
|
+
if (!perTable) {
|
|
132
|
+
perTable = new Map();
|
|
133
|
+
out.set(tableName, perTable);
|
|
134
|
+
}
|
|
135
|
+
perTable.set(columnName, tsType.trim());
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
75
138
|
}
|
|
76
139
|
/**
|
|
77
140
|
* Characters that must never reach generated source verbatim: the two that end
|
|
@@ -145,7 +208,13 @@ function generate(options) {
|
|
|
145
208
|
// F3: resolve which extension sibling imports get in index.ts.
|
|
146
209
|
const { ext: importExt, mode: importMode } = resolveImportExtension(outDir, options.importExtension ?? 'auto');
|
|
147
210
|
const files = [];
|
|
148
|
-
const fileOptions = {
|
|
211
|
+
const fileOptions = {
|
|
212
|
+
noTimestamp: options.noTimestamp,
|
|
213
|
+
importExt,
|
|
214
|
+
importMode,
|
|
215
|
+
...(options.columnTypes !== undefined ? { columnTypes: options.columnTypes } : {}),
|
|
216
|
+
...(options.logger !== undefined ? { logger: options.logger } : {}),
|
|
217
|
+
};
|
|
149
218
|
// Generate types.ts
|
|
150
219
|
const typesContent = generateTypes(schema, fileOptions);
|
|
151
220
|
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(outDir, 'types.ts'), typesContent, 'utf-8');
|
|
@@ -175,6 +244,7 @@ function generate(options) {
|
|
|
175
244
|
* mode label for the generated comment.
|
|
176
245
|
*
|
|
177
246
|
* - `'js'` → `.js`
|
|
247
|
+
* - `'ts'` → `.ts`
|
|
178
248
|
* - `'none'` → `''`
|
|
179
249
|
* - `'auto'` → tsconfig-driven ({@link detectTsconfigExtension}); falls back
|
|
180
250
|
* to `.js` (the pre-0.41 default) when detection is uncertain, so NodeNext
|
|
@@ -183,6 +253,8 @@ function generate(options) {
|
|
|
183
253
|
function resolveImportExtension(outDir, requested) {
|
|
184
254
|
if (requested === 'js')
|
|
185
255
|
return { ext: '.js', mode: 'js' };
|
|
256
|
+
if (requested === 'ts')
|
|
257
|
+
return { ext: '.ts', mode: 'ts' };
|
|
186
258
|
if (requested === 'none')
|
|
187
259
|
return { ext: '', mode: 'none' };
|
|
188
260
|
const detected = detectTsconfigExtension((0, node_path_1.resolve)(outDir));
|
|
@@ -335,14 +407,14 @@ function generatedFileHeader(options) {
|
|
|
335
407
|
* skip those relations here with a warning instead of emitting broken types.
|
|
336
408
|
* The runtime metadata (metadata.ts) still carries every relation.
|
|
337
409
|
*/
|
|
338
|
-
function typeSafeRelations(table,
|
|
410
|
+
function typeSafeRelations(table, logger) {
|
|
339
411
|
const columnFields = new Set(table.columns.map((c) => c.field));
|
|
340
412
|
const usable = [];
|
|
341
413
|
for (const [relName, rel] of Object.entries(table.relations)) {
|
|
342
414
|
if (columnFields.has(relName)) {
|
|
343
|
-
if (
|
|
344
|
-
|
|
345
|
-
|
|
415
|
+
if (logger) {
|
|
416
|
+
(0, logger_js_1.logTo)(logger, 'warn', (0, introspect_js_1.printableCatalogName)(`Relation "${relName}" on table "${table.name}" shadows a column field of the same name, ` +
|
|
417
|
+
'omitting it from the generated types. Rename the relation (or the column) to expose it.'), { code: 'generate-relation-shadows-column', table: table.name, relation: relName });
|
|
346
418
|
}
|
|
347
419
|
continue;
|
|
348
420
|
}
|
|
@@ -378,10 +450,42 @@ function isEmittableIdentifier(name) {
|
|
|
378
450
|
function requireEmittable(derived, subject, role) {
|
|
379
451
|
if (isEmittableIdentifier(derived))
|
|
380
452
|
return;
|
|
381
|
-
throw new errors_js_1.ValidationError(`Cannot generate code for ${subject}: it produces ${JSON.stringify(derived)} as ${role}, ` +
|
|
453
|
+
throw new errors_js_1.ValidationError(`Cannot generate code for ${(0, introspect_js_1.printableCatalogName)(subject)}: it produces ${(0, introspect_js_1.printableCatalogName)(JSON.stringify(derived))} as ${role}, ` +
|
|
382
454
|
`which is not a valid TypeScript identifier. Rename the database object, or exclude it from generation ` +
|
|
383
455
|
`(introspect \`exclude\`).`);
|
|
384
456
|
}
|
|
457
|
+
/** Table accessors the generated `TurbineClient` class cannot declare as members. */
|
|
458
|
+
const RESERVED_ACCESSORS = new Set(['constructor', '__proto__']);
|
|
459
|
+
/** The type a defineSchema file declared for a column (bare, no `| null`), if any. */
|
|
460
|
+
function declaredTsTypeOf(col) {
|
|
461
|
+
return col.declaredTsType;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* The metadata `types.ts` is written from. A column whose schema file declared a
|
|
465
|
+
* TypeScript type takes it, with `| null` for a nullable column, in place of the
|
|
466
|
+
* type mapped from its database type. The runtime keeps the mapped type
|
|
467
|
+
* (`metadata.ts` is written from the input), because the engines branch on it.
|
|
468
|
+
* Returns `schema` itself when no column declared one.
|
|
469
|
+
*/
|
|
470
|
+
function withDeclaredTypes(schema) {
|
|
471
|
+
let declaredAny = false;
|
|
472
|
+
const tables = Object.create(null);
|
|
473
|
+
for (const [name, table] of Object.entries(schema.tables)) {
|
|
474
|
+
if (!table.columns.some((c) => declaredTsTypeOf(c) !== undefined)) {
|
|
475
|
+
tables[name] = table;
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
declaredAny = true;
|
|
479
|
+
tables[name] = {
|
|
480
|
+
...table,
|
|
481
|
+
columns: table.columns.map((col) => {
|
|
482
|
+
const declared = declaredTsTypeOf(col);
|
|
483
|
+
return declared === undefined ? col : { ...col, tsType: col.nullable ? `${declared} | null` : declared };
|
|
484
|
+
}),
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
return declaredAny ? { ...schema, tables } : schema;
|
|
488
|
+
}
|
|
385
489
|
/**
|
|
386
490
|
* Refuse a schema whose names cannot be emitted as TypeScript identifiers.
|
|
387
491
|
*
|
|
@@ -403,11 +507,29 @@ function assertEmittableSchema(schema) {
|
|
|
403
507
|
for (const table of Object.values(schema.tables)) {
|
|
404
508
|
requireEmittable(entityName(table.name), `table "${table.name}"`, 'the generated entity type name');
|
|
405
509
|
requireEmittable(snakeToCamelStr(table.name), `table "${table.name}"`, 'the generated client accessor');
|
|
510
|
+
if (RESERVED_ACCESSORS.has(snakeToCamelStr(table.name))) {
|
|
511
|
+
throw new errors_js_1.ValidationError(`Cannot generate code for table "${(0, introspect_js_1.printableCatalogName)(table.name)}": its client accessor ` +
|
|
512
|
+
`"${snakeToCamelStr(table.name)}" is a reserved class member name, so the generated class could not declare it. ` +
|
|
513
|
+
'Rename the table, or exclude it from generation (introspect `exclude`).');
|
|
514
|
+
}
|
|
406
515
|
// Two columns on one field would emit a duplicate interface member (TS2300
|
|
407
516
|
// in the consumer's build) and a `columnMap` missing a column. The rule is
|
|
408
517
|
// introspect.ts's; re-asserting it here covers a hand-built or
|
|
409
518
|
// engine-introspected schema that never went through that path.
|
|
410
519
|
(0, introspect_js_1.assertDistinctColumnFields)(table.name, table.columns);
|
|
520
|
+
// A column's type is emitted verbatim. Introspection only produces mapped
|
|
521
|
+
// types, but hand-built or code-first metadata (`tsType` on defineSchema)
|
|
522
|
+
// can carry anything, and a line break or a stray bracket would end the type
|
|
523
|
+
// and start a statement in the generated file.
|
|
524
|
+
for (const col of table.columns) {
|
|
525
|
+
for (const text of [col.tsType, declaredTsTypeOf(col)]) {
|
|
526
|
+
const problem = text === undefined ? null : (0, schema_builder_js_1.tsTypeTextProblem)(text);
|
|
527
|
+
if (problem !== null) {
|
|
528
|
+
throw new errors_js_1.ValidationError(`Cannot generate code for column "${(0, introspect_js_1.printableCatalogName)(col.name)}" on table "${(0, introspect_js_1.printableCatalogName)(table.name)}": ` +
|
|
529
|
+
`its TypeScript type ${(0, introspect_js_1.printableCatalogName)(JSON.stringify(text))} cannot be emitted, because ${problem}. Give one type on one line.`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
411
533
|
// Only the relations that reach the TYPE layer: a relation shadowing a
|
|
412
534
|
// column field is already dropped from types.ts, so refusing on its name
|
|
413
535
|
// would refuse a schema that generates fine.
|
|
@@ -419,38 +541,33 @@ function assertEmittableSchema(schema) {
|
|
|
419
541
|
}
|
|
420
542
|
/**
|
|
421
543
|
* Generate the contents of `types.ts` (entity interfaces, *Create / *Update,
|
|
422
|
-
*
|
|
423
|
-
* generator output without writing files to disk.
|
|
544
|
+
* *Relations brand-field interfaces, and the write input types). Exported so
|
|
545
|
+
* tests can pin the generator output without writing files to disk.
|
|
424
546
|
*/
|
|
425
|
-
function generateTypes(
|
|
426
|
-
assertEmittableSchema(
|
|
547
|
+
function generateTypes(input, options) {
|
|
548
|
+
assertEmittableSchema(input);
|
|
549
|
+
const schema = withDeclaredTypes(input);
|
|
550
|
+
const overrides = resolveColumnTypeOverrides(schema, options?.columnTypes);
|
|
427
551
|
const lines = [...generatedFileHeader(options)];
|
|
428
|
-
//
|
|
429
|
-
//
|
|
430
|
-
//
|
|
431
|
-
//
|
|
432
|
-
|
|
433
|
-
// recurse through nested `with` clauses at any depth. The generator emits
|
|
434
|
-
// each `*Relations` member as a `RelationDescriptor<Target, Cardinality,
|
|
435
|
-
// TargetRelations>` so users get full deep `with`-clause type inference
|
|
436
|
-
// out of the box (TASK-2.1).
|
|
437
|
-
lines.push("import type { RelationDescriptor, UpdateOperatorInput } from 'turbine-orm';");
|
|
552
|
+
// UpdateOperatorInput, SqlExpr and ColumnRef let the generated *Update
|
|
553
|
+
// types express atomic operators, `expr.*` update expressions and `col()`.
|
|
554
|
+
// RelationDescriptor is the brand that lets `WithResult` recurse through
|
|
555
|
+
// nested `with` clauses at any depth.
|
|
556
|
+
lines.push("import type { ColumnRef, RelationDescriptor, SqlExpr, UpdateOperatorInput } from 'turbine-orm';");
|
|
438
557
|
lines.push('');
|
|
439
|
-
// Pre-compute which tables have relations so we know whether to thread
|
|
440
|
-
// `${TargetType}Relations` (for deep inference) or `{}` (the no-relations
|
|
441
|
-
// default) into each `RelationDescriptor`. Built once up-front because
|
|
442
|
-
// relations can point at tables we haven't iterated to yet.
|
|
443
558
|
// Relations that can be surfaced in the type layer, computed once per table
|
|
444
559
|
// (relations that would shadow a scalar column field are excluded + warned).
|
|
445
560
|
const safeRelationsByTable = new Map();
|
|
446
561
|
for (const t of Object.values(schema.tables)) {
|
|
447
|
-
safeRelationsByTable.set(t.name, typeSafeRelations(t));
|
|
562
|
+
safeRelationsByTable.set(t.name, typeSafeRelations(t, options?.logger ?? logger_js_1.consoleLogger));
|
|
448
563
|
}
|
|
449
564
|
const tablesWithRelations = new Set();
|
|
450
565
|
for (const t of Object.values(schema.tables)) {
|
|
451
566
|
if ((safeRelationsByTable.get(t.name) ?? []).length > 0)
|
|
452
567
|
tablesWithRelations.add(t.name);
|
|
453
568
|
}
|
|
569
|
+
const rowType = (table, col) => columnTsType(col, schema.enums, overrideFor(overrides, table.name, col.name));
|
|
570
|
+
const writeType = (table, col) => writeColumnTsType(col, schema.enums, overrideFor(overrides, table.name, col.name));
|
|
454
571
|
// Generate enum types
|
|
455
572
|
for (const [enumName, labels] of Object.entries(schema.enums)) {
|
|
456
573
|
const typeName = (0, schema_js_1.snakeToPascal)(enumName);
|
|
@@ -468,62 +585,57 @@ function generateTypes(schema, options) {
|
|
|
468
585
|
const pkNote = table.primaryKey.includes(col.name) ? ' (primary key)' : '';
|
|
469
586
|
const nullNote = col.nullable ? ' (nullable)' : '';
|
|
470
587
|
// PII columns are excluded from default projections, so the field is
|
|
471
|
-
// absent unless the
|
|
472
|
-
//
|
|
473
|
-
|
|
588
|
+
// absent unless the read passes `includePii: UNSAFE` (a `select` naming
|
|
589
|
+
// it without the flag throws, and a write never returns it). The emitted
|
|
590
|
+
// type marks it optional so it tells the truth about absence.
|
|
591
|
+
const piiNote = col.pii ? ' (PII: absent unless the read passes includePii: UNSAFE)' : '';
|
|
474
592
|
const optional = col.pii ? '?' : '';
|
|
475
593
|
lines.push(` /** Column: ${docSafe(col.name)}, ${docSafe(col.pgType)}${pkNote}${nullNote}${piiNote} */`);
|
|
476
|
-
lines.push(` ${quoteIfNeeded(col.field)}${optional}: ${
|
|
594
|
+
lines.push(` ${quoteIfNeeded(col.field)}${optional}: ${rowType(table, col)};`);
|
|
477
595
|
}
|
|
478
596
|
lines.push('}');
|
|
479
597
|
lines.push('');
|
|
480
598
|
// --- Create input type ---
|
|
481
599
|
// Optional: server-generated columns (serial / identity), columns with a
|
|
482
600
|
// default, and nullable columns (default to NULL). Everything else is
|
|
483
|
-
// required, PRIMARY KEY MEMBERSHIP INCLUDED
|
|
601
|
+
// required, PRIMARY KEY MEMBERSHIP INCLUDED: a text natural key, a
|
|
484
602
|
// client-supplied uuid with no default, or a composite key of plain
|
|
485
|
-
// integers must be supplied,
|
|
486
|
-
// (E010). Marking every PK column optional let `create({ data: {} })` on
|
|
487
|
-
// a junction table typecheck and fail at runtime, which is the opposite of
|
|
488
|
-
// what a generated type is for.
|
|
603
|
+
// integers must be supplied, or the database rejects the row (E010).
|
|
489
604
|
lines.push(`/** Input type for creating a row in \`${docSafe(table.name)}\` */`);
|
|
490
605
|
lines.push(`export type ${typeName}Create = {`);
|
|
491
606
|
for (const col of table.columns) {
|
|
492
607
|
// STORED generated columns are computed by the database, never writable.
|
|
493
608
|
if (col.isGeneratedStored)
|
|
494
609
|
continue;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
if (isOptional) {
|
|
498
|
-
const reason = isGenerated ? 'auto-generated' : col.hasDefault ? 'has default' : 'nullable';
|
|
610
|
+
if (isOptionalOnCreate(col)) {
|
|
611
|
+
const reason = col.isGenerated === true ? 'auto-generated' : col.hasDefault ? 'has default' : 'nullable';
|
|
499
612
|
lines.push(` /** Optional: ${reason} */`);
|
|
500
|
-
lines.push(` ${quoteIfNeeded(col.field)}?: ${
|
|
613
|
+
lines.push(` ${quoteIfNeeded(col.field)}?: ${writeType(table, col)} | undefined;`);
|
|
501
614
|
}
|
|
502
615
|
else {
|
|
503
|
-
lines.push(` ${quoteIfNeeded(col.field)}: ${
|
|
616
|
+
lines.push(` ${quoteIfNeeded(col.field)}: ${writeType(table, col)};`);
|
|
504
617
|
}
|
|
505
618
|
}
|
|
506
619
|
lines.push('};');
|
|
507
620
|
lines.push('');
|
|
508
621
|
// --- Update input type (all fields optional except PK) ---
|
|
509
|
-
//
|
|
510
|
-
//
|
|
622
|
+
// Every field also takes `{ set: value }`, and numeric fields the atomic
|
|
623
|
+
// operators (`{ viewCount: { increment: 1 } }`).
|
|
511
624
|
const nonPkCols = table.columns.filter((c) => !table.primaryKey.includes(c.name) && !c.isGeneratedStored);
|
|
512
625
|
lines.push(`/** Input type for updating a row in \`${docSafe(table.name)}\` */`);
|
|
513
626
|
lines.push(`export type ${typeName}Update = {`);
|
|
514
627
|
for (const col of nonPkCols) {
|
|
515
|
-
lines.push(` ${quoteIfNeeded(col.field)}?: ${updateFieldType(
|
|
628
|
+
lines.push(` ${quoteIfNeeded(col.field)}?: ${updateFieldType(writeType(table, col))} | undefined;`);
|
|
516
629
|
}
|
|
517
630
|
lines.push('};');
|
|
518
631
|
lines.push('');
|
|
519
632
|
// --- Relations map (for type-safe `with` clauses) ---
|
|
520
633
|
//
|
|
521
|
-
// Each relation is
|
|
522
|
-
// TargetRelations
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
// no relations of its own, the descriptor uses `{}` (the default).
|
|
634
|
+
// Each relation is a `RelationDescriptor<Target, Cardinality,
|
|
635
|
+
// TargetRelations, Kind, Nullable>`. `WithResult` reads the third
|
|
636
|
+
// parameter to recurse into nested `with` clauses and the fifth to type a
|
|
637
|
+
// to-one relation whose foreign key is NOT NULL as never null; the write
|
|
638
|
+
// input types read the kind.
|
|
527
639
|
const safeRelations = safeRelationsByTable.get(table.name) ?? [];
|
|
528
640
|
const hasRelations = safeRelations.length > 0;
|
|
529
641
|
if (hasRelations) {
|
|
@@ -532,168 +644,367 @@ function generateTypes(schema, options) {
|
|
|
532
644
|
for (const [relName, rel] of safeRelations) {
|
|
533
645
|
const targetType = entityName(rel.to);
|
|
534
646
|
// manyToMany is a collection too → 'many' cardinality (same as hasMany).
|
|
535
|
-
const cardinality = rel
|
|
647
|
+
const cardinality = isToMany(rel) ? "'many'" : "'one'";
|
|
536
648
|
const targetRelations = tablesWithRelations.has(rel.to) ? `${targetType}Relations` : '{}';
|
|
537
|
-
|
|
649
|
+
const notNull = isRequiredBelongsTo(table, rel) ? ', false' : '';
|
|
650
|
+
lines.push(` ${quoteIfNeeded(relName)}: RelationDescriptor<${targetType}, ${cardinality}, ${targetRelations}, '${escSQ(rel.type)}'${notNull}>;`);
|
|
538
651
|
}
|
|
539
652
|
lines.push('}');
|
|
540
653
|
lines.push('');
|
|
541
654
|
// --- Legacy per-relation interfaces (kept for backward compatibility) ---
|
|
542
655
|
for (const [relName, rel] of safeRelations) {
|
|
543
656
|
const targetType = entityName(rel.to);
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
lines.push(` ${quoteIfNeeded(relName)}: ${targetType} | null;`);
|
|
554
|
-
lines.push('}');
|
|
555
|
-
}
|
|
657
|
+
const loaded = isToMany(rel)
|
|
658
|
+
? `${targetType}[]`
|
|
659
|
+
: isRequiredBelongsTo(table, rel)
|
|
660
|
+
? targetType
|
|
661
|
+
: `${targetType} | null`;
|
|
662
|
+
lines.push(`/** ${typeName} with \`${docSafe(relName)}\` relation loaded (${rel.type}: ${docSafe(rel.to)}) */`);
|
|
663
|
+
lines.push(`export interface ${typeName}With${(0, schema_js_1.snakeToPascal)(relName)} extends ${typeName} {`);
|
|
664
|
+
lines.push(` ${quoteIfNeeded(relName)}: ${loaded};`);
|
|
665
|
+
lines.push('}');
|
|
556
666
|
lines.push('');
|
|
557
667
|
}
|
|
558
668
|
}
|
|
559
669
|
}
|
|
560
670
|
// ---------------------------------------------------------------------------
|
|
561
|
-
//
|
|
562
|
-
//
|
|
671
|
+
// Write input types: WhereUnique, CreateInput, UpdateInput, the nested
|
|
672
|
+
// relation write inputs, and the per-table Inputs map the client accessor
|
|
673
|
+
// takes.
|
|
563
674
|
// ---------------------------------------------------------------------------
|
|
564
675
|
for (const table of Object.values(schema.tables)) {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
676
|
+
emitWhereUnique(lines, table, (col) => rowType(table, { ...col, nullable: false, tsType: stripNull(col.tsType) }));
|
|
677
|
+
}
|
|
678
|
+
const variantsByTable = createVariantsByTable(schema, safeRelationsByTable);
|
|
679
|
+
for (const table of Object.values(schema.tables)) {
|
|
680
|
+
emitWriteInputs(lines, schema, table, safeRelationsByTable.get(table.name) ?? [], variantsByTable, (col) => writeType(table, col));
|
|
681
|
+
}
|
|
682
|
+
return lines.join('\n');
|
|
683
|
+
}
|
|
684
|
+
/** A to-many relation (an array on the row). */
|
|
685
|
+
function isToMany(rel) {
|
|
686
|
+
return rel.type === 'hasMany' || rel.type === 'manyToMany';
|
|
687
|
+
}
|
|
688
|
+
/** Whether a column may be omitted on create. */
|
|
689
|
+
function isOptionalOnCreate(col) {
|
|
690
|
+
return col.isGenerated === true || col.hasDefault || col.nullable;
|
|
691
|
+
}
|
|
692
|
+
/** `T | null` -> `T`. */
|
|
693
|
+
function stripNull(tsType) {
|
|
694
|
+
return tsType.replace(/\s*\|\s*null$/, '');
|
|
695
|
+
}
|
|
696
|
+
/** The foreign-key columns of a relation, as columns of `table` (the side that holds them). */
|
|
697
|
+
function fkColumns(table, rel) {
|
|
698
|
+
const out = [];
|
|
699
|
+
for (const name of Array.isArray(rel.foreignKey) ? rel.foreignKey : [rel.foreignKey]) {
|
|
700
|
+
const col = table.columns.find((c) => c.name === name);
|
|
701
|
+
if (col)
|
|
702
|
+
out.push(col);
|
|
703
|
+
}
|
|
704
|
+
return out;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* A belongsTo relation whose foreign key is NOT NULL, so a row always points
|
|
708
|
+
* at a target row and reading the relation never yields null (absent a
|
|
709
|
+
* per-relation `where` or a global filter on the target, see
|
|
710
|
+
* `RelationDescriptor`).
|
|
711
|
+
*/
|
|
712
|
+
function isRequiredBelongsTo(table, rel) {
|
|
713
|
+
if (rel.type !== 'belongsTo')
|
|
714
|
+
return false;
|
|
715
|
+
const fks = fkColumns(table, rel);
|
|
716
|
+
return fks.length > 0 && fks.length === normalizeFk(rel).length && fks.every((c) => !c.nullable);
|
|
717
|
+
}
|
|
718
|
+
function normalizeFk(rel) {
|
|
719
|
+
return Array.isArray(rel.foreignKey) ? rel.foreignKey : [rel.foreignKey];
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* The keys a nested create through `rel` (a hasMany / hasOne on `parent`) must
|
|
723
|
+
* NOT require from the child: the child's foreign-key fields, which the
|
|
724
|
+
* runtime fills from the parent row, and the child's matching belongsTo
|
|
725
|
+
* relation, which would point the row at a second parent.
|
|
726
|
+
*/
|
|
727
|
+
function suppliedByParent(schema, rel) {
|
|
728
|
+
if (rel.type !== 'hasMany' && rel.type !== 'hasOne')
|
|
729
|
+
return [];
|
|
730
|
+
const child = Object.hasOwn(schema.tables, rel.to) ? schema.tables[rel.to] : undefined;
|
|
731
|
+
if (!child)
|
|
732
|
+
return [];
|
|
733
|
+
const fks = normalizeFk(rel);
|
|
734
|
+
const keys = fkColumns(child, rel).map((c) => c.field);
|
|
735
|
+
const fkKey = [...fks].sort().join(',');
|
|
736
|
+
for (const [name, back] of Object.entries(child.relations)) {
|
|
737
|
+
if (back.type === 'belongsTo' && back.to === rel.from && [...normalizeFk(back)].sort().join(',') === fkKey) {
|
|
738
|
+
keys.push(name);
|
|
582
739
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
return {
|
|
624
|
-
// The selector NAME is the underscore-join of the member FIELD names.
|
|
625
|
-
// It is normally a valid identifier (`orgId_userId`), but a
|
|
626
|
-
// junction-style column that is not a valid identifier (e.g. a quoted
|
|
627
|
-
// uppercase `"A"` / `"B"`) would join into a broken object key. Emit
|
|
628
|
-
// any non-identifier name as ONE quoted string-literal key so the
|
|
629
|
-
// generated types.ts always parses; the runtime selector map in
|
|
630
|
-
// query/compound-unique.ts registers the same name spelling.
|
|
631
|
-
selectorName: quoteIfNeeded(members.map((m) => m.field).join('_')),
|
|
632
|
-
memberType: `{ ${members.map((m) => `${quoteIfNeeded(m.field)}: ${m.tsType}`).join('; ')} }`,
|
|
633
|
-
};
|
|
634
|
-
});
|
|
635
|
-
// Named helper type for annotating a compound selector by hand (Prisma parity).
|
|
636
|
-
if (selectorEntries.length > 0) {
|
|
637
|
-
const cuFields = selectorEntries.map((e) => `${e.selectorName}: ${e.memberType}`);
|
|
638
|
-
lines.push(`export type ${typeName}CompoundUniques = { ${cuFields.join('; ')} };`);
|
|
639
|
-
}
|
|
640
|
-
const selectorBranches = selectorEntries.map((e) => `{ ${e.selectorName}: ${e.memberType} }`);
|
|
641
|
-
const branches = [...flatBranches, ...selectorBranches];
|
|
642
|
-
lines.push(`export type ${typeName}WhereUnique = ${branches.join(' | ')};`);
|
|
643
|
-
lines.push('');
|
|
740
|
+
}
|
|
741
|
+
return keys;
|
|
742
|
+
}
|
|
743
|
+
const FULL_CREATE_VARIANT = { omitted: new Set(), suffix: '' };
|
|
744
|
+
function variantKey(omitted) {
|
|
745
|
+
return JSON.stringify([...omitted].sort());
|
|
746
|
+
}
|
|
747
|
+
/** A PascalCase identifier fragment for a type name, or `''`. */
|
|
748
|
+
function identifierPart(name) {
|
|
749
|
+
return (0, schema_js_1.snakeToPascal)(name).replace(/[^A-Za-z0-9_$]/g, '');
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Every create shape each table needs: the full one, plus one per distinct
|
|
753
|
+
* set of parent-supplied keys among the hasMany / hasOne relations pointing at
|
|
754
|
+
* it. Keyed by table name, then by {@link variantKey}.
|
|
755
|
+
*/
|
|
756
|
+
function createVariantsByTable(schema, relationsByTable) {
|
|
757
|
+
const out = new Map();
|
|
758
|
+
for (const table of Object.values(schema.tables)) {
|
|
759
|
+
out.set(table.name, new Map([[variantKey([]), FULL_CREATE_VARIANT]]));
|
|
760
|
+
}
|
|
761
|
+
for (const table of Object.values(schema.tables)) {
|
|
762
|
+
for (const [, rel] of relationsByTable.get(table.name) ?? []) {
|
|
763
|
+
const omitted = suppliedByParent(schema, rel);
|
|
764
|
+
const variants = out.get(rel.to);
|
|
765
|
+
if (omitted.length === 0 || !variants)
|
|
766
|
+
continue;
|
|
767
|
+
const key = variantKey(omitted);
|
|
768
|
+
if (variants.has(key))
|
|
769
|
+
continue;
|
|
770
|
+
const child = schema.tables[rel.to];
|
|
771
|
+
// Name the variant after the child's relation back to the parent when
|
|
772
|
+
// there is one (`PostCreateWithoutAuthorInput`), else after the keys.
|
|
773
|
+
const back = omitted.find((k) => child !== undefined && Object.hasOwn(child.relations, k));
|
|
774
|
+
const base = `Without${identifierPart(back ?? omitted.join('_')) || 'Parent'}`;
|
|
775
|
+
const taken = new Set([...variants.values()].map((v) => v.suffix));
|
|
776
|
+
let suffix = base;
|
|
777
|
+
for (let n = 2; taken.has(suffix); n++)
|
|
778
|
+
suffix = `${base}${n}`;
|
|
779
|
+
variants.set(key, { omitted: new Set(omitted), suffix });
|
|
644
780
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
781
|
+
}
|
|
782
|
+
return out;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Required belongsTo relations beyond this many keep their foreign keys
|
|
786
|
+
* optional on create instead of becoming an either-or union: TypeScript
|
|
787
|
+
* multiplies an intersection of unions out, so N either-or pairs are 2^N
|
|
788
|
+
* object types for every create on the table.
|
|
789
|
+
*/
|
|
790
|
+
const MAX_FK_CHOICES = 4;
|
|
791
|
+
/** `XWhereUnique`: one object per unique key, plus compound selector branches. */
|
|
792
|
+
function emitWhereUnique(lines, table, memberTs) {
|
|
793
|
+
const typeName = entityName(table.name);
|
|
794
|
+
// Union of unique constraint shapes, deduplicating PK
|
|
795
|
+
const seen = new Set();
|
|
796
|
+
const uniqueSets = [];
|
|
797
|
+
const pkKey = table.primaryKey.join(',');
|
|
798
|
+
seen.add(pkKey);
|
|
799
|
+
uniqueSets.push(table.primaryKey);
|
|
800
|
+
for (const uc of table.uniqueColumns) {
|
|
801
|
+
const ucKey = uc.join(',');
|
|
802
|
+
if (!seen.has(ucKey)) {
|
|
803
|
+
seen.add(ucKey);
|
|
804
|
+
uniqueSets.push(uc);
|
|
666
805
|
}
|
|
667
806
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
807
|
+
const memberType = (colName) => {
|
|
808
|
+
const col = table.columns.find((c) => c.name === colName);
|
|
809
|
+
return { field: col?.field ?? colName, tsType: col ? memberTs(col) : 'unknown' };
|
|
810
|
+
};
|
|
811
|
+
// Flat branches: one object per unique constraint carrying its columns.
|
|
812
|
+
const flatBranches = uniqueSets.map((cols) => {
|
|
813
|
+
const fields = cols.map((colName) => {
|
|
814
|
+
const m = memberType(colName);
|
|
815
|
+
return `${quoteIfNeeded(m.field)}: ${m.tsType}`;
|
|
816
|
+
});
|
|
817
|
+
return `{ ${fields.join('; ')} }`;
|
|
818
|
+
});
|
|
819
|
+
// Prisma-style compound-unique SELECTOR branches: a synthetic key
|
|
820
|
+
// (`orgId_userId`) whose value holds the member columns, emitted for every
|
|
821
|
+
// COMPOSITE unique (PK, composite UNIQUE constraint, or composite UNIQUE
|
|
822
|
+
// index). Runtime expansion lives in query/compound-unique.ts.
|
|
823
|
+
const compoundSeen = new Set();
|
|
824
|
+
const compoundSets = [];
|
|
825
|
+
const addCompound = (cols) => {
|
|
826
|
+
if (cols.length < 2)
|
|
827
|
+
return;
|
|
828
|
+
const key = cols.join(',');
|
|
829
|
+
if (compoundSeen.has(key))
|
|
830
|
+
return;
|
|
831
|
+
compoundSeen.add(key);
|
|
832
|
+
compoundSets.push(cols);
|
|
833
|
+
};
|
|
834
|
+
addCompound(table.primaryKey);
|
|
835
|
+
for (const uc of table.uniqueColumns)
|
|
836
|
+
addCompound(uc);
|
|
837
|
+
for (const idx of table.indexes) {
|
|
838
|
+
// A partial unique index does not guarantee table-wide row uniqueness,
|
|
839
|
+
// so it must not become a compound-unique selector (matches the runtime
|
|
840
|
+
// exclusion in query/compound-unique.ts).
|
|
841
|
+
if (idx.unique && !idx.docPath && !idx.partial)
|
|
842
|
+
addCompound(idx.columns);
|
|
843
|
+
}
|
|
844
|
+
const selectorEntries = compoundSets.map((cols) => {
|
|
845
|
+
const members = cols.map(memberType);
|
|
846
|
+
return {
|
|
847
|
+
// The selector NAME is the underscore-join of the member FIELD names.
|
|
848
|
+
// A junction-style column that is not a valid identifier would join
|
|
849
|
+
// into a broken object key, so a non-identifier name is emitted as ONE
|
|
850
|
+
// quoted key; query/compound-unique.ts registers the same spelling.
|
|
851
|
+
selectorName: quoteIfNeeded(members.map((m) => m.field).join('_')),
|
|
852
|
+
memberType: `{ ${members.map((m) => `${quoteIfNeeded(m.field)}: ${m.tsType}`).join('; ')} }`,
|
|
853
|
+
};
|
|
854
|
+
});
|
|
855
|
+
// Named helper type for annotating a compound selector by hand (Prisma parity).
|
|
856
|
+
if (selectorEntries.length > 0) {
|
|
857
|
+
const cuFields = selectorEntries.map((e) => `${e.selectorName}: ${e.memberType}`);
|
|
858
|
+
lines.push(`export type ${typeName}CompoundUniques = { ${cuFields.join('; ')} };`);
|
|
859
|
+
}
|
|
860
|
+
const selectorBranches = selectorEntries.map((e) => `{ ${e.selectorName}: ${e.memberType} }`);
|
|
861
|
+
const branches = [...flatBranches, ...selectorBranches];
|
|
862
|
+
lines.push(`export type ${typeName}WhereUnique = ${branches.join(' | ')};`);
|
|
863
|
+
lines.push('');
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* The write inputs for one table:
|
|
867
|
+
*
|
|
868
|
+
* - `XCreateInput`: the create data. A required belongsTo is an either-or:
|
|
869
|
+
* give the foreign key, or the relation (`connect` / `create` /
|
|
870
|
+
* `connectOrCreate`).
|
|
871
|
+
* - `XCreateWithout<Relation>Input`: the same data for a row created through
|
|
872
|
+
* a parent's hasMany / hasOne, without the keys that parent supplies (see
|
|
873
|
+
* {@link CreateVariant}).
|
|
874
|
+
* - `XUpdateInput`: update data, scalars plus nested relation writes.
|
|
875
|
+
* - `XNestedCreateInput` / `XNestedUpdateInput` / `XConnectOrCreate` (and
|
|
876
|
+
* their `Without<Relation>` forms): the ops a to-many (or hasOne) relation
|
|
877
|
+
* pointing at X takes.
|
|
878
|
+
* - `XNestedToOneCreateInput` / `XNestedToOneUpdateInput`: the ops a
|
|
879
|
+
* belongsTo relation pointing at X takes. Single objects; `disconnect` and
|
|
880
|
+
* `delete` take `true` for the connected row.
|
|
881
|
+
* - `XInputs`: the map the generated client hands to `QueryInterface`.
|
|
882
|
+
*/
|
|
883
|
+
function emitWriteInputs(lines, schema, table, relations, variantsByTable, writeTs) {
|
|
884
|
+
const typeName = entityName(table.name);
|
|
885
|
+
const relationField = (rel, update) => {
|
|
886
|
+
const target = entityName(rel.to);
|
|
887
|
+
if (rel.type === 'belongsTo')
|
|
888
|
+
return `${target}NestedToOne${update ? 'Update' : 'Create'}Input`;
|
|
889
|
+
const variant = variantsByTable.get(rel.to)?.get(variantKey(suppliedByParent(schema, rel))) ?? FULL_CREATE_VARIANT;
|
|
890
|
+
return `${target}Nested${update ? 'Update' : 'Create'}${variant.suffix}Input`;
|
|
891
|
+
};
|
|
892
|
+
// Required belongsTo relations whose foreign keys are themselves required on
|
|
893
|
+
// create become either-or choices.
|
|
894
|
+
const choices = [];
|
|
895
|
+
const relaxed = [];
|
|
896
|
+
for (const [relName, rel] of relations) {
|
|
897
|
+
if (!isRequiredBelongsTo(table, rel))
|
|
898
|
+
continue;
|
|
899
|
+
const fks = fkColumns(table, rel).filter((c) => !c.isGeneratedStored && !isOptionalOnCreate(c));
|
|
900
|
+
if (fks.length === 0)
|
|
901
|
+
continue;
|
|
902
|
+
if (choices.length < MAX_FK_CHOICES)
|
|
903
|
+
choices.push({ relName, rel, fks });
|
|
904
|
+
else
|
|
905
|
+
relaxed.push(...fks);
|
|
906
|
+
}
|
|
907
|
+
const choiceFields = new Set(choices.flatMap((c) => c.fks).map((c) => c.field));
|
|
908
|
+
const relaxedFields = new Set(relaxed.map((c) => c.field));
|
|
909
|
+
const where = `${typeName}WhereUnique`;
|
|
910
|
+
const filter = `${typeName}WhereUnique | Partial<${typeName}>`;
|
|
911
|
+
const variants = [...(variantsByTable.get(table.name)?.values() ?? [FULL_CREATE_VARIANT])];
|
|
912
|
+
for (const { omitted, suffix } of variants) {
|
|
913
|
+
// --- CreateInput (one concrete type per variant) ---
|
|
914
|
+
const members = [];
|
|
915
|
+
for (const col of table.columns) {
|
|
916
|
+
if (col.isGeneratedStored || choiceFields.has(col.field) || omitted.has(col.field))
|
|
917
|
+
continue;
|
|
918
|
+
const optional = isOptionalOnCreate(col) || relaxedFields.has(col.field);
|
|
919
|
+
members.push(optional
|
|
920
|
+
? `${quoteIfNeeded(col.field)}?: ${writeTs(col)} | undefined`
|
|
921
|
+
: `${quoteIfNeeded(col.field)}: ${writeTs(col)}`);
|
|
922
|
+
}
|
|
923
|
+
for (const [relName, rel] of relations) {
|
|
924
|
+
if (omitted.has(relName))
|
|
925
|
+
continue;
|
|
926
|
+
members.push(`${quoteIfNeeded(relName)}?: ${relationField(rel, false)} | undefined`);
|
|
927
|
+
}
|
|
928
|
+
const parts = [members.length > 0 ? `{ ${members.join('; ')} }` : 'Record<string, never>'];
|
|
929
|
+
for (const { relName, rel, fks } of choices) {
|
|
930
|
+
if (omitted.has(relName) || fks.every((c) => omitted.has(c.field)))
|
|
931
|
+
continue;
|
|
932
|
+
const viaKey = fks.map((c) => `${quoteIfNeeded(c.field)}: ${writeTs(c)}`).join('; ');
|
|
933
|
+
const viaRelation = [
|
|
934
|
+
...fks.map((c) => `${quoteIfNeeded(c.field)}?: ${writeTs(c)} | undefined`),
|
|
935
|
+
`${quoteIfNeeded(relName)}: ${relationField(rel, false)}`,
|
|
936
|
+
].join('; ');
|
|
937
|
+
parts.push(`({ ${viaKey} } | { ${viaRelation} })`);
|
|
938
|
+
}
|
|
939
|
+
const supplied = [...omitted].map((k) => `\`${docSafe(k)}\``).join(', ');
|
|
940
|
+
lines.push(suffix === ''
|
|
941
|
+
? `/** Data for creating a \`${docSafe(table.name)}\` row */`
|
|
942
|
+
: `/** Data for creating a \`${docSafe(table.name)}\` row through a parent relation, which supplies ${supplied} */`);
|
|
943
|
+
lines.push(`export type ${typeName}Create${suffix}Input = ${parts.join(' & ')};`);
|
|
944
|
+
lines.push('');
|
|
945
|
+
// --- Ops a to-many (or hasOne) relation pointing AT this table takes ---
|
|
946
|
+
const create = `${typeName}Create${suffix}Input`;
|
|
947
|
+
const connectOrCreate = `${typeName}ConnectOrCreate${suffix}`;
|
|
948
|
+
lines.push(`export interface ${typeName}NestedCreate${suffix}Input {`);
|
|
949
|
+
lines.push(` create?: ${create} | ${create}[] | undefined;`);
|
|
950
|
+
lines.push(` connect?: ${where} | ${where}[] | undefined;`);
|
|
951
|
+
lines.push(` connectOrCreate?: ${connectOrCreate} | ${connectOrCreate}[] | undefined;`);
|
|
679
952
|
lines.push('}');
|
|
680
953
|
lines.push('');
|
|
681
|
-
lines.push(`export interface ${typeName}
|
|
682
|
-
lines.push(`
|
|
683
|
-
lines.push(`
|
|
684
|
-
lines.push(`
|
|
685
|
-
|
|
686
|
-
lines.push(`
|
|
687
|
-
|
|
954
|
+
lines.push(`export interface ${typeName}NestedUpdate${suffix}Input extends ${typeName}NestedCreate${suffix}Input {`);
|
|
955
|
+
lines.push(` disconnect?: ${filter} | (${filter})[] | undefined;`);
|
|
956
|
+
lines.push(` set?: ${where}[] | undefined;`);
|
|
957
|
+
lines.push(` delete?: ${filter} | (${filter})[] | undefined;`);
|
|
958
|
+
const updateItem = `{ where: ${filter}; data: ${typeName}UpdateInput }`;
|
|
959
|
+
lines.push(` update?: ${updateItem} | ${updateItem}[] | undefined;`);
|
|
960
|
+
const upsertItem = `{ where: ${filter}; create: ${create}; update: ${typeName}UpdateInput }`;
|
|
961
|
+
lines.push(` upsert?: ${upsertItem} | ${upsertItem}[] | undefined;`);
|
|
688
962
|
lines.push('}');
|
|
689
963
|
lines.push('');
|
|
690
|
-
lines.push(`export interface ${
|
|
691
|
-
lines.push(` where: ${
|
|
692
|
-
lines.push(` create: ${
|
|
964
|
+
lines.push(`export interface ${connectOrCreate} {`);
|
|
965
|
+
lines.push(` where: ${where};`);
|
|
966
|
+
lines.push(` create: ${create};`);
|
|
693
967
|
lines.push('}');
|
|
694
968
|
lines.push('');
|
|
695
969
|
}
|
|
696
|
-
|
|
970
|
+
// --- UpdateInput ---
|
|
971
|
+
if (relations.length > 0) {
|
|
972
|
+
lines.push(`export type ${typeName}UpdateInput = ${typeName}Update & {`);
|
|
973
|
+
for (const [relName, rel] of relations) {
|
|
974
|
+
lines.push(` ${quoteIfNeeded(relName)}?: ${relationField(rel, true)} | undefined;`);
|
|
975
|
+
}
|
|
976
|
+
lines.push('};');
|
|
977
|
+
}
|
|
978
|
+
else {
|
|
979
|
+
lines.push(`export type ${typeName}UpdateInput = ${typeName}Update;`);
|
|
980
|
+
}
|
|
981
|
+
lines.push('');
|
|
982
|
+
// A belongsTo points at ONE row: single objects, no `set`, and `true` (or
|
|
983
|
+
// `{}`) for "the connected row" on disconnect / delete.
|
|
984
|
+
const connected = `boolean | Record<string, never> | ${where}`;
|
|
985
|
+
lines.push(`export interface ${typeName}NestedToOneCreateInput {`);
|
|
986
|
+
lines.push(` create?: ${typeName}CreateInput | undefined;`);
|
|
987
|
+
lines.push(` connect?: ${where} | undefined;`);
|
|
988
|
+
lines.push(` connectOrCreate?: ${typeName}ConnectOrCreate | undefined;`);
|
|
989
|
+
lines.push('}');
|
|
990
|
+
lines.push('');
|
|
991
|
+
lines.push(`export interface ${typeName}NestedToOneUpdateInput extends ${typeName}NestedToOneCreateInput {`);
|
|
992
|
+
lines.push(` disconnect?: ${connected} | undefined;`);
|
|
993
|
+
lines.push(` delete?: ${connected} | undefined;`);
|
|
994
|
+
lines.push(` update?: { data: ${typeName}UpdateInput } | undefined;`);
|
|
995
|
+
lines.push(` upsert?: { where: ${filter}; create: ${typeName}CreateInput; update: ${typeName}UpdateInput } | undefined;`);
|
|
996
|
+
lines.push('}');
|
|
997
|
+
lines.push('');
|
|
998
|
+
// --- The accessor's input map ---
|
|
999
|
+
lines.push(`/** Write and lookup input types for the \`${docSafe(table.name)}\` client accessor */`);
|
|
1000
|
+
lines.push(`export interface ${typeName}Inputs {`);
|
|
1001
|
+
lines.push(` create: ${typeName}CreateInput;`);
|
|
1002
|
+
lines.push(` createMany: ${typeName}Create;`);
|
|
1003
|
+
lines.push(` update: ${typeName}UpdateInput;`);
|
|
1004
|
+
lines.push(` updateMany: ${typeName}Update;`);
|
|
1005
|
+
lines.push(` whereUnique: ${where};`);
|
|
1006
|
+
lines.push('}');
|
|
1007
|
+
lines.push('');
|
|
697
1008
|
}
|
|
698
1009
|
// ---------------------------------------------------------------------------
|
|
699
1010
|
// zod.ts generator (H1, `turbine generate --zod`)
|
|
@@ -721,6 +1032,19 @@ function zodScalar(ts) {
|
|
|
721
1032
|
case 'number[]':
|
|
722
1033
|
// pgvector, `pgTypeToTs('vector')` yields `number[]`.
|
|
723
1034
|
return 'z.array(z.number())';
|
|
1035
|
+
case 'number | string | bigint':
|
|
1036
|
+
// int8 write input.
|
|
1037
|
+
return 'z.union([z.number(), z.string(), z.bigint()])';
|
|
1038
|
+
case 'string | number':
|
|
1039
|
+
// numeric write input.
|
|
1040
|
+
return 'z.union([z.string(), z.number()])';
|
|
1041
|
+
case '{ x: number; y: number }':
|
|
1042
|
+
return 'z.object({ x: z.number(), y: z.number() })';
|
|
1043
|
+
case '{ x: number; y: number; radius: number }':
|
|
1044
|
+
return 'z.object({ x: z.number(), y: z.number(), radius: z.number() })';
|
|
1045
|
+
case '{ years?: number; months?: number; days?: number; hours?: number; minutes?: number; seconds?: number; milliseconds?: number }':
|
|
1046
|
+
// interval: the driver's parsed object, zero fields absent.
|
|
1047
|
+
return `z.object({ ${['years', 'months', 'days', 'hours', 'minutes', 'seconds', 'milliseconds'].map((f) => `${f}: z.number().optional()`).join(', ')} })`;
|
|
724
1048
|
default:
|
|
725
1049
|
// json/jsonb and any unmapped user-defined type.
|
|
726
1050
|
return 'z.unknown()';
|
|
@@ -749,7 +1073,14 @@ function zodBaseType(col, enums, forWrite = false) {
|
|
|
749
1073
|
expr = 'z.union([z.string(), z.date()])';
|
|
750
1074
|
}
|
|
751
1075
|
else {
|
|
752
|
-
|
|
1076
|
+
// The read (or write) map decides the element type, which for an array
|
|
1077
|
+
// is not always the scalar's (`bigint[]` reads as strings).
|
|
1078
|
+
const full = (forWrite ? schema_js_1.pgTypeToTsInput : schema_js_1.pgTypeToTs)(isArray ? `_${base}` : base, false);
|
|
1079
|
+
const element = isArray && full.endsWith('[]') ? full.slice(0, -2).replace(/^\((.*)\)$/, '$1') : full;
|
|
1080
|
+
expr = zodScalar(element);
|
|
1081
|
+
if (isArray)
|
|
1082
|
+
expr += '.array()';
|
|
1083
|
+
return expr;
|
|
753
1084
|
}
|
|
754
1085
|
if (isArray)
|
|
755
1086
|
expr += '.array()';
|
|
@@ -778,7 +1109,7 @@ function generateZod(schema, options) {
|
|
|
778
1109
|
let expr = zodBaseType(col, schema.enums);
|
|
779
1110
|
if (col.nullable)
|
|
780
1111
|
expr += '.nullable()';
|
|
781
|
-
lines.push(` ${
|
|
1112
|
+
lines.push(` ${objectKey(col.field)}: ${expr},`);
|
|
782
1113
|
}
|
|
783
1114
|
lines.push('});');
|
|
784
1115
|
lines.push('');
|
|
@@ -795,7 +1126,7 @@ function generateZod(schema, options) {
|
|
|
795
1126
|
expr += '.nullable()';
|
|
796
1127
|
if (col.isGenerated === true || col.hasDefault || col.nullable)
|
|
797
1128
|
expr += '.optional()';
|
|
798
|
-
lines.push(` ${
|
|
1129
|
+
lines.push(` ${objectKey(col.field)}: ${expr},`);
|
|
799
1130
|
}
|
|
800
1131
|
lines.push('});');
|
|
801
1132
|
lines.push('');
|
|
@@ -811,7 +1142,7 @@ function generateZod(schema, options) {
|
|
|
811
1142
|
if (col.nullable)
|
|
812
1143
|
expr += '.nullable()';
|
|
813
1144
|
expr += '.optional()';
|
|
814
|
-
lines.push(` ${
|
|
1145
|
+
lines.push(` ${objectKey(col.field)}: ${expr},`);
|
|
815
1146
|
}
|
|
816
1147
|
lines.push('});');
|
|
817
1148
|
lines.push('');
|
|
@@ -835,7 +1166,7 @@ function generateMetadata(schema, options) {
|
|
|
835
1166
|
' tables: {',
|
|
836
1167
|
];
|
|
837
1168
|
for (const table of Object.values(schema.tables)) {
|
|
838
|
-
lines.push(` ${
|
|
1169
|
+
lines.push(` ${objectKey(table.name)}: {`);
|
|
839
1170
|
lines.push(` name: '${escSQ(table.name)}',`);
|
|
840
1171
|
// columns
|
|
841
1172
|
lines.push(' columns: [');
|
|
@@ -846,13 +1177,13 @@ function generateMetadata(schema, options) {
|
|
|
846
1177
|
// columnMap
|
|
847
1178
|
lines.push(' columnMap: {');
|
|
848
1179
|
for (const [field, col] of Object.entries(table.columnMap)) {
|
|
849
|
-
lines.push(` ${
|
|
1180
|
+
lines.push(` ${objectKey(field)}: '${escSQ(col)}',`);
|
|
850
1181
|
}
|
|
851
1182
|
lines.push(' },');
|
|
852
1183
|
// reverseColumnMap
|
|
853
1184
|
lines.push(' reverseColumnMap: {');
|
|
854
1185
|
for (const [col, field] of Object.entries(table.reverseColumnMap)) {
|
|
855
|
-
lines.push(` ${
|
|
1186
|
+
lines.push(` ${objectKey(col)}: '${escSQ(field)}',`);
|
|
856
1187
|
}
|
|
857
1188
|
lines.push(' },');
|
|
858
1189
|
// dateColumns
|
|
@@ -862,12 +1193,12 @@ function generateMetadata(schema, options) {
|
|
|
862
1193
|
const dialectTypes = table.dialectTypes ?? table.pgTypes;
|
|
863
1194
|
lines.push(' dialectTypes: {');
|
|
864
1195
|
for (const [col, dialectType] of Object.entries(dialectTypes)) {
|
|
865
|
-
lines.push(` ${
|
|
1196
|
+
lines.push(` ${objectKey(col)}: '${escSQ(dialectType)}',`);
|
|
866
1197
|
}
|
|
867
1198
|
lines.push(' },');
|
|
868
1199
|
lines.push(' pgTypes: {');
|
|
869
1200
|
for (const [col, pgType] of Object.entries(table.pgTypes)) {
|
|
870
|
-
lines.push(` ${
|
|
1201
|
+
lines.push(` ${objectKey(col)}: '${escSQ(pgType)}',`);
|
|
871
1202
|
}
|
|
872
1203
|
lines.push(' },');
|
|
873
1204
|
// allColumns
|
|
@@ -896,7 +1227,7 @@ function generateMetadata(schema, options) {
|
|
|
896
1227
|
`sourceKey: ${keyLiteral(rel.through.sourceKey)}, ` +
|
|
897
1228
|
`targetKey: ${keyLiteral(rel.through.targetKey)} }`;
|
|
898
1229
|
}
|
|
899
|
-
lines.push(` ${
|
|
1230
|
+
lines.push(` ${objectKey(relName)}: { type: '${escSQ(rel.type)}', name: '${escSQ(rel.name)}', from: '${escSQ(rel.from)}', to: '${escSQ(rel.to)}', foreignKey: ${fkLiteral}, referenceKey: ${refLiteral}${throughLiteral} },`);
|
|
900
1231
|
}
|
|
901
1232
|
lines.push(' },');
|
|
902
1233
|
// indexes
|
|
@@ -929,7 +1260,7 @@ function generateMetadata(schema, options) {
|
|
|
929
1260
|
// enums
|
|
930
1261
|
lines.push(' enums: {');
|
|
931
1262
|
for (const [enumName, labels] of Object.entries(schema.enums)) {
|
|
932
|
-
lines.push(` ${
|
|
1263
|
+
lines.push(` ${objectKey(enumName)}: [${labels.map((l) => `'${escSQ(l)}'`).join(', ')}],`);
|
|
933
1264
|
}
|
|
934
1265
|
lines.push(' },');
|
|
935
1266
|
lines.push('};');
|
|
@@ -960,7 +1291,7 @@ function generateIndex(schema, options) {
|
|
|
960
1291
|
// drove it, so direct generateIndex() callers stay byte-identical).
|
|
961
1292
|
...(options?.importMode ? [`// Sibling imports resolved with importExtension: ${options.importMode}.`, ''] : []),
|
|
962
1293
|
"import { TurbineClient as BaseTurbineClient, TransactionClient as BaseTransactionClient, QueryInterface } from 'turbine-orm';",
|
|
963
|
-
"import type { TurbineConfig, TransactionOptions, DeferredQuery, PipelineResults } from 'turbine-orm';",
|
|
1294
|
+
"import type { TurbineConfig, TransactionOptions, DeferredQuery, PipelineResults, WhereClause } from 'turbine-orm';",
|
|
964
1295
|
`import { SCHEMA } from './metadata${ext}';`,
|
|
965
1296
|
];
|
|
966
1297
|
// Import all entity types and relations maps
|
|
@@ -970,9 +1301,36 @@ function generateIndex(schema, options) {
|
|
|
970
1301
|
if (hasSafeRelations.get(t.name)) {
|
|
971
1302
|
typeImports.push(`${entityName(t.name)}Relations`);
|
|
972
1303
|
}
|
|
1304
|
+
typeImports.push(`${entityName(t.name)}Inputs`);
|
|
973
1305
|
}
|
|
974
1306
|
lines.push(`import type { ${typeImports.join(', ')} } from './types${ext}';`);
|
|
975
1307
|
lines.push('');
|
|
1308
|
+
// Global filters keyed by table, each checked against its own row type, so a
|
|
1309
|
+
// misspelled table or column is a compile error. A table is keyed by its
|
|
1310
|
+
// client accessor, and also by its database name when the two differ, the
|
|
1311
|
+
// two spellings the runtime accepts.
|
|
1312
|
+
lines.push('/** `globalFilters` for this schema: a where clause (or a function returning one) per table. */');
|
|
1313
|
+
lines.push('export interface TurbineGlobalFilters {');
|
|
1314
|
+
for (const table of tableEntries) {
|
|
1315
|
+
const typeName = entityName(table.name);
|
|
1316
|
+
const where = `WhereClause<${typeName}${hasSafeRelations.get(table.name) ? `, ${typeName}Relations` : ''}>`;
|
|
1317
|
+
// `null` (or a function returning it) applies no filter on purpose.
|
|
1318
|
+
const filter = `${where} | null | (() => ${where} | null)`;
|
|
1319
|
+
const accessor = snakeToCamelStr(table.name);
|
|
1320
|
+
lines.push(` ${quoteIfNeeded(accessor)}?: ${filter} | undefined;`);
|
|
1321
|
+
if (accessor !== table.name)
|
|
1322
|
+
lines.push(` ${quoteIfNeeded(table.name)}?: ${filter} | undefined;`);
|
|
1323
|
+
}
|
|
1324
|
+
lines.push('}');
|
|
1325
|
+
lines.push('');
|
|
1326
|
+
// Each field also admits an explicit `undefined` (read as unset), so
|
|
1327
|
+
// `turbine({ connectionString: process.env.DATABASE_URL })` compiles under
|
|
1328
|
+
// exactOptionalPropertyTypes.
|
|
1329
|
+
lines.push('/** Client configuration with `globalFilters` typed against this schema. */');
|
|
1330
|
+
lines.push('export type TypedTurbineConfig = {');
|
|
1331
|
+
lines.push(" [K in Exclude<keyof TurbineConfig, 'globalFilters'>]?: TurbineConfig[K] | undefined;");
|
|
1332
|
+
lines.push('} & { globalFilters?: TurbineGlobalFilters | undefined };');
|
|
1333
|
+
lines.push('');
|
|
976
1334
|
// -------------------------------------------------------------------------
|
|
977
1335
|
// TypedTransactionClient, same typed table accessors as TurbineClient,
|
|
978
1336
|
// but scoped to a single transaction connection. The runtime instance is
|
|
@@ -990,22 +1348,23 @@ function generateIndex(schema, options) {
|
|
|
990
1348
|
const typeName = entityName(table.name);
|
|
991
1349
|
const accessor = snakeToCamelStr(table.name);
|
|
992
1350
|
const hasRelations = hasSafeRelations.get(table.name) === true;
|
|
993
|
-
const genericArgs =
|
|
1351
|
+
const genericArgs = `${typeName}, ${hasRelations ? `${typeName}Relations` : '{}'}, ${typeName}Inputs`;
|
|
994
1352
|
lines.push(` /** Query interface for the \`${docSafe(table.name)}\` table (transaction-scoped) */`);
|
|
995
1353
|
lines.push(` declare readonly ${accessor}: ${accessorType(table, genericArgs)};`);
|
|
996
1354
|
}
|
|
997
|
-
|
|
1355
|
+
// The nested `$transaction` (SAVEPOINT) hands its callback a typed client.
|
|
1356
|
+
// Declared as a real overriding method rather than through a same-named
|
|
1357
|
+
// interface (declaration merging), which strict lint configs reject
|
|
1358
|
+
// (no-unsafe-declaration-merging). It only narrows the callback's type:
|
|
1359
|
+
// the body is the base method.
|
|
998
1360
|
lines.push('');
|
|
999
|
-
// Augment the class with a typed `$transaction` overload via interface
|
|
1000
|
-
// merging. This adds an additional callable signature whose callback
|
|
1001
|
-
// parameter is narrowed to `TypedTransactionClient`, while the base
|
|
1002
|
-
// signature (callback parameter `BaseTransactionClient`) remains valid.
|
|
1003
|
-
lines.push('export interface TypedTransactionClient {');
|
|
1004
1361
|
lines.push(' /**');
|
|
1005
1362
|
lines.push(' * Nested transaction via SAVEPOINT. The callback receives a typed');
|
|
1006
1363
|
lines.push(' * `TypedTransactionClient` so all table accessors auto-complete.');
|
|
1007
1364
|
lines.push(' */');
|
|
1008
|
-
lines.push(' $transaction<R>(fn: (tx: TypedTransactionClient) => Promise<R>): Promise<R
|
|
1365
|
+
lines.push(' override $transaction<R>(fn: (tx: TypedTransactionClient) => Promise<R>): Promise<R> {');
|
|
1366
|
+
lines.push(' return super.$transaction(fn as (tx: BaseTransactionClient) => Promise<R>);');
|
|
1367
|
+
lines.push(' }');
|
|
1009
1368
|
lines.push('}');
|
|
1010
1369
|
lines.push('');
|
|
1011
1370
|
// Generate the client class with JSDoc
|
|
@@ -1032,37 +1391,30 @@ function generateIndex(schema, options) {
|
|
|
1032
1391
|
const typeName = entityName(table.name);
|
|
1033
1392
|
const accessor = snakeToCamelStr(table.name);
|
|
1034
1393
|
const hasRelations = hasSafeRelations.get(table.name) === true;
|
|
1035
|
-
const genericArgs =
|
|
1394
|
+
const genericArgs = `${typeName}, ${hasRelations ? `${typeName}Relations` : '{}'}, ${typeName}Inputs`;
|
|
1036
1395
|
lines.push(` /** Query interface for the \`${docSafe(table.name)}\` table */`);
|
|
1037
1396
|
lines.push(` declare readonly ${accessor}: ${accessorType(table, genericArgs)};`);
|
|
1038
1397
|
}
|
|
1039
1398
|
lines.push('');
|
|
1040
|
-
lines.push(' constructor(config?:
|
|
1041
|
-
lines.push(' super(config, SCHEMA);');
|
|
1399
|
+
lines.push(' constructor(config?: TypedTurbineConfig) {');
|
|
1400
|
+
lines.push(' super(config as TurbineConfig | undefined, SCHEMA);');
|
|
1042
1401
|
lines.push(' }');
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
//
|
|
1046
|
-
//
|
|
1047
|
-
//
|
|
1048
|
-
// `tx.posts`, etc. $withSession is the RLS shorthand for
|
|
1049
|
-
// `$transaction(fn, { sessionContext })`; it used to be left out, so its
|
|
1050
|
-
// callback stayed the untyped base `TransactionClient` and the documented
|
|
1051
|
-
// `tx.<table>.findMany()` did not compile on a generated client.
|
|
1402
|
+
// Typed `$transaction` / `$withSession`: the callback parameter is narrowed
|
|
1403
|
+
// to `TypedTransactionClient`, so `tx.users` etc. autocomplete. Real
|
|
1404
|
+
// overriding methods whose bodies are the base methods, NOT a same-named
|
|
1405
|
+
// interface merged onto the class: strict lint configs reject that merge
|
|
1406
|
+
// (no-unsafe-declaration-merging), and generated code must pass them.
|
|
1052
1407
|
//
|
|
1053
|
-
//
|
|
1054
|
-
//
|
|
1055
|
-
//
|
|
1056
|
-
//
|
|
1057
|
-
|
|
1058
|
-
// $withSession overload mirrors the base parameter list exactly for the same
|
|
1059
|
-
// reason; src/test/generate-typecheck.test.ts compiles the result.
|
|
1060
|
-
lines.push('export interface TurbineClient {');
|
|
1408
|
+
// Each overload must stay compatible with the base class's (TS2416): the
|
|
1409
|
+
// base `$transaction` has a callback form AND a batch-array form, so both
|
|
1410
|
+
// are redeclared, and `$withSession` mirrors the base parameter list.
|
|
1411
|
+
// src/test/generate-typecheck.test.ts compiles the result.
|
|
1412
|
+
lines.push('');
|
|
1061
1413
|
lines.push(' /**');
|
|
1062
1414
|
lines.push(' * Run a callback inside a transaction. The callback receives a typed');
|
|
1063
1415
|
lines.push(' * `TypedTransactionClient` with autocompletion for every table accessor.');
|
|
1064
1416
|
lines.push(' */');
|
|
1065
|
-
lines.push(' $transaction<R>(');
|
|
1417
|
+
lines.push(' override $transaction<R>(');
|
|
1066
1418
|
lines.push(' fn: (tx: TypedTransactionClient) => Promise<R>,');
|
|
1067
1419
|
lines.push(' options?: TransactionOptions,');
|
|
1068
1420
|
lines.push(' ): Promise<R>;');
|
|
@@ -1070,18 +1422,28 @@ function generateIndex(schema, options) {
|
|
|
1070
1422
|
lines.push(' * Batch form: run several deferred queries in one transaction and get');
|
|
1071
1423
|
lines.push(' * their results as a tuple (same as the base client).');
|
|
1072
1424
|
lines.push(' */');
|
|
1073
|
-
lines.push(' $transaction<T extends readonly DeferredQuery<unknown>[]>(');
|
|
1425
|
+
lines.push(' override $transaction<T extends readonly DeferredQuery<unknown>[]>(');
|
|
1074
1426
|
lines.push(' queries: readonly [...T],');
|
|
1427
|
+
lines.push(' options?: TransactionOptions,');
|
|
1075
1428
|
lines.push(' ): Promise<PipelineResults<T>>;');
|
|
1429
|
+
lines.push(' override $transaction(');
|
|
1430
|
+
lines.push(' fnOrQueries: ((tx: TypedTransactionClient) => Promise<unknown>) | readonly DeferredQuery<unknown>[],');
|
|
1431
|
+
lines.push(' options?: TransactionOptions,');
|
|
1432
|
+
lines.push(' ): Promise<unknown> {');
|
|
1433
|
+
lines.push(" return super.$transaction(fnOrQueries as Parameters<BaseTurbineClient['$transaction']>[0], options);");
|
|
1434
|
+
lines.push(' }');
|
|
1435
|
+
lines.push('');
|
|
1076
1436
|
lines.push(' /**');
|
|
1077
1437
|
lines.push(' * Run a callback inside a transaction with the given session GUCs applied');
|
|
1078
1438
|
lines.push(' * via `set_config(..., true)` (the RLS / multi-tenant shorthand). The');
|
|
1079
1439
|
lines.push(' * callback receives a typed `TypedTransactionClient`, same as `$transaction`.');
|
|
1080
1440
|
lines.push(' */');
|
|
1081
|
-
lines.push(' $withSession<R>(');
|
|
1441
|
+
lines.push(' override $withSession<R>(');
|
|
1082
1442
|
lines.push(' context: Record<string, string | number | boolean>,');
|
|
1083
1443
|
lines.push(' fn: (tx: TypedTransactionClient) => Promise<R>,');
|
|
1084
|
-
lines.push(' ): Promise<R
|
|
1444
|
+
lines.push(' ): Promise<R> {');
|
|
1445
|
+
lines.push(' return super.$withSession(context, fn as (tx: BaseTransactionClient) => Promise<R>);');
|
|
1446
|
+
lines.push(' }');
|
|
1085
1447
|
lines.push('}');
|
|
1086
1448
|
lines.push('');
|
|
1087
1449
|
// Factory function with JSDoc
|
|
@@ -1092,7 +1454,7 @@ function generateIndex(schema, options) {
|
|
|
1092
1454
|
lines.push(' * fields) to fall back to the `DATABASE_URL` environment variable.');
|
|
1093
1455
|
lines.push(' * @returns A fully-typed TurbineClient with table accessors.');
|
|
1094
1456
|
lines.push(' */');
|
|
1095
|
-
lines.push('export function turbine(config?:
|
|
1457
|
+
lines.push('export function turbine(config?: TypedTurbineConfig): TurbineClient {');
|
|
1096
1458
|
lines.push(' return new TurbineClient(config);');
|
|
1097
1459
|
lines.push('}');
|
|
1098
1460
|
lines.push('');
|
|
@@ -1129,13 +1491,13 @@ function generatePrismaMap(map, options) {
|
|
|
1129
1491
|
' models: {',
|
|
1130
1492
|
];
|
|
1131
1493
|
for (const [modelName, model] of Object.entries(map.models)) {
|
|
1132
|
-
lines.push(` ${
|
|
1494
|
+
lines.push(` ${objectKey(modelName)}: {`);
|
|
1133
1495
|
lines.push(` table: '${escSQ(model.table)}',`);
|
|
1134
1496
|
lines.push(` accessor: '${escSQ(model.accessor)}',`);
|
|
1135
1497
|
lines.push(` fields: ${serializeStringRecord(model.fields)},`);
|
|
1136
1498
|
lines.push(' relations: {');
|
|
1137
1499
|
for (const [rel, r] of Object.entries(model.relations)) {
|
|
1138
|
-
lines.push(` ${
|
|
1500
|
+
lines.push(` ${objectKey(rel)}: { name: '${escSQ(r.name)}', cardinality: '${r.cardinality}' },`);
|
|
1139
1501
|
}
|
|
1140
1502
|
lines.push(' },');
|
|
1141
1503
|
lines.push(` compoundUniques: ${serializeStringArrayRecord(model.compoundUniques)},`);
|
|
@@ -1160,14 +1522,14 @@ function serializeStringRecord(rec) {
|
|
|
1160
1522
|
const entries = Object.entries(rec);
|
|
1161
1523
|
if (entries.length === 0)
|
|
1162
1524
|
return '{}';
|
|
1163
|
-
return `{ ${entries.map(([k, v]) => `${
|
|
1525
|
+
return `{ ${entries.map(([k, v]) => `${objectKey(k)}: '${escSQ(v)}'`).join(', ')} }`;
|
|
1164
1526
|
}
|
|
1165
1527
|
/** Serialize a `Record<string, string[]>` as an inline object literal. */
|
|
1166
1528
|
function serializeStringArrayRecord(rec) {
|
|
1167
1529
|
const entries = Object.entries(rec);
|
|
1168
1530
|
if (entries.length === 0)
|
|
1169
1531
|
return '{}';
|
|
1170
|
-
return `{ ${entries.map(([k, v]) => `${
|
|
1532
|
+
return `{ ${entries.map(([k, v]) => `${objectKey(k)}: [${v.map((s) => `'${escSQ(s)}'`).join(', ')}]`).join(', ')} }`;
|
|
1171
1533
|
}
|
|
1172
1534
|
// ---------------------------------------------------------------------------
|
|
1173
1535
|
// Helpers
|
|
@@ -1221,6 +1583,11 @@ function serializeColumn(col) {
|
|
|
1221
1583
|
parts.push(`updatedAt: true`);
|
|
1222
1584
|
if (col.maxLength !== undefined)
|
|
1223
1585
|
parts.push(`maxLength: ${col.maxLength}`);
|
|
1586
|
+
// The default expression, so an engine without column defaults (PowDB) can
|
|
1587
|
+
// apply it on create. Arbitrary SQL text, so written as a JSON string
|
|
1588
|
+
// literal, which escapes every quote, backslash and line break.
|
|
1589
|
+
if (col.defaultValue != null)
|
|
1590
|
+
parts.push(`defaultValue: ${JSON.stringify(col.defaultValue)}`);
|
|
1224
1591
|
return `{ ${parts.join(', ')} }`;
|
|
1225
1592
|
}
|
|
1226
1593
|
/**
|
|
@@ -1245,44 +1612,33 @@ const BARE_KEY_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
|
1245
1612
|
* return `'${s}'` verbatim, so a name containing a quote closed the key and
|
|
1246
1613
|
* reopened in expression position.
|
|
1247
1614
|
*
|
|
1248
|
-
*
|
|
1249
|
-
*
|
|
1250
|
-
* adding a property, because the object-literal special case applies to the
|
|
1251
|
-
* quoted spelling too. Quoting is not a fix for it, so there is no branch for
|
|
1252
|
-
* it; the consequence is a metadata map that silently omits that one entry, not
|
|
1253
|
-
* code execution.
|
|
1615
|
+
* A name of `__proto__` is safe in an interface but not in an object literal;
|
|
1616
|
+
* see {@link objectKey}.
|
|
1254
1617
|
*/
|
|
1255
1618
|
function quoteIfNeeded(s) {
|
|
1256
1619
|
return BARE_KEY_RE.test(s) ? s : `'${escSQ(s)}'`;
|
|
1257
1620
|
}
|
|
1621
|
+
/**
|
|
1622
|
+
* {@link quoteIfNeeded} for a key in an OBJECT LITERAL that is evaluated at
|
|
1623
|
+
* runtime (metadata.ts, the Prisma map). `{ '__proto__': v }` sets the literal's
|
|
1624
|
+
* prototype instead of defining a property, quoted or not, so that one name is
|
|
1625
|
+
* emitted as a computed key, whose string literal is just as inert and which does
|
|
1626
|
+
* define the property. Interface members (types.ts) have no such rule and keep
|
|
1627
|
+
* using {@link quoteIfNeeded}.
|
|
1628
|
+
*/
|
|
1629
|
+
function objectKey(s) {
|
|
1630
|
+
return s === '__proto__' ? "['__proto__']" : quoteIfNeeded(s);
|
|
1631
|
+
}
|
|
1258
1632
|
function snakeToCamelStr(s) {
|
|
1259
1633
|
return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
1260
1634
|
}
|
|
1261
1635
|
/**
|
|
1262
|
-
* Build the update-input field type for a column
|
|
1263
|
-
*
|
|
1264
|
-
*
|
|
1265
|
-
*
|
|
1266
|
-
*
|
|
1267
|
-
* `'number'` (e.g. `number`, `number | null`), it's eligible. Other
|
|
1268
|
-
* scalar types (`string`, `boolean`, `Date`, `unknown`, `Buffer`,
|
|
1269
|
-
* `Date | null`, etc.) pass through unchanged.
|
|
1636
|
+
* Build the update-input field type for a column: the plain value, the
|
|
1637
|
+
* {@link UpdateOperatorInput} object (`{ set }` on every column, plus
|
|
1638
|
+
* `increment` / `decrement` / `multiply` / `divide` when the type includes
|
|
1639
|
+
* `number`), an `expr.*` update expression or a `col()` reference, matching
|
|
1640
|
+
* what `update()` accepts at runtime and the core `UpdateData` type.
|
|
1270
1641
|
*/
|
|
1271
1642
|
function updateFieldType(tsType) {
|
|
1272
|
-
|
|
1273
|
-
if (containsNumberType(tsType)) {
|
|
1274
|
-
return `${tsType} | UpdateOperatorInput<number>`;
|
|
1275
|
-
}
|
|
1276
|
-
return tsType;
|
|
1277
|
-
}
|
|
1278
|
-
/**
|
|
1279
|
-
* Detect whether a TypeScript type expression contains the `number` primitive
|
|
1280
|
-
* as a top-level union member. Conservative on purpose, only matches
|
|
1281
|
-
* `number`, `number | null`, `null | number`, etc., not `number[]` or
|
|
1282
|
-
* `Record<string, number>`.
|
|
1283
|
-
*/
|
|
1284
|
-
function containsNumberType(tsType) {
|
|
1285
|
-
// Tokenize on `|` and check each member.
|
|
1286
|
-
const parts = tsType.split('|').map((p) => p.trim());
|
|
1287
|
-
return parts.some((p) => p === 'number');
|
|
1643
|
+
return `${tsType} | UpdateOperatorInput<${tsType}> | SqlExpr<${tsType}> | ColumnRef`;
|
|
1288
1644
|
}
|