@vibeorm/dsl 3.0.0 → 4.0.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/dist/column.d.ts +3 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +112 -80
- package/dist/index.js.map +7 -7
- package/dist/types.d.ts +28 -10
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/dsl/src/column.ts
|
|
3
|
+
import { VibeError, assertJsonSchemaRef, encodePrismaString } from "@vibeorm/schema";
|
|
3
4
|
function cloneColumnDef(params) {
|
|
4
5
|
const { column } = params;
|
|
5
6
|
return {
|
|
6
7
|
__type: "column",
|
|
7
8
|
__columnType: column.__columnType,
|
|
8
9
|
__name: column.__name,
|
|
9
|
-
__config: { ...column.__config },
|
|
10
|
+
__config: { ...column.__config, ...column.__config.jsonSchema === undefined ? {} : { jsonSchema: assertJsonSchemaRef({ value: column.__config.jsonSchema }) } },
|
|
10
11
|
__enumValues: column.__enumValues,
|
|
11
12
|
__enumName: column.__enumName,
|
|
12
13
|
__enumStorage: column.__enumStorage
|
|
@@ -165,10 +166,17 @@ function datetime(config = {}) {
|
|
|
165
166
|
|
|
166
167
|
class ChainableJsonColumn extends ChainableColumn {
|
|
167
168
|
constructor(config = {}) {
|
|
168
|
-
super("json", config);
|
|
169
|
+
super("json", { ...config, ...config.jsonSchema === undefined ? {} : { jsonSchema: assertJsonSchemaRef({ value: config.jsonSchema }) } });
|
|
170
|
+
}
|
|
171
|
+
schema(reference) {
|
|
172
|
+
if (this.__config.jsonSchema !== undefined)
|
|
173
|
+
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "A field may declare only one JSON schema reference", meta: { issues: [{ rule: "duplicate-json-schema-ref" }] } });
|
|
174
|
+
this.__config.jsonSchema = assertJsonSchemaRef({ value: reference });
|
|
175
|
+
return this;
|
|
169
176
|
}
|
|
170
177
|
typed(config) {
|
|
171
|
-
const
|
|
178
|
+
const ref = assertJsonSchemaRef({ value: config });
|
|
179
|
+
const annotation = `@type(${ref.name} from ${encodePrismaString({ value: ref.from })})`;
|
|
172
180
|
this.__config.comment = this.__config.comment === undefined ? annotation : `${this.__config.comment} ${annotation}`;
|
|
173
181
|
return this;
|
|
174
182
|
}
|
|
@@ -227,11 +235,11 @@ function createEnum(config) {
|
|
|
227
235
|
}
|
|
228
236
|
|
|
229
237
|
// packages/dsl/src/generator/prisma.ts
|
|
230
|
-
import { VibeError as
|
|
238
|
+
import { VibeError as VibeError6, encodePrismaString as encodePrismaString2, internalError, normalizeJsonSchemaRef as normalizeJsonSchemaRef2, providerToDialect as providerToDialect2 } from "@vibeorm/schema";
|
|
231
239
|
import { getSqlDialect as getSqlDialect2 } from "@vibeorm/sql";
|
|
232
240
|
|
|
233
241
|
// packages/dsl/src/predicate.ts
|
|
234
|
-
import { VibeError } from "@vibeorm/schema";
|
|
242
|
+
import { VibeError as VibeError2 } from "@vibeorm/schema";
|
|
235
243
|
import { compileExclusion, renderPredicate } from "@vibeorm/sql";
|
|
236
244
|
function isFieldExpr(operand) {
|
|
237
245
|
return typeof operand === "object" && operand !== null && "kind" in operand && operand.kind === "field";
|
|
@@ -246,7 +254,7 @@ function toValue(params) {
|
|
|
246
254
|
if (typeof input === "string" || typeof input === "number" || typeof input === "boolean") {
|
|
247
255
|
return { kind: "value", value: input };
|
|
248
256
|
}
|
|
249
|
-
throw new
|
|
257
|
+
throw new VibeError2({
|
|
250
258
|
code: "VIBE_SCHEMA_INVALID",
|
|
251
259
|
message: `${at}: ${input === null ? "null" : typeof input} is not a comparable literal \u2014 use isNull()/isNotNull() for null, and a string, number, bigint or boolean otherwise`,
|
|
252
260
|
meta: { rule: "predicate-literal-invalid", at }
|
|
@@ -254,13 +262,27 @@ function toValue(params) {
|
|
|
254
262
|
}
|
|
255
263
|
function toOperand(params) {
|
|
256
264
|
const { operand, at } = params;
|
|
257
|
-
return
|
|
265
|
+
return typeof operand === "object" && operand !== null ? toExpression({ value: operand, at }) : toValue({ input: operand, at });
|
|
266
|
+
}
|
|
267
|
+
function toExpression(params) {
|
|
268
|
+
const { value, at } = params;
|
|
269
|
+
if (isFieldExpr(value))
|
|
270
|
+
return toRef({ expr: value });
|
|
271
|
+
if (typeof value === "object" && value !== null && "kind" in value) {
|
|
272
|
+
if (value.kind === "jsonText" && "field" in value && "path" in value && Array.isArray(value.path) && Array.from(value.path).every((segment) => typeof segment === "string" && !segment.includes("\x00"))) {
|
|
273
|
+
return { kind: "jsonText", field: toRef({ expr: requireFieldExpr({ value: value.field, at }) }), path: [...value.path] };
|
|
274
|
+
}
|
|
275
|
+
if (value.kind === "length" && "expression" in value) {
|
|
276
|
+
return { kind: "length", expression: toExpression({ value: value.expression, at }) };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: `${at}: expected a field, jsonText or length expression with a string-only JSON path`, meta: { rule: "predicate-operand-invalid", at } });
|
|
258
280
|
}
|
|
259
281
|
function requireFieldExpr(params) {
|
|
260
282
|
const { value, at } = params;
|
|
261
283
|
if (isFieldExpr(value))
|
|
262
284
|
return value;
|
|
263
|
-
throw new
|
|
285
|
+
throw new VibeError2({
|
|
264
286
|
code: "VIBE_SCHEMA_INVALID",
|
|
265
287
|
message: `${at}: the left side of a comparison must be c.field("\u2026"), not a bare value`,
|
|
266
288
|
meta: { rule: "predicate-operand-invalid", at }
|
|
@@ -269,7 +291,7 @@ function requireFieldExpr(params) {
|
|
|
269
291
|
function requireOperands(params) {
|
|
270
292
|
const { operands, at } = params;
|
|
271
293
|
if (operands.length === 0) {
|
|
272
|
-
throw new
|
|
294
|
+
throw new VibeError2({
|
|
273
295
|
code: "VIBE_SCHEMA_INVALID",
|
|
274
296
|
message: `${at}: an empty boolean group has no honest SQL spelling \u2014 pass at least one operand`,
|
|
275
297
|
meta: { rule: "predicate-empty", at }
|
|
@@ -281,14 +303,14 @@ function createPredicateBuilder() {
|
|
|
281
303
|
const compare = (op) => (left, right) => ({
|
|
282
304
|
kind: "compare",
|
|
283
305
|
op,
|
|
284
|
-
left:
|
|
306
|
+
left: toExpression({ value: left, at: `c.${op}()` }),
|
|
285
307
|
right: toOperand({ operand: right, at: `c.${op}()` })
|
|
286
308
|
});
|
|
287
309
|
const membership = (negate) => (field, values) => {
|
|
288
310
|
const at = negate ? "c.notInList()" : "c.inList()";
|
|
289
311
|
const ref = toRef({ expr: requireFieldExpr({ value: field, at }) });
|
|
290
312
|
if (values.length === 0) {
|
|
291
|
-
throw new
|
|
313
|
+
throw new VibeError2({
|
|
292
314
|
code: "VIBE_SCHEMA_INVALID",
|
|
293
315
|
message: `${at}: an empty IN list is UNKNOWN on every row and is never what an author means`,
|
|
294
316
|
meta: { rule: "predicate-empty", at, field: ref.name }
|
|
@@ -309,6 +331,9 @@ function createPredicateBuilder() {
|
|
|
309
331
|
gte: compare("gte"),
|
|
310
332
|
lt: compare("lt"),
|
|
311
333
|
lte: compare("lte"),
|
|
334
|
+
jsonText: ({ field, path }) => ({ ...toExpression({ value: { kind: "jsonText", field: { kind: "field", name: field }, path }, at: "c.jsonText()" }), __kind: "String" }),
|
|
335
|
+
length: (expression) => ({ kind: "length", expression: toExpression({ value: expression, at: "c.length()" }), __kind: "Int" }),
|
|
336
|
+
isTrue: (operand) => ({ kind: "isTrue", operand }),
|
|
312
337
|
inList: membership(false),
|
|
313
338
|
notInList: membership(true),
|
|
314
339
|
isNull: (name) => ({ kind: "isNull", field: { kind: "field", name } }),
|
|
@@ -318,7 +343,7 @@ function createPredicateBuilder() {
|
|
|
318
343
|
not: (operand) => ({ kind: "not", operand }),
|
|
319
344
|
named: (name, predicate) => {
|
|
320
345
|
if (name.trim() === "") {
|
|
321
|
-
throw new
|
|
346
|
+
throw new VibeError2({
|
|
322
347
|
code: "VIBE_SCHEMA_INVALID",
|
|
323
348
|
message: "c.named(): a CHECK needs a non-empty name \u2014 the name is what keys the migration diff",
|
|
324
349
|
meta: { rule: "predicate-unnamed-check" }
|
|
@@ -337,10 +362,13 @@ function evaluateChecks(params) {
|
|
|
337
362
|
}
|
|
338
363
|
const declared = checks(createPredicateBuilder());
|
|
339
364
|
return declared.map((entry, index) => {
|
|
340
|
-
if (entry
|
|
341
|
-
|
|
365
|
+
if (entry !== null && typeof entry === "object" && !("__type" in entry) && typeof entry.name === "string" && "expression" in entry && typeof entry.expression === "string") {
|
|
366
|
+
return { kind: "raw", name: entry.name, expression: entry.expression };
|
|
367
|
+
}
|
|
368
|
+
if (entry === null || typeof entry !== "object" || !("__type" in entry) || entry.__type !== "check") {
|
|
369
|
+
throw new VibeError2({
|
|
342
370
|
code: "VIBE_SCHEMA_INVALID",
|
|
343
|
-
message: `Model "${model}": checks[${index}]
|
|
371
|
+
message: `Model "${model}": checks[${index}] must be c.named("name", predicate) or a raw { name, expression } CHECK`,
|
|
344
372
|
meta: { rule: "predicate-unnamed-check", model, index }
|
|
345
373
|
});
|
|
346
374
|
}
|
|
@@ -369,7 +397,7 @@ function compileChecks(params) {
|
|
|
369
397
|
function compileIndexPredicate(params) {
|
|
370
398
|
const { where, wherePredicate, columns, model, dialect, enums } = params;
|
|
371
399
|
if (where !== undefined && wherePredicate !== undefined) {
|
|
372
|
-
throw new
|
|
400
|
+
throw new VibeError2({
|
|
373
401
|
code: "VIBE_SCHEMA_INVALID",
|
|
374
402
|
message: `Index on "${model.name}" [${columns.join(", ")}] carries both a raw where: string and a typed .where() \u2014 an index has ONE predicate`,
|
|
375
403
|
meta: { rule: "predicate-duplicate-where", model: model.name, columns: [...columns] }
|
|
@@ -395,13 +423,14 @@ function compileIndexPredicate(params) {
|
|
|
395
423
|
// packages/dsl/src/to-ir.ts
|
|
396
424
|
import {
|
|
397
425
|
EMPTY_EXTENSION_REGISTRY,
|
|
398
|
-
VibeError as
|
|
426
|
+
VibeError as VibeError5,
|
|
399
427
|
assertValidSchemaIR,
|
|
400
428
|
exactIntegerLiteral,
|
|
401
429
|
providerToDialect,
|
|
402
430
|
scalarListDefault,
|
|
403
431
|
serializeJsonDefault,
|
|
404
432
|
mergeExtensionDeclarations,
|
|
433
|
+
normalizeJsonSchemaRef,
|
|
405
434
|
unsupportedCapability
|
|
406
435
|
} from "@vibeorm/schema";
|
|
407
436
|
import { getSqlDialect } from "@vibeorm/sql";
|
|
@@ -442,10 +471,10 @@ function fieldTypeKey(params) {
|
|
|
442
471
|
}
|
|
443
472
|
|
|
444
473
|
// packages/dsl/src/relation-plan.ts
|
|
445
|
-
import { VibeError as
|
|
474
|
+
import { VibeError as VibeError4, isSqlObjectIdentity, predicateKey } from "@vibeorm/schema";
|
|
446
475
|
|
|
447
476
|
// packages/dsl/src/model.ts
|
|
448
|
-
import { VibeError as
|
|
477
|
+
import { VibeError as VibeError3 } from "@vibeorm/schema";
|
|
449
478
|
|
|
450
479
|
// packages/dsl/src/naming.ts
|
|
451
480
|
function capitalize(params) {
|
|
@@ -487,7 +516,7 @@ function createIndexBuilder() {
|
|
|
487
516
|
const partial = (index) => {
|
|
488
517
|
const where = (predicate) => {
|
|
489
518
|
if (index.__where !== undefined) {
|
|
490
|
-
throw new
|
|
519
|
+
throw new VibeError3({
|
|
491
520
|
code: "VIBE_SCHEMA_INVALID",
|
|
492
521
|
message: `Index on [${index.__columns.join(", ")}] already carries a raw where: predicate \u2014 an index has ONE predicate, so use either the raw string or .where(), not both`,
|
|
493
522
|
meta: { rule: "predicate-duplicate-where", columns: [...index.__columns] }
|
|
@@ -525,7 +554,7 @@ function createIndexBuilder() {
|
|
|
525
554
|
const [first, second] = args;
|
|
526
555
|
if (args.length === 2 && Array.isArray(first) && typeof second === "object" && !Array.isArray(second)) {
|
|
527
556
|
if ("ops" in second && second.ops !== undefined) {
|
|
528
|
-
throw new
|
|
557
|
+
throw new VibeError3({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Custom unique operator classes can change client selector equality", meta: { feature: "uniqueIndexOperatorClasses" } });
|
|
529
558
|
}
|
|
530
559
|
return partial({
|
|
531
560
|
__type: "unique",
|
|
@@ -620,7 +649,7 @@ function detectOneToOnePairs(params) {
|
|
|
620
649
|
for (const [modelName, modelRelations] of Object.entries(relations)) {
|
|
621
650
|
for (const [relName, rel] of Object.entries(modelRelations)) {
|
|
622
651
|
if (rel.__ownsForeignKey !== undefined && (typeof rel.__ownsForeignKey !== "boolean" || rel.__relationType !== "one" || rel.__ownsForeignKey && rel.__fromModel !== modelName)) {
|
|
623
|
-
throw new
|
|
652
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: `Invalid foreign-key ownership declaration on ${modelName}.${relName}` });
|
|
624
653
|
}
|
|
625
654
|
if (rel.__relationType === "one") {
|
|
626
655
|
allOnes.push({ modelName, relName, rel });
|
|
@@ -695,7 +724,7 @@ function sameColumnTuple(params) {
|
|
|
695
724
|
function resolveFkOwner(params) {
|
|
696
725
|
const { a, b, models } = params;
|
|
697
726
|
const ownershipError = () => {
|
|
698
|
-
throw new
|
|
727
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: `Ambiguous foreign-key ownership between ${a.modelName}.${a.relName} and ${b.modelName}.${b.relName}; set ownsForeignKey: true on the foreign-key side`, meta: { rule: "ambiguous-foreign-key-owner" } });
|
|
699
728
|
};
|
|
700
729
|
const aOwns = a.rel.__ownsForeignKey;
|
|
701
730
|
const bOwns = b.rel.__ownsForeignKey;
|
|
@@ -866,14 +895,14 @@ function planIndexes(params) {
|
|
|
866
895
|
for (const index of evaluateIndexes({ model })) {
|
|
867
896
|
if (index.__ops !== undefined) {
|
|
868
897
|
if (index.__ops === null || typeof index.__ops !== "object" || Array.isArray(index.__ops))
|
|
869
|
-
throw new
|
|
898
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "Index ops must be a field-keyed qualified-name map", meta: { rule: "index-operator-class-map", model: model.__name } });
|
|
870
899
|
if (index.__type === "unique")
|
|
871
|
-
throw new
|
|
900
|
+
throw new VibeError4({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Custom unique operator classes can change client selector equality", meta: { feature: "uniqueIndexOperatorClasses", model: model.__name } });
|
|
872
901
|
for (const [field, operatorClass] of Object.entries(index.__ops)) {
|
|
873
902
|
if (!index.__columns.includes(field))
|
|
874
|
-
throw new
|
|
903
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "Operator class names a field outside the index", meta: { rule: "index-operator-class-field", model: model.__name, field } });
|
|
875
904
|
if (operatorClass !== undefined && !isSqlObjectIdentity({ kind: "table", name: operatorClass }))
|
|
876
|
-
throw new
|
|
905
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "Index operator class must be a qualified SQL name { namespace, name }", meta: { rule: "index-operator-class-identity", model: model.__name, field } });
|
|
877
906
|
}
|
|
878
907
|
}
|
|
879
908
|
const operatorClasses = index.__columns.map((field) => index.__ops?.[field]);
|
|
@@ -924,7 +953,7 @@ function assertForeignKeyIntegrity(params) {
|
|
|
924
953
|
const references = relation.references ?? [];
|
|
925
954
|
const at = `${relation.model}.${relation.name}`;
|
|
926
955
|
const refuse = (message, rule, meta = {}) => {
|
|
927
|
-
throw new
|
|
956
|
+
throw new VibeError4({
|
|
928
957
|
code: "VIBE_SCHEMA_INVALID",
|
|
929
958
|
message: `Relation ${at}: ${message}`,
|
|
930
959
|
meta: { rule, model: relation.model, relation: relation.name, ...meta }
|
|
@@ -1086,16 +1115,16 @@ function fieldDefault(params) {
|
|
|
1086
1115
|
const { column } = params;
|
|
1087
1116
|
if (column.__columnType === "id") {
|
|
1088
1117
|
if (column.__config.default !== undefined || column.__config.dbgenerated !== undefined || column.__config.autoincrement !== undefined)
|
|
1089
|
-
throw new
|
|
1118
|
+
throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "Use the id generator strategy instead of a conflicting id default" });
|
|
1090
1119
|
return idDefault({ column });
|
|
1091
1120
|
}
|
|
1092
1121
|
const { dbgenerated, default: value, autoincrement } = column.__config;
|
|
1093
1122
|
if ([dbgenerated !== undefined, value !== undefined, autoincrement === true].filter(Boolean).length > 1) {
|
|
1094
|
-
throw new
|
|
1123
|
+
throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: `Column "${column.__name ?? column.__columnType}" has conflicting defaults` });
|
|
1095
1124
|
}
|
|
1096
1125
|
if (autoincrement === true) {
|
|
1097
1126
|
if (column.__columnType !== "int" && column.__columnType !== "bigint" || column.__config.array === true || column.__config.optional === true) {
|
|
1098
|
-
throw new
|
|
1127
|
+
throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "autoincrement requires a required scalar Int or BigInt column" });
|
|
1099
1128
|
}
|
|
1100
1129
|
return { kind: "autoincrement" };
|
|
1101
1130
|
}
|
|
@@ -1105,7 +1134,7 @@ function fieldDefault(params) {
|
|
|
1105
1134
|
return;
|
|
1106
1135
|
if (column.__config.array === true) {
|
|
1107
1136
|
if (!Array.isArray(value))
|
|
1108
|
-
throw new
|
|
1137
|
+
throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "A scalar-list default must be an array" });
|
|
1109
1138
|
return { kind: "list", values: scalarListDefault({ type: fieldType({ column }), values: column.__columnType === "json" ? value.map((entry) => typeof entry === "string" ? entry : serializeJsonDefault({ value: entry })) : value }) };
|
|
1110
1139
|
}
|
|
1111
1140
|
if (column.__columnType === "json" && typeof value !== "string") {
|
|
@@ -1120,20 +1149,20 @@ function fieldDefault(params) {
|
|
|
1120
1149
|
return { kind: "enumValue", value };
|
|
1121
1150
|
}
|
|
1122
1151
|
if (typeof value === "number" && (!Number.isFinite(value) || (column.__columnType === "int" || column.__columnType === "bigint") && !Number.isSafeInteger(value))) {
|
|
1123
|
-
throw new
|
|
1152
|
+
throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "Numeric defaults must be finite; integer defaults must be exact" });
|
|
1124
1153
|
}
|
|
1125
1154
|
if (typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
|
|
1126
1155
|
return { kind: "literal", value };
|
|
1127
1156
|
}
|
|
1128
1157
|
if (typeof value === "bigint")
|
|
1129
1158
|
return { kind: "literal", value: exactIntegerLiteral({ text: value.toString() }) };
|
|
1130
|
-
throw new
|
|
1159
|
+
throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: `Unsupported default for ${column.__columnType}` });
|
|
1131
1160
|
}
|
|
1132
1161
|
function datetimeNativeType(params) {
|
|
1133
1162
|
const { tz, precision } = params.config;
|
|
1134
1163
|
if (precision !== undefined && (tz !== true || !Number.isInteger(precision) || precision < 0 || precision > 6)) {
|
|
1135
1164
|
const at = params.column === undefined ? "A datetime column" : `Column "${params.column}"`;
|
|
1136
|
-
throw new
|
|
1165
|
+
throw new VibeError5({
|
|
1137
1166
|
code: "VIBE_SCHEMA_INVALID",
|
|
1138
1167
|
message: `${at}: a datetime precision must be an integer from 0 to 6 on a tz() column, as in dsl.datetime().tz({ precision: 3 })`,
|
|
1139
1168
|
meta: { issues: [{ rule: "datetime-precision", column: params.column, precision, tz: tz === true }] }
|
|
@@ -1150,7 +1179,7 @@ function nativeType(params) {
|
|
|
1150
1179
|
if (maxLength !== undefined || text !== undefined) {
|
|
1151
1180
|
const invalid = column.__enumStorage !== "string" || maxLength !== undefined && (typeof maxLength !== "number" || !Number.isSafeInteger(maxLength) || maxLength <= 0) || text !== undefined && typeof text !== "boolean" || text === true && maxLength !== undefined;
|
|
1152
1181
|
if (invalid) {
|
|
1153
|
-
throw new
|
|
1182
|
+
throw new VibeError5({
|
|
1154
1183
|
code: "VIBE_SCHEMA_INVALID",
|
|
1155
1184
|
message: "Enum text/maxLength options require string storage and either a positive integer maxLength or text: true, not both",
|
|
1156
1185
|
meta: { issues: [{ rule: "enum-string-storage-options", enum: column.__enumName }] }
|
|
@@ -1187,6 +1216,7 @@ function buildField(params) {
|
|
|
1187
1216
|
const dbName = typeof config.map === "string" ? config.map : undefined;
|
|
1188
1217
|
const documentation = typeof config.comment === "string" ? config.comment : undefined;
|
|
1189
1218
|
const collation = typeof config.collation === "string" ? config.collation : undefined;
|
|
1219
|
+
const metadata = normalizeJsonSchemaRef({ documentation, reference: config.jsonSchema });
|
|
1190
1220
|
const defaultValue = fieldDefault({ column });
|
|
1191
1221
|
const native = extensionNativeType ?? nativeType({ column });
|
|
1192
1222
|
return {
|
|
@@ -1202,7 +1232,7 @@ function buildField(params) {
|
|
|
1202
1232
|
...defaultValue === undefined ? {} : { default: defaultValue },
|
|
1203
1233
|
...native === undefined ? {} : { nativeType: native },
|
|
1204
1234
|
...column.__columnType === "unsupported" ? { isUnsupported: true } : {},
|
|
1205
|
-
...
|
|
1235
|
+
...metadata
|
|
1206
1236
|
};
|
|
1207
1237
|
}
|
|
1208
1238
|
function buildRelation(params) {
|
|
@@ -1390,7 +1420,7 @@ function toIR(params) {
|
|
|
1390
1420
|
}
|
|
1391
1421
|
}
|
|
1392
1422
|
if (issues.length > 0) {
|
|
1393
|
-
throw new
|
|
1423
|
+
throw new VibeError5({
|
|
1394
1424
|
code: "VIBE_SCHEMA_INVALID",
|
|
1395
1425
|
message: `Schema validation failed with ${issues.length} issue(s): ${issues.map((issue) => issue.message).join("; ")}`,
|
|
1396
1426
|
meta: { issues }
|
|
@@ -1447,7 +1477,7 @@ function prismaType(params) {
|
|
|
1447
1477
|
const args = Array.isArray(rawArgs) ? rawArgs.map(String).join(", ") : "";
|
|
1448
1478
|
return `${extensionTypeName}${args.length > 0 ? `(${args})` : ""}${list}`;
|
|
1449
1479
|
}
|
|
1450
|
-
return `Unsupported(${
|
|
1480
|
+
return `Unsupported(${encodePrismaString2({ value: String(column.__config.dbType) })})${list}`;
|
|
1451
1481
|
}
|
|
1452
1482
|
}
|
|
1453
1483
|
}
|
|
@@ -1462,11 +1492,11 @@ function defaultAttribute(params) {
|
|
|
1462
1492
|
}
|
|
1463
1493
|
const numericColumn = column.__columnType === "int" || column.__columnType === "bigint" || column.__columnType === "float" || column.__columnType === "decimal";
|
|
1464
1494
|
if (value.kind === "literal")
|
|
1465
|
-
return `@default(${typeof value.value === "string" && !(numericColumn && /^-?\d+(\.\d+)?$/.test(value.value)) ?
|
|
1495
|
+
return `@default(${typeof value.value === "string" && !(numericColumn && /^-?\d+(\.\d+)?$/.test(value.value)) ? encodePrismaString2({ value: value.value }) : String(value.value)})`;
|
|
1466
1496
|
if (value.kind === "enumValue")
|
|
1467
1497
|
return `@default(${value.value})`;
|
|
1468
1498
|
if (value.kind === "dbgenerated")
|
|
1469
|
-
return `@default(dbgenerated(${
|
|
1499
|
+
return `@default(dbgenerated(${encodePrismaString2({ value: value.sql })}))`;
|
|
1470
1500
|
return `@default(${value.kind}())`;
|
|
1471
1501
|
}
|
|
1472
1502
|
function nativeTypeAttribute(params) {
|
|
@@ -1511,39 +1541,41 @@ function formatColumn(params) {
|
|
|
1511
1541
|
parts.push("@unique");
|
|
1512
1542
|
}
|
|
1513
1543
|
if (typeof column.__config.map === "string") {
|
|
1514
|
-
parts.push(`@map(${
|
|
1544
|
+
parts.push(`@map(${encodePrismaString2({ value: column.__config.map })})`);
|
|
1515
1545
|
}
|
|
1516
1546
|
const nativeType = nativeTypeAttribute({ column });
|
|
1517
1547
|
if (nativeType !== undefined)
|
|
1518
1548
|
parts.push(nativeType);
|
|
1519
1549
|
if (typeof column.__config.collation === "string") {
|
|
1520
|
-
parts.push(`@collation(${
|
|
1550
|
+
parts.push(`@collation(${encodePrismaString2({ value: column.__config.collation })})`);
|
|
1521
1551
|
}
|
|
1522
1552
|
const columnLine = parts.join(" ");
|
|
1523
|
-
const
|
|
1524
|
-
if (
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1553
|
+
const metadata = normalizeJsonSchemaRef2({ documentation: typeof column.__config.comment === "string" ? column.__config.comment : undefined, reference: column.__config.jsonSchema });
|
|
1554
|
+
if (metadata.jsonSchema !== undefined && column.__columnType !== "json")
|
|
1555
|
+
throw new VibeError6({ code: "VIBE_SCHEMA_INVALID", message: "JSON schemas are only valid on Json fields", meta: { issues: [{ rule: "json-schema-non-json", field: name }] } });
|
|
1556
|
+
const comments = [
|
|
1557
|
+
...metadata.documentation === undefined ? [] : metadata.documentation.split(`
|
|
1558
|
+
`),
|
|
1559
|
+
...metadata.jsonSchema === undefined ? [] : [`@schema(${metadata.jsonSchema.name} from ${encodePrismaString2({ value: metadata.jsonSchema.from })})`]
|
|
1560
|
+
];
|
|
1561
|
+
return [...comments.map((line) => ` /// ${line}`), columnLine].join(`
|
|
1528
1562
|
`);
|
|
1529
|
-
}
|
|
1530
|
-
return columnLine;
|
|
1531
1563
|
}
|
|
1532
1564
|
function formatRelation(params) {
|
|
1533
1565
|
const { relation } = params;
|
|
1534
1566
|
const { name, targetModel, relationName } = relation;
|
|
1535
1567
|
if (relation.kind === "list") {
|
|
1536
|
-
const attribute = relationName === undefined ? "" : ` @relation(${
|
|
1568
|
+
const attribute = relationName === undefined ? "" : ` @relation(${encodePrismaString2({ value: relationName })})`;
|
|
1537
1569
|
return ` ${name} ${targetModel}[]${attribute}`;
|
|
1538
1570
|
}
|
|
1539
1571
|
const optional = relation.isOptional ? "?" : "";
|
|
1540
1572
|
if (relation.kind === "backOne") {
|
|
1541
|
-
const attribute = relationName === undefined ? "" : ` @relation(${
|
|
1573
|
+
const attribute = relationName === undefined ? "" : ` @relation(${encodePrismaString2({ value: relationName })})`;
|
|
1542
1574
|
return ` ${name} ${targetModel}${optional}${attribute}`;
|
|
1543
1575
|
}
|
|
1544
1576
|
const args = [];
|
|
1545
1577
|
if (relationName !== undefined)
|
|
1546
|
-
args.push(
|
|
1578
|
+
args.push(encodePrismaString2({ value: relationName }));
|
|
1547
1579
|
if (relation.fields === undefined || relation.references === undefined) {
|
|
1548
1580
|
throw internalError({
|
|
1549
1581
|
message: `planned FK relation "${relation.model}.${name}" has no fields/references`,
|
|
@@ -1557,14 +1589,14 @@ function formatRelation(params) {
|
|
|
1557
1589
|
if (relation.onUpdate !== undefined)
|
|
1558
1590
|
args.push(`onUpdate: ${relation.onUpdate}`);
|
|
1559
1591
|
if (relation.constraintName !== undefined) {
|
|
1560
|
-
args.push(`map: ${
|
|
1592
|
+
args.push(`map: ${encodePrismaString2({ value: relation.constraintName })}`);
|
|
1561
1593
|
}
|
|
1562
1594
|
return ` ${name} ${targetModel}${optional} @relation(${args.join(", ")})`;
|
|
1563
1595
|
}
|
|
1564
1596
|
function formatIndex(params) {
|
|
1565
1597
|
const { index } = params;
|
|
1566
1598
|
if (index.ops !== undefined && Object.values(index.ops).some((operatorClass) => operatorClass !== undefined))
|
|
1567
|
-
throw new
|
|
1599
|
+
throw new VibeError6({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Prisma output cannot preserve qualified index operator classes; keep the DSL source", meta: { feature: "prismaIndexOperatorClasses", target: "prisma" } });
|
|
1568
1600
|
const columns = index.columns.join(", ");
|
|
1569
1601
|
const predicate = compileIndexPredicate({
|
|
1570
1602
|
where: index.where,
|
|
@@ -1574,10 +1606,10 @@ function formatIndex(params) {
|
|
|
1574
1606
|
dialect: params.dialect,
|
|
1575
1607
|
enums: params.enums
|
|
1576
1608
|
});
|
|
1577
|
-
const whereArg = predicate === undefined ? "" : `, where: ${
|
|
1578
|
-
const mapArg = index.map === undefined ? "" : `, map: ${
|
|
1609
|
+
const whereArg = predicate === undefined ? "" : `, where: ${encodePrismaString2({ value: predicate.where })}`;
|
|
1610
|
+
const mapArg = index.map === undefined ? "" : `, map: ${encodePrismaString2({ value: index.map })}`;
|
|
1579
1611
|
const nndArg = index.nullsNotDistinct === true ? ", nullsNotDistinct: true" : "";
|
|
1580
|
-
const nameArg = index.clientName === undefined ? "" : `, name: ${
|
|
1612
|
+
const nameArg = index.clientName === undefined ? "" : `, name: ${encodePrismaString2({ value: index.clientName })}`;
|
|
1581
1613
|
if (index.isUnique) {
|
|
1582
1614
|
return ` @@unique([${columns}]${nameArg}${mapArg}${nndArg}${whereArg})`;
|
|
1583
1615
|
}
|
|
@@ -1587,7 +1619,7 @@ function formatIndex(params) {
|
|
|
1587
1619
|
function formatExtensionValue(params) {
|
|
1588
1620
|
const { value } = params;
|
|
1589
1621
|
if (typeof value === "string")
|
|
1590
|
-
return
|
|
1622
|
+
return encodePrismaString2({ value });
|
|
1591
1623
|
if (Array.isArray(value)) {
|
|
1592
1624
|
return `[${value.map((entry) => formatExtensionValue({ value: entry })).join(", ")}]`;
|
|
1593
1625
|
}
|
|
@@ -1621,7 +1653,7 @@ function formatModel(params) {
|
|
|
1621
1653
|
dialect: params.dialect,
|
|
1622
1654
|
enums: params.enums
|
|
1623
1655
|
})) {
|
|
1624
|
-
lines.push(` @@check(${
|
|
1656
|
+
lines.push(` @@check(${encodePrismaString2({ value: check.expression })}, name: ${encodePrismaString2({ value: check.name })})`);
|
|
1625
1657
|
}
|
|
1626
1658
|
for (const use of model.__extensions ?? []) {
|
|
1627
1659
|
const args = [];
|
|
@@ -1637,11 +1669,11 @@ function formatModel(params) {
|
|
|
1637
1669
|
}
|
|
1638
1670
|
if (model.__policy !== undefined) {
|
|
1639
1671
|
const { field, context } = model.__policy;
|
|
1640
|
-
const contextArg = context === undefined || context === field ? "" : `, context: ${
|
|
1672
|
+
const contextArg = context === undefined || context === field ? "" : `, context: ${encodePrismaString2({ value: context })}`;
|
|
1641
1673
|
lines.push(` @@policy([${field}]${contextArg})`);
|
|
1642
1674
|
}
|
|
1643
1675
|
if (typeof model.__map === "string") {
|
|
1644
|
-
lines.push(` @@map(${
|
|
1676
|
+
lines.push(` @@map(${encodePrismaString2({ value: model.__map })})`);
|
|
1645
1677
|
}
|
|
1646
1678
|
lines.push("}");
|
|
1647
1679
|
return lines.join(`
|
|
@@ -1656,18 +1688,18 @@ function generatePrisma(schema, config = {}) {
|
|
|
1656
1688
|
for (const model of schema.__models.values()) {
|
|
1657
1689
|
const exclusions = compileExclusions({ exclusions: model.__exclusions, model: { name: model.__name, fields: Array.from(model.__columns, ([name, column]) => buildField({ name, column })) }, dialect: getSqlDialect2({ dialect: providerToDialect2({ provider: config.provider ?? "postgresql" }) }) });
|
|
1658
1690
|
if (exclusions.length > 0)
|
|
1659
|
-
throw new
|
|
1691
|
+
throw new VibeError6({ code: "VIBE_CONFIG", message: "Prisma output cannot preserve exclusion constraints; keep the DSL source", meta: { feature: "exclusionConstraints", target: "prisma" } });
|
|
1660
1692
|
}
|
|
1661
1693
|
const stringEnums = [...schema.__enumStorage].filter(([, storage]) => storage === "string").map(([name]) => name);
|
|
1662
1694
|
if (stringEnums.length > 0) {
|
|
1663
|
-
throw new
|
|
1695
|
+
throw new VibeError6({
|
|
1664
1696
|
code: "VIBE_CONFIG",
|
|
1665
1697
|
message: `Cannot print to Prisma: enum(s) ${stringEnums.join(", ")} use storage: "string", which .prisma cannot express losslessly. Keep the DSL as the source or switch the enum to database storage.`,
|
|
1666
1698
|
meta: { enums: stringEnums, target: "prisma" }
|
|
1667
1699
|
});
|
|
1668
1700
|
}
|
|
1669
1701
|
if (schema.__rls !== undefined) {
|
|
1670
|
-
throw new
|
|
1702
|
+
throw new VibeError6({
|
|
1671
1703
|
code: "VIBE_CONFIG",
|
|
1672
1704
|
message: "Cannot print to Prisma: this schema uses native RLS, which .prisma cannot express losslessly. Keep the DSL as the source.",
|
|
1673
1705
|
meta: { feature: "nativeRowLevelSecurity", target: "prisma" }
|
|
@@ -1700,7 +1732,7 @@ function generatePrisma(schema, config = {}) {
|
|
|
1700
1732
|
}
|
|
1701
1733
|
|
|
1702
1734
|
// packages/dsl/src/relations.ts
|
|
1703
|
-
import { VibeError as
|
|
1735
|
+
import { VibeError as VibeError7 } from "@vibeorm/schema";
|
|
1704
1736
|
var REF_KIND_BY_COLUMN_TYPE = {
|
|
1705
1737
|
string: "String",
|
|
1706
1738
|
int: "Int",
|
|
@@ -1741,7 +1773,7 @@ function extractRefInfo(params) {
|
|
|
1741
1773
|
}
|
|
1742
1774
|
const members = ref;
|
|
1743
1775
|
if (members.length === 0) {
|
|
1744
|
-
throw new
|
|
1776
|
+
throw new VibeError7({
|
|
1745
1777
|
code: "VIBE_SCHEMA_INVALID",
|
|
1746
1778
|
message: `Relation to ${target}: "${side}" is an empty tuple \u2014 name at least one column`,
|
|
1747
1779
|
meta: { rule: "relation-empty-tuple", side, targetModel: target }
|
|
@@ -1749,14 +1781,14 @@ function extractRefInfo(params) {
|
|
|
1749
1781
|
}
|
|
1750
1782
|
const models = [...new Set(members.map((member) => member.__model))].sort();
|
|
1751
1783
|
if (models.length > 1) {
|
|
1752
|
-
throw new
|
|
1784
|
+
throw new VibeError7({
|
|
1753
1785
|
code: "VIBE_SCHEMA_INVALID",
|
|
1754
1786
|
message: `Relation to ${target}: "${side}" mixes models ${models.join(", ")} \u2014 every member of a composite key must sit on one model`,
|
|
1755
1787
|
meta: { rule: "relation-mixed-models", side, targetModel: target, models }
|
|
1756
1788
|
});
|
|
1757
1789
|
}
|
|
1758
1790
|
if (members.some((member) => member.__type !== "columnRef")) {
|
|
1759
|
-
throw new
|
|
1791
|
+
throw new VibeError7({
|
|
1760
1792
|
code: "VIBE_SCHEMA_INVALID",
|
|
1761
1793
|
message: `Relation to ${target}: "${side}" routes through a junction inside a tuple \u2014 implicit many-to-many is single-key; model the join with an explicit junction model`,
|
|
1762
1794
|
meta: { rule: "relation-through-in-tuple", side, targetModel: target }
|
|
@@ -1770,7 +1802,7 @@ function createRelationFactory(params) {
|
|
|
1770
1802
|
const from = extractRefInfo({ ref: config.from, side: "from", target: targetModelName });
|
|
1771
1803
|
const to = extractRefInfo({ ref: config.to, side: "to", target: targetModelName });
|
|
1772
1804
|
if (from.columns.length !== to.columns.length) {
|
|
1773
|
-
throw new
|
|
1805
|
+
throw new VibeError7({
|
|
1774
1806
|
code: "VIBE_SCHEMA_INVALID",
|
|
1775
1807
|
message: `Relation to ${targetModelName}: "from" names ${from.columns.length} column(s) but "to" names ${to.columns.length} \u2014 a composite key pairs member by member`,
|
|
1776
1808
|
meta: { rule: "relation-tuple-arity", targetModel: targetModelName, from: from.columns, to: to.columns }
|
|
@@ -1868,7 +1900,7 @@ function mergeRelations(...parts) {
|
|
|
1868
1900
|
}
|
|
1869
1901
|
|
|
1870
1902
|
// packages/dsl/src/schema.ts
|
|
1871
|
-
import { VibeError as
|
|
1903
|
+
import { VibeError as VibeError8, validateRlsContextIR } from "@vibeorm/schema";
|
|
1872
1904
|
function sameValues(left, right) {
|
|
1873
1905
|
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
1874
1906
|
}
|
|
@@ -1887,7 +1919,7 @@ function registerEnum(params) {
|
|
|
1887
1919
|
...valuesDiffer ? [`values [${existing.values.join(", ")}] vs [${entry.values.join(", ")}]`] : [],
|
|
1888
1920
|
...storageDiffers ? [`storage "${existing.storage}" vs "${entry.storage}"`] : []
|
|
1889
1921
|
].join("; ");
|
|
1890
|
-
throw new
|
|
1922
|
+
throw new VibeError8({
|
|
1891
1923
|
code: "VIBE_SCHEMA_INVALID",
|
|
1892
1924
|
message: `Enum "${name}" is defined more than once with conflicting definitions (${origin}): ${detail} \u2014 share one enum object across modules, or give the definitions different names`,
|
|
1893
1925
|
meta: {
|
|
@@ -1913,7 +1945,7 @@ function addModelTree(params) {
|
|
|
1913
1945
|
const { model, models, origin } = params;
|
|
1914
1946
|
const existing = models.get(model.__name);
|
|
1915
1947
|
if (existing !== undefined && existing !== model) {
|
|
1916
|
-
throw new
|
|
1948
|
+
throw new VibeError8({
|
|
1917
1949
|
code: "VIBE_SCHEMA_INVALID",
|
|
1918
1950
|
message: `Model "${model.__name}" is defined more than once with different definitions (${origin}) \u2014 ` + `share one model object across modules, or give the definitions different names`,
|
|
1919
1951
|
meta: {
|
|
@@ -1991,7 +2023,7 @@ var RLS_CONTEXT_TYPES = {
|
|
|
1991
2023
|
};
|
|
1992
2024
|
function defineRls(params) {
|
|
1993
2025
|
if (typeof params.context !== "object" || params.context === null || Array.isArray(params.context)) {
|
|
1994
|
-
throw new
|
|
2026
|
+
throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "RLS context must be a record of scalar declarations", meta: { issues: [{ rule: "rls-invalid-context" }] } });
|
|
1995
2027
|
}
|
|
1996
2028
|
const entries = [];
|
|
1997
2029
|
const refs = [];
|
|
@@ -1999,7 +2031,7 @@ function defineRls(params) {
|
|
|
1999
2031
|
const descriptor = Object.getOwnPropertyDescriptor(params.context, key);
|
|
2000
2032
|
const column = descriptor?.value;
|
|
2001
2033
|
if (typeof key !== "string" || descriptor?.enumerable !== true || typeof column !== "object" || column === null || !("__columnType" in column) || !("__config" in column) || typeof column.__config !== "object" || column.__config === null || typeof column.__columnType !== "string" || !Object.hasOwn(RLS_CONTEXT_TYPES, column.__columnType) || "array" in column.__config && column.__config.array === true) {
|
|
2002
|
-
throw new
|
|
2034
|
+
throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "RLS context slots must be named scalar column declarations", meta: { issues: [{ rule: "rls-invalid-context" }] } });
|
|
2003
2035
|
}
|
|
2004
2036
|
entries.push([key, { type: RLS_CONTEXT_TYPES[column.__columnType], nullable: "optional" in column.__config && column.__config.optional === true }]);
|
|
2005
2037
|
refs.push([key, Object.freeze({ context: key })]);
|
|
@@ -2007,22 +2039,22 @@ function defineRls(params) {
|
|
|
2007
2039
|
const context = Object.freeze(Object.fromEntries(entries));
|
|
2008
2040
|
const issues = validateRlsContextIR({ context });
|
|
2009
2041
|
if (issues.length > 0) {
|
|
2010
|
-
throw new
|
|
2042
|
+
throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "Invalid RLS context declaration", meta: { issues } });
|
|
2011
2043
|
}
|
|
2012
2044
|
const policies = params.policies({ context: Object.freeze(Object.fromEntries(refs)) });
|
|
2013
2045
|
if (typeof policies !== "object" || policies === null || Array.isArray(policies)) {
|
|
2014
|
-
throw new
|
|
2046
|
+
throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be a record of model classifications", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
|
|
2015
2047
|
}
|
|
2016
2048
|
const named = [];
|
|
2017
2049
|
const classified = new Set;
|
|
2018
2050
|
for (const key of Reflect.ownKeys(policies)) {
|
|
2019
2051
|
const descriptor = Object.getOwnPropertyDescriptor(policies, key);
|
|
2020
2052
|
if (typeof key !== "string" || descriptor?.enumerable !== true || !("value" in descriptor)) {
|
|
2021
|
-
throw new
|
|
2053
|
+
throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be named data properties", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
|
|
2022
2054
|
}
|
|
2023
2055
|
const name = params.models[key]?.__name ?? key;
|
|
2024
2056
|
if (classified.has(name)) {
|
|
2025
|
-
throw new
|
|
2057
|
+
throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: `RLS classifies model "${name}" more than once`, meta: { issues: [{ rule: "rls-duplicate-classification", model: name }] } });
|
|
2026
2058
|
}
|
|
2027
2059
|
classified.add(name);
|
|
2028
2060
|
named.push([name, descriptor.value]);
|
|
@@ -2334,4 +2366,4 @@ export {
|
|
|
2334
2366
|
withNameDescription
|
|
2335
2367
|
};
|
|
2336
2368
|
|
|
2337
|
-
//# debugId=
|
|
2369
|
+
//# debugId=21B90DDA6F5E192964756E2164756E21
|