@vibeorm/schema 3.0.0 → 4.1.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/index.js CHANGED
@@ -18,6 +18,7 @@ var VIBE_ERROR_CODES = [
18
18
  "VIBE_READ_ONLY",
19
19
  "VIBE_RLS_CONTEXT",
20
20
  "VIBE_PERMISSION_DENIED",
21
+ "VIBE_CANCELLED",
21
22
  "VIBE_ADAPTER",
22
23
  "VIBE_ADAPTER_CLOSED",
23
24
  "VIBE_INTERNAL"
@@ -260,6 +261,7 @@ var POSTGRES_CAPABILITIES = Object.freeze({
260
261
  exclusionConstraints: true,
261
262
  exclusionExtensions: Object.freeze(["btree_gist"]),
262
263
  checkNormalization: "rollback",
264
+ schemaCheckExpressions: true,
263
265
  nativeRowLevelSecurity: true,
264
266
  returning: true,
265
267
  scalarArrays: true,
@@ -286,6 +288,7 @@ var SQLITE_CAPABILITIES = Object.freeze({
286
288
  exclusionConstraints: false,
287
289
  exclusionExtensions: Object.freeze([]),
288
290
  checkNormalization: "sourceText",
291
+ schemaCheckExpressions: false,
289
292
  nativeRowLevelSecurity: false,
290
293
  returning: true,
291
294
  scalarArrays: false,
@@ -312,6 +315,7 @@ var MYSQL_CAPABILITIES = Object.freeze({
312
315
  exclusionConstraints: false,
313
316
  exclusionExtensions: Object.freeze([]),
314
317
  checkNormalization: "temporary",
318
+ schemaCheckExpressions: false,
315
319
  nativeRowLevelSecurity: false,
316
320
  returning: false,
317
321
  scalarArrays: false,
@@ -337,6 +341,261 @@ var DIALECT_CAPABILITIES = Object.freeze({
337
341
  mysql: MYSQL_CAPABILITIES
338
342
  });
339
343
 
344
+ // packages/schema/src/ir.ts
345
+ var DIALECTS = ["postgres", "sqlite", "mysql"];
346
+ function providerToDialect(params) {
347
+ const { provider } = params;
348
+ switch (provider) {
349
+ case "postgresql":
350
+ case "pglite":
351
+ return "postgres";
352
+ case "sqlite":
353
+ return "sqlite";
354
+ case "mysql":
355
+ return "mysql";
356
+ }
357
+ }
358
+ function isEnumType(fieldType) {
359
+ return typeof fieldType === "object" && "enum" in fieldType;
360
+ }
361
+ function fieldTypeIdentity(params) {
362
+ const { type } = params;
363
+ return isEnumType(type) ? type.namespace === undefined || type.namespace === "public" ? `enum:${type.enum}` : JSON.stringify(["enum", type.namespace, type.enum]) : type;
364
+ }
365
+ var ENUM_STORAGES = ["database", "string"];
366
+ function enumStorageOf(params) {
367
+ return params.enumDef.storage ?? "database";
368
+ }
369
+
370
+ // packages/schema/src/native-rls-admission.ts
371
+ function assertNativeRlsAdmission(params) {
372
+ const { schema } = params;
373
+ if (schema.rls === undefined && !schema.models.some((model) => model.rls !== undefined))
374
+ return;
375
+ const dialect = params.dialect ?? providerToDialect({ provider: schema.datasource.provider });
376
+ if (!DIALECT_CAPABILITIES[dialect].nativeRowLevelSecurity) {
377
+ throw unsupportedCapability({ dialect, feature: "nativeRowLevelSecurity" });
378
+ }
379
+ for (const model of schema.models) {
380
+ const rule = model.rls;
381
+ if (rule === undefined || rule === "unprotected")
382
+ continue;
383
+ const parentName = rule !== null && typeof rule === "object" && "through" in rule ? model.relations.find((relation) => relation.name === rule.through)?.targetModel : undefined;
384
+ const parent = parentName === undefined ? undefined : schema.models.find((entry) => entry.name === parentName);
385
+ if (model.namespace !== undefined && model.namespace !== "public" || parent?.namespace !== undefined && parent.namespace !== "public") {
386
+ throw unsupportedCapability({
387
+ dialect,
388
+ feature: "fixedNamespaceNativeRls",
389
+ hint: "Native RLS protects public tables only, including deny-protected models and through-policy parents"
390
+ });
391
+ }
392
+ }
393
+ }
394
+ // packages/schema/src/reserved.ts
395
+ var RESERVED_DELEGATE_NAMES = [
396
+ "findMany",
397
+ "findPage",
398
+ "findFirst",
399
+ "findUnique",
400
+ "findFirstOrThrow",
401
+ "findUniqueOrThrow",
402
+ "create",
403
+ "createMany",
404
+ "createManyAndReturn",
405
+ "update",
406
+ "updateMany",
407
+ "updateManyAndReturn",
408
+ "upsert",
409
+ "upsertMany",
410
+ "delete",
411
+ "deleteMany",
412
+ "count",
413
+ "aggregate",
414
+ "groupBy",
415
+ "fields"
416
+ ];
417
+ var RESERVED_CLIENT_NAMES = [
418
+ "$connect",
419
+ "$disconnect",
420
+ "$transaction",
421
+ "$querySql",
422
+ "$querySqlResult",
423
+ "$executeSql",
424
+ "$queryRaw",
425
+ "$queryRawUnsafe",
426
+ "$executeRaw",
427
+ "$executeRawUnsafe",
428
+ "$on",
429
+ "$extends",
430
+ "$defineView",
431
+ "$withPolicy",
432
+ "$bypassPolicy",
433
+ "$scoped",
434
+ "$readOnly",
435
+ "$models",
436
+ "$withContext",
437
+ "$advisoryLock",
438
+ "$tryAdvisoryLock"
439
+ ];
440
+ var RESERVED_COLUMN_PREFIX = "__vibe_";
441
+ function isReservedDelegateName(params) {
442
+ return RESERVED_DELEGATE_NAMES.includes(params.name);
443
+ }
444
+ function isReservedClientName(params) {
445
+ const withPrefix = params.name.startsWith("$") ? params.name : `$${params.name}`;
446
+ return RESERVED_CLIENT_NAMES.includes(withPrefix);
447
+ }
448
+
449
+ // packages/schema/src/extension.ts
450
+ function stableSerialize(value) {
451
+ if (value === null || typeof value !== "object")
452
+ return JSON.stringify(value) ?? "undefined";
453
+ if (Array.isArray(value))
454
+ return `[${value.map(stableSerialize).join(",")}]`;
455
+ const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== undefined).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, entryValue]) => `${JSON.stringify(key)}:${stableSerialize(entryValue)}`);
456
+ return `{${entries.join(",")}}`;
457
+ }
458
+ function extensionOptionsHash(params) {
459
+ const text = stableSerialize(params.options);
460
+ let hash = 2166136261;
461
+ for (let index = 0;index < text.length; index += 1) {
462
+ hash ^= text.charCodeAt(index);
463
+ hash = Math.imul(hash, 16777619);
464
+ }
465
+ return (hash >>> 0).toString(16).padStart(8, "0");
466
+ }
467
+ function extensionManifestEntry(params) {
468
+ const { extension } = params;
469
+ return {
470
+ name: extension.name,
471
+ version: extension.version,
472
+ optionsHash: extensionOptionsHash({ options: extension.options })
473
+ };
474
+ }
475
+ var EXTENSION_NAME_PATTERN = /^[a-z][a-zA-Z0-9]*$/;
476
+ function defineExtension(extension) {
477
+ const { name } = extension;
478
+ if (!EXTENSION_NAME_PATTERN.test(name)) {
479
+ throw new VibeError({
480
+ code: "VIBE_CONFIG",
481
+ message: `extension name "${name}" is invalid \u2014 must match ${EXTENSION_NAME_PATTERN} (it becomes a client identifier)`,
482
+ meta: { name }
483
+ });
484
+ }
485
+ if (RESERVED_DELEGATE_NAMES.includes(name)) {
486
+ throw new VibeError({
487
+ code: "VIBE_CONFIG",
488
+ message: `extension name "${name}" collides with a core delegate method`,
489
+ meta: { name }
490
+ });
491
+ }
492
+ if (RESERVED_CLIENT_NAMES.includes(`$${name}`)) {
493
+ throw new VibeError({
494
+ code: "VIBE_CONFIG",
495
+ message: `extension name "${name}" collides with the core client method "$${name}"`,
496
+ meta: { name }
497
+ });
498
+ }
499
+ return Object.freeze(extension);
500
+ }
501
+ function buildExtensionRegistry(params) {
502
+ const byName = new Map;
503
+ const modelAttributes = new Map;
504
+ const fieldTypes = new Map;
505
+ for (const extension of params.extensions) {
506
+ if (byName.has(extension.name)) {
507
+ throw new VibeError({
508
+ code: "VIBE_CONFIG",
509
+ message: `duplicate extension name "${extension.name}" in the configured extension list`,
510
+ meta: { name: extension.name }
511
+ });
512
+ }
513
+ byName.set(extension.name, extension);
514
+ for (const attribute of extension.schema?.modelAttributes ?? []) {
515
+ const existing = modelAttributes.get(attribute.name);
516
+ if (existing !== undefined) {
517
+ throw new VibeError({
518
+ code: "VIBE_CONFIG",
519
+ message: `model attribute "@@${attribute.name}" is claimed by both "${existing.extension.name}" and "${extension.name}"`,
520
+ meta: { attribute: attribute.name }
521
+ });
522
+ }
523
+ modelAttributes.set(attribute.name, { extension, attribute });
524
+ }
525
+ for (const fieldType of extension.schema?.fieldTypes ?? []) {
526
+ const existing = fieldTypes.get(fieldType.name);
527
+ if (existing !== undefined) {
528
+ throw new VibeError({
529
+ code: "VIBE_CONFIG",
530
+ message: `field type "${fieldType.name}" is claimed by both "${existing.extension.name}" and "${extension.name}"`,
531
+ meta: { fieldType: fieldType.name }
532
+ });
533
+ }
534
+ fieldTypes.set(fieldType.name, { extension, fieldType });
535
+ }
536
+ }
537
+ return { extensions: params.extensions, byName, modelAttributes, fieldTypes };
538
+ }
539
+ var EMPTY_EXTENSION_REGISTRY = buildExtensionRegistry({
540
+ extensions: []
541
+ });
542
+ var PUBLIC_ONLY_EXTENSIONS = ["search", "vector", "trgm"];
543
+ function hasPublicExtensionDeclaration(params) {
544
+ return PUBLIC_ONLY_EXTENSIONS.some((name) => Object.hasOwn(extensionDeclarations({ schema: params.schema, name }), params.model));
545
+ }
546
+ function assertExtensionModelPlacement(params) {
547
+ if (params.namespace !== undefined && params.namespace !== "public") {
548
+ throw unsupportedCapability({ dialect: params.dialect ?? "postgres", feature: "fixedNamespaceExtensions", hint: "Search, vector and trigram extensions currently require public table placement" });
549
+ }
550
+ }
551
+ function assertExtensionPlacement(params) {
552
+ const dialect = params.dialect ?? providerToDialect({ provider: params.schema.datasource.provider });
553
+ for (const extension of params.schema.extensions) {
554
+ if (!PUBLIC_ONLY_EXTENSIONS.includes(extension.name))
555
+ continue;
556
+ const declarations = extensionDeclarations({ schema: params.schema, name: extension.name });
557
+ for (const model of params.schema.models) {
558
+ if (Object.hasOwn(declarations, model.name))
559
+ assertExtensionModelPlacement({ namespace: model.namespace, dialect });
560
+ }
561
+ }
562
+ }
563
+ function isPlainObject(value) {
564
+ return typeof value === "object" && value !== null && !Array.isArray(value);
565
+ }
566
+ function mergeExtensionDeclarations(params) {
567
+ if (params.extension !== undefined && PUBLIC_ONLY_EXTENSIONS.includes(params.extension) && Array.isArray(params.base.fields) && Array.isArray(params.next.fields)) {
568
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: `Repeated index-bearing ${params.extension} declarations are unsupported; use one common-configuration multi-field declaration`, meta: { rule: "repeated-extension-declaration", extension: params.extension } });
569
+ }
570
+ const out = { ...params.base };
571
+ for (const [key, value] of Object.entries(params.next)) {
572
+ const existing = out[key];
573
+ out[key] = isPlainObject(existing) && isPlainObject(value) ? { ...existing, ...value } : value;
574
+ }
575
+ return out;
576
+ }
577
+ function extensionDeclarations(params) {
578
+ const decl = params.schema.extensions.find((entry) => entry.name === params.name);
579
+ if (decl === undefined)
580
+ return {};
581
+ const models = decl.options["models"];
582
+ if (models === null || typeof models !== "object" || Array.isArray(models))
583
+ return {};
584
+ return models;
585
+ }
586
+
587
+ // packages/schema/src/model-sql-namespace.ts
588
+ function modelSqlNamespace(params) {
589
+ const { schema, model } = params;
590
+ if (model.namespace !== undefined || (params.dialect ?? providerToDialect({ provider: schema.datasource.provider })) !== "postgres")
591
+ return model.namespace;
592
+ const protectedModel = model.rls !== undefined && model.rls !== "unprotected";
593
+ const throughParent = schema.models.some((child) => {
594
+ const rule = child.rls;
595
+ return rule !== null && typeof rule === "object" && "through" in rule && child.relations.some((relation) => relation.name === rule.through && relation.targetModel === model.name);
596
+ });
597
+ return protectedModel || throughParent || hasPublicExtensionDeclaration({ schema, model: model.name }) ? SQL_NAMESPACES.postgres : undefined;
598
+ }
340
599
  // packages/schema/src/migration-objects.ts
341
600
  function sqlObjectKey(params) {
342
601
  const object = params.identity;
@@ -536,32 +795,6 @@ function assertIndexOperatorClasses(params) {
536
795
  throw new VibeError({ 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: field.name } });
537
796
  }
538
797
  }
539
- // packages/schema/src/ir.ts
540
- var DIALECTS = ["postgres", "sqlite", "mysql"];
541
- function providerToDialect(params) {
542
- const { provider } = params;
543
- switch (provider) {
544
- case "postgresql":
545
- case "pglite":
546
- return "postgres";
547
- case "sqlite":
548
- return "sqlite";
549
- case "mysql":
550
- return "mysql";
551
- }
552
- }
553
- function isEnumType(fieldType) {
554
- return typeof fieldType === "object" && "enum" in fieldType;
555
- }
556
- function fieldTypeIdentity(params) {
557
- const { type } = params;
558
- return isEnumType(type) ? type.namespace === undefined || type.namespace === "public" ? `enum:${type.enum}` : JSON.stringify(["enum", type.namespace, type.enum]) : type;
559
- }
560
- var ENUM_STORAGES = ["database", "string"];
561
- function enumStorageOf(params) {
562
- return params.enumDef.storage ?? "database";
563
- }
564
-
565
798
  // packages/schema/src/module-schema.ts
566
799
  function prepareDatabaseModuleSchema(params) {
567
800
  const { schema, module } = params;
@@ -610,36 +843,121 @@ function isPredicateFieldRef(operand) {
610
843
  return operand.kind === "field";
611
844
  }
612
845
  function predicateFieldNames(params) {
613
- const seen = new Set;
846
+ return [...new Set(inspectPredicate(params).expressions.flatMap((expression) => expression.kind === "field" ? [expression.name] : []))];
847
+ }
848
+ function inspectPredicate(params) {
849
+ const expressions = [];
850
+ let checkOnly = false;
851
+ const expression = (node) => {
852
+ if (typeof node !== "object" || node === null)
853
+ throw invalidOperand();
854
+ switch (node.kind) {
855
+ case "field":
856
+ expressions.push(node);
857
+ return;
858
+ case "jsonText":
859
+ if (typeof node.field !== "object" || node.field === null || node.field.kind !== "field")
860
+ throw invalidOperand();
861
+ expressions.push(node);
862
+ checkOnly = true;
863
+ expression(node.field);
864
+ return;
865
+ case "length":
866
+ expressions.push(node);
867
+ checkOnly = true;
868
+ expression(node.expression);
869
+ return;
870
+ default:
871
+ throw invalidOperand();
872
+ }
873
+ };
614
874
  const walk = (node) => {
615
875
  switch (node.kind) {
616
876
  case "compare":
617
- seen.add(node.left.name);
618
- if (isPredicateFieldRef(node.right))
619
- seen.add(node.right.name);
877
+ expression(node.left);
878
+ if (typeof node.right !== "object" || node.right === null || node.right.kind !== "value")
879
+ expression(node.right);
620
880
  return;
621
881
  case "inList":
622
882
  case "isNull":
623
- seen.add(node.field.name);
883
+ expression(node.field);
624
884
  return;
625
885
  case "and":
626
886
  case "or":
627
887
  for (const operand of node.operands)
628
888
  walk(operand);
629
889
  return;
890
+ case "isTrue":
891
+ checkOnly = true;
892
+ walk(node.operand);
893
+ return;
630
894
  case "not":
631
895
  walk(node.operand);
632
896
  return;
633
897
  }
634
898
  };
635
899
  walk(params.predicate);
636
- return [...seen];
900
+ return { expressions, checkOnly };
901
+ }
902
+ function invalidOperand() {
903
+ return new VibeError({
904
+ code: "VIBE_SCHEMA_INVALID",
905
+ message: "A schema predicate operand must be a field, jsonText or length expression, or a literal on the right of a comparison",
906
+ meta: { rule: "predicate-operand-invalid" }
907
+ });
908
+ }
909
+ function assertPredicateCapabilities(params) {
910
+ if (!inspectPredicate(params).checkOnly)
911
+ return;
912
+ if (params.usage !== "check")
913
+ throw unsupportedCapability({ dialect: params.dialect, feature: "schema CHECK expressions outside CHECK constraints" });
914
+ if (!DIALECT_CAPABILITIES[params.dialect].schemaCheckExpressions)
915
+ throw unsupportedCapability({ dialect: params.dialect, feature: "schemaCheckExpressions" });
637
916
  }
638
917
  function predicateKey(params) {
918
+ if (inspectPredicate(params).checkOnly) {
919
+ const scalar = (node) => {
920
+ switch (node.kind) {
921
+ case "field":
922
+ return ["field", node.name];
923
+ case "jsonText":
924
+ return ["jsonText", node.field.name, node.path];
925
+ case "length":
926
+ return ["length", scalar(node.expression)];
927
+ }
928
+ };
929
+ const key = (node) => {
930
+ switch (node.kind) {
931
+ case "compare":
932
+ return [node.kind, node.op, scalar(node.left), node.right.kind === "value" ? ["value", node.right.value] : scalar(node.right)];
933
+ case "inList":
934
+ return [node.kind, node.field.name, node.negate === true, node.values.map((value) => value.value)];
935
+ case "isNull":
936
+ return [node.kind, node.field.name, node.negate === true];
937
+ case "and":
938
+ case "or":
939
+ return [node.kind, node.operands.map(key)];
940
+ case "not":
941
+ case "isTrue":
942
+ return [node.kind, key(node.operand)];
943
+ }
944
+ };
945
+ return `check:${JSON.stringify(key(params.predicate))}`;
946
+ }
947
+ const expression = (node) => {
948
+ switch (node.kind) {
949
+ case "field":
950
+ return `f:${node.name}`;
951
+ case "jsonText":
952
+ return `jsonText(${JSON.stringify([node.field.name, node.path])})`;
953
+ case "length":
954
+ return `length(${expression(node.expression)})`;
955
+ }
956
+ };
639
957
  const encode = (node) => {
640
958
  switch (node.kind) {
641
959
  case "compare":
642
- return `cmp(${node.op},${node.left.name},${isPredicateFieldRef(node.right) ? `f:${node.right.name}` : `v:${typeof node.right.value}:${String(node.right.value)}`})`;
960
+ return `cmp(${node.op},${node.left.kind === "field" ? node.left.name : expression(node.left)},${node.right.kind === "value" ? `v:${typeof node.right.value}:${String(node.right.value)}` : expression(node.right)})`;
643
961
  case "inList":
644
962
  return `in${node.negate === true ? "!" : ""}(${node.field.name},[${node.values.map((entry) => `${typeof entry.value}:${String(entry.value)}`).join("|")}])`;
645
963
  case "isNull":
@@ -649,6 +967,8 @@ function predicateKey(params) {
649
967
  return `${node.kind}(${node.operands.map(encode).join(",")})`;
650
968
  case "not":
651
969
  return `not(${encode(node.operand)})`;
970
+ case "isTrue":
971
+ return `isTrue(${encode(node.operand)})`;
652
972
  }
653
973
  };
654
974
  return encode(params.predicate);
@@ -775,172 +1095,6 @@ function exactDecimalLiteral(params) {
775
1095
  const mantissa = text.replace(/[eE].*$/, "").replace("-", "").replace(".", "").replace(/^0+/, "");
776
1096
  return mantissa.length <= 15 ? Number(text) : text;
777
1097
  }
778
- // packages/schema/src/reserved.ts
779
- var RESERVED_DELEGATE_NAMES = [
780
- "findMany",
781
- "findPage",
782
- "findFirst",
783
- "findUnique",
784
- "findFirstOrThrow",
785
- "findUniqueOrThrow",
786
- "create",
787
- "createMany",
788
- "createManyAndReturn",
789
- "update",
790
- "updateMany",
791
- "updateManyAndReturn",
792
- "upsert",
793
- "upsertMany",
794
- "delete",
795
- "deleteMany",
796
- "count",
797
- "aggregate",
798
- "groupBy",
799
- "fields"
800
- ];
801
- var RESERVED_CLIENT_NAMES = [
802
- "$connect",
803
- "$disconnect",
804
- "$transaction",
805
- "$querySql",
806
- "$executeSql",
807
- "$queryRaw",
808
- "$queryRawUnsafe",
809
- "$executeRaw",
810
- "$executeRawUnsafe",
811
- "$on",
812
- "$extends",
813
- "$defineView",
814
- "$withPolicy",
815
- "$bypassPolicy",
816
- "$scoped",
817
- "$readOnly",
818
- "$models",
819
- "$withContext",
820
- "$advisoryLock",
821
- "$tryAdvisoryLock"
822
- ];
823
- var RESERVED_COLUMN_PREFIX = "__vibe_";
824
- function isReservedDelegateName(params) {
825
- return RESERVED_DELEGATE_NAMES.includes(params.name);
826
- }
827
- function isReservedClientName(params) {
828
- const withPrefix = params.name.startsWith("$") ? params.name : `$${params.name}`;
829
- return RESERVED_CLIENT_NAMES.includes(withPrefix);
830
- }
831
- // packages/schema/src/extension.ts
832
- function stableSerialize(value) {
833
- if (value === null || typeof value !== "object")
834
- return JSON.stringify(value) ?? "undefined";
835
- if (Array.isArray(value))
836
- return `[${value.map(stableSerialize).join(",")}]`;
837
- const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== undefined).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, entryValue]) => `${JSON.stringify(key)}:${stableSerialize(entryValue)}`);
838
- return `{${entries.join(",")}}`;
839
- }
840
- function extensionOptionsHash(params) {
841
- const text = stableSerialize(params.options);
842
- let hash = 2166136261;
843
- for (let index = 0;index < text.length; index += 1) {
844
- hash ^= text.charCodeAt(index);
845
- hash = Math.imul(hash, 16777619);
846
- }
847
- return (hash >>> 0).toString(16).padStart(8, "0");
848
- }
849
- function extensionManifestEntry(params) {
850
- const { extension } = params;
851
- return {
852
- name: extension.name,
853
- version: extension.version,
854
- optionsHash: extensionOptionsHash({ options: extension.options })
855
- };
856
- }
857
- var EXTENSION_NAME_PATTERN = /^[a-z][a-zA-Z0-9]*$/;
858
- function defineExtension(extension) {
859
- const { name } = extension;
860
- if (!EXTENSION_NAME_PATTERN.test(name)) {
861
- throw new VibeError({
862
- code: "VIBE_CONFIG",
863
- message: `extension name "${name}" is invalid \u2014 must match ${EXTENSION_NAME_PATTERN} (it becomes a client identifier)`,
864
- meta: { name }
865
- });
866
- }
867
- if (RESERVED_DELEGATE_NAMES.includes(name)) {
868
- throw new VibeError({
869
- code: "VIBE_CONFIG",
870
- message: `extension name "${name}" collides with a core delegate method`,
871
- meta: { name }
872
- });
873
- }
874
- if (RESERVED_CLIENT_NAMES.includes(`$${name}`)) {
875
- throw new VibeError({
876
- code: "VIBE_CONFIG",
877
- message: `extension name "${name}" collides with the core client method "$${name}"`,
878
- meta: { name }
879
- });
880
- }
881
- return Object.freeze(extension);
882
- }
883
- function buildExtensionRegistry(params) {
884
- const byName = new Map;
885
- const modelAttributes = new Map;
886
- const fieldTypes = new Map;
887
- for (const extension of params.extensions) {
888
- if (byName.has(extension.name)) {
889
- throw new VibeError({
890
- code: "VIBE_CONFIG",
891
- message: `duplicate extension name "${extension.name}" in the configured extension list`,
892
- meta: { name: extension.name }
893
- });
894
- }
895
- byName.set(extension.name, extension);
896
- for (const attribute of extension.schema?.modelAttributes ?? []) {
897
- const existing = modelAttributes.get(attribute.name);
898
- if (existing !== undefined) {
899
- throw new VibeError({
900
- code: "VIBE_CONFIG",
901
- message: `model attribute "@@${attribute.name}" is claimed by both "${existing.extension.name}" and "${extension.name}"`,
902
- meta: { attribute: attribute.name }
903
- });
904
- }
905
- modelAttributes.set(attribute.name, { extension, attribute });
906
- }
907
- for (const fieldType of extension.schema?.fieldTypes ?? []) {
908
- const existing = fieldTypes.get(fieldType.name);
909
- if (existing !== undefined) {
910
- throw new VibeError({
911
- code: "VIBE_CONFIG",
912
- message: `field type "${fieldType.name}" is claimed by both "${existing.extension.name}" and "${extension.name}"`,
913
- meta: { fieldType: fieldType.name }
914
- });
915
- }
916
- fieldTypes.set(fieldType.name, { extension, fieldType });
917
- }
918
- }
919
- return { extensions: params.extensions, byName, modelAttributes, fieldTypes };
920
- }
921
- var EMPTY_EXTENSION_REGISTRY = buildExtensionRegistry({
922
- extensions: []
923
- });
924
- function isPlainObject(value) {
925
- return typeof value === "object" && value !== null && !Array.isArray(value);
926
- }
927
- function mergeExtensionDeclarations(params) {
928
- const out = { ...params.base };
929
- for (const [key, value] of Object.entries(params.next)) {
930
- const existing = out[key];
931
- out[key] = isPlainObject(existing) && isPlainObject(value) ? { ...existing, ...value } : value;
932
- }
933
- return out;
934
- }
935
- function extensionDeclarations(params) {
936
- const decl = params.schema.extensions.find((entry) => entry.name === params.name);
937
- if (decl === undefined)
938
- return {};
939
- const models = decl.options["models"];
940
- if (models === null || typeof models !== "object" || Array.isArray(models))
941
- return {};
942
- return models;
943
- }
944
1098
  // packages/schema/src/computed.ts
945
1099
  function buildComputedManifest(params) {
946
1100
  const { computed } = params;
@@ -1097,6 +1251,80 @@ function scalarListDefault(params) {
1097
1251
  });
1098
1252
  }
1099
1253
 
1254
+ // packages/schema/src/json-schema-ref.ts
1255
+ var EXPORT_NAME = /^[$_\p{ID_Start}][$_\u200c\u200d\p{ID_Continue}]*$/u;
1256
+ var REFERENCE_ANNOTATION = /@(schema|type)\(\s*([$_\p{ID_Start}][$_\u200c\u200d\p{ID_Continue}]*)\s+from\s+("(?:[^"\\\r\n]|\\.)*")\s*\)/gu;
1257
+ function annotations(params) {
1258
+ const matches = [...params.documentation.matchAll(REFERENCE_ANNOTATION)].filter((match) => match[1] === params.kind);
1259
+ const remaining = params.documentation.replace(REFERENCE_ANNOTATION, "");
1260
+ const malformed = remaining.match(params.kind === "schema" ? /@schema(?=\s*\(|\s|$)/gu : /@type(?=\s*\(|\s|$)/gu) ?? [];
1261
+ return { matches, count: matches.length + malformed.length };
1262
+ }
1263
+ function hasJsonSchemaAnnotation(params) {
1264
+ return annotations({ documentation: params.documentation ?? "", kind: "schema" }).count > 0;
1265
+ }
1266
+ function jsonTypeAnnotationIssue(params) {
1267
+ const refs = annotations({ documentation: params.documentation ?? "", kind: "type" });
1268
+ if (refs.count === 0)
1269
+ return;
1270
+ if (refs.count > 1)
1271
+ return "A field may declare only one assertion-only JSON type";
1272
+ const match = refs.matches[0];
1273
+ if (match === undefined)
1274
+ return 'Expected @type(Name from "specifier")';
1275
+ return jsonSchemaRefIssue({ value: { name: match[2], from: decodePrismaString({ raw: match[3] }) } });
1276
+ }
1277
+ function jsonTypeAnnotationRef(params) {
1278
+ const issue = jsonTypeAnnotationIssue(params);
1279
+ if (issue !== undefined)
1280
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: issue, meta: { issues: [{ rule: "invalid-json-type-ref", message: issue }] } });
1281
+ const match = annotations({ documentation: params.documentation ?? "", kind: "type" }).matches[0];
1282
+ return match === undefined ? undefined : assertJsonSchemaRef({ value: { name: match[2], from: decodePrismaString({ raw: match[3] }) } });
1283
+ }
1284
+ function jsonSchemaRefIssue(params) {
1285
+ const { value } = params;
1286
+ if (typeof value !== "object" || value === null || Array.isArray(value))
1287
+ return "JSON schema reference must be { name, from }; validators belong in the referenced export";
1288
+ const keys = Reflect.ownKeys(value);
1289
+ if (keys.length !== 2 || !keys.includes("name") || !keys.includes("from"))
1290
+ return "JSON schema reference accepts only name and from";
1291
+ const name = Object.getOwnPropertyDescriptor(value, "name");
1292
+ const from = Object.getOwnPropertyDescriptor(value, "from");
1293
+ if (name?.enumerable !== true || !("value" in name) || typeof name.value !== "string" || !EXPORT_NAME.test(name.value) || name.value === "default")
1294
+ return "JSON schema name must be a valid named export, not default";
1295
+ if (from?.enumerable !== true || !("value" in from) || typeof from.value !== "string" || from.value.trim().length === 0 || /[\u0000-\u001f\u007f]/u.test(from.value))
1296
+ return "JSON schema from must be a non-empty import specifier without control characters";
1297
+ return;
1298
+ }
1299
+ function assertJsonSchemaRef(params) {
1300
+ const issue = jsonSchemaRefIssue(params);
1301
+ if (issue !== undefined)
1302
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: issue, meta: { issues: [{ rule: "invalid-json-schema-ref", message: issue }] } });
1303
+ const value = params.value;
1304
+ return Object.freeze({ name: value.name, from: value.from });
1305
+ }
1306
+ function normalizeJsonSchemaRef(params) {
1307
+ const { documentation, reference } = params;
1308
+ const jsonType = jsonTypeAnnotationRef({ documentation });
1309
+ const refs = annotations({ documentation: documentation ?? "", kind: "schema" });
1310
+ let jsonSchema = reference === undefined ? undefined : assertJsonSchemaRef({ value: reference });
1311
+ let remaining = documentation;
1312
+ if (refs.count > 1 || refs.count > 0 && jsonSchema !== undefined)
1313
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "A field may declare only one JSON schema reference", meta: { issues: [{ rule: "duplicate-json-schema-ref" }] } });
1314
+ if (refs.count === 1) {
1315
+ const match = refs.matches[0];
1316
+ if (match === undefined)
1317
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: 'Expected @schema(Name from "specifier")', meta: { issues: [{ rule: "invalid-json-schema-ref" }] } });
1318
+ jsonSchema = assertJsonSchemaRef({ value: { name: match[2], from: decodePrismaString({ raw: match[3] }) } });
1319
+ remaining = documentation?.replace(match[0], "").trim();
1320
+ if (remaining === "")
1321
+ remaining = undefined;
1322
+ }
1323
+ if (jsonSchema !== undefined && jsonType !== undefined)
1324
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "JSON schema validation and assertion-only @type cannot be combined on one field", meta: { issues: [{ rule: "conflicting-json-schema-ref" }] } });
1325
+ return { ...remaining === undefined ? {} : { documentation: remaining }, ...jsonSchema === undefined ? {} : { jsonSchema } };
1326
+ }
1327
+
1100
1328
  // packages/schema/src/m2m.ts
1101
1329
  function implicitJoinTable(params) {
1102
1330
  const sorted = [params.modelA, params.modelB].sort();
@@ -1122,6 +1350,8 @@ var RESERVED_ENUM_NAMES = [
1122
1350
  "SqlColumn",
1123
1351
  "SqlFragment",
1124
1352
  "ResultDecoder",
1353
+ "ResultReadDecoder",
1354
+ "SqlQueryResult",
1125
1355
  "ExtendedDateTime",
1126
1356
  "enumValidators",
1127
1357
  "enumValidator",
@@ -1178,6 +1408,9 @@ var RESERVED_ENUM_NAMES = [
1178
1408
  "InputJsonValue",
1179
1409
  "InputJsonArray",
1180
1410
  "InputJsonObject",
1411
+ "BoundJsonFilter",
1412
+ "BoundJsonFilterCommon",
1413
+ "BoundJsonNullableFilter",
1181
1414
  "VibeTelemetryOptions",
1182
1415
  "VibeTelemetryExporter",
1183
1416
  "VibeTelemetryOperationEvent",
@@ -1874,6 +2107,23 @@ function validateModel(params) {
1874
2107
  });
1875
2108
  }
1876
2109
  memberNames.add(field.name);
2110
+ const typedIssue = jsonTypeAnnotationIssue({ documentation: field.documentation });
2111
+ if (typedIssue !== undefined)
2112
+ issues.push({ rule: "invalid-json-type-ref", message: typedIssue, model: model.name, field: field.name });
2113
+ const hasType = typedIssue === undefined && jsonTypeAnnotationRef({ documentation: field.documentation }) !== undefined;
2114
+ if (hasType && field.type !== "Json")
2115
+ issues.push({ rule: "json-type-non-json", message: "JSON type assertions are only valid on Json fields", model: model.name, field: field.name });
2116
+ if (field.jsonSchema !== undefined) {
2117
+ const issue = jsonSchemaRefIssue({ value: field.jsonSchema });
2118
+ if (issue !== undefined)
2119
+ issues.push({ rule: "invalid-json-schema-ref", message: issue, model: model.name, field: field.name });
2120
+ if (field.type !== "Json" || field.isUnsupported === true)
2121
+ issues.push({ rule: "json-schema-non-json", message: "JSON schemas are only valid on queryable Json fields", model: model.name, field: field.name });
2122
+ if (hasType)
2123
+ issues.push({ rule: "conflicting-json-schema-ref", message: "A field cannot declare both @type and a JSON schema", model: model.name, field: field.name });
2124
+ }
2125
+ if (hasJsonSchemaAnnotation({ documentation: field.documentation }))
2126
+ issues.push({ rule: "unnormalized-json-schema-ref", message: "Normalize @schema documentation into FieldIR.jsonSchema before consuming SchemaIR", model: model.name, field: field.name });
1877
2127
  const typeIdentity = field.nativeTypeIdentity;
1878
2128
  if (typeIdentity !== undefined && (typeIdentity === null || typeof typeIdentity !== "object" || field.isUnsupported !== true || typeof typeIdentity.source !== "string" || typeIdentity.source !== field.nativeType || typeof typeIdentity.namespace !== "string" || typeIdentity.namespace.length === 0 || typeIdentity.namespace.includes("\x00"))) {
1879
2129
  issues.push({ rule: "invalid-native-type-identity", message: `PostgreSQL type identity on "${model.name}.${field.name}" must be bound to its unsupported declaration`, model: model.name, field: field.name });
@@ -2117,6 +2367,8 @@ function validateModel(params) {
2117
2367
  model,
2118
2368
  fieldNames,
2119
2369
  site: `CHECK "${check.name}"`,
2370
+ usage: "check",
2371
+ dialect: providerToDialect({ provider: params.schema.datasource.provider }),
2120
2372
  issues
2121
2373
  });
2122
2374
  }
@@ -2138,13 +2390,55 @@ function validateModel(params) {
2138
2390
  model,
2139
2391
  fieldNames,
2140
2392
  site: `partial index on [${index.fields.map((entry) => entry.name).join(", ")}]`,
2393
+ usage: "indexPredicate",
2394
+ dialect: providerToDialect({ provider: params.schema.datasource.provider }),
2141
2395
  issues
2142
2396
  });
2143
2397
  }
2144
2398
  }
2399
+ for (const exclusion of model.exclusions ?? []) {
2400
+ const predicate = exclusion.source?.where;
2401
+ if (predicate !== undefined)
2402
+ validatePredicateFields({ predicate, model, fieldNames, site: `exclusion "${exclusion.name}"`, usage: "exclusionPredicate", dialect: providerToDialect({ provider: params.schema.datasource.provider }), issues });
2403
+ }
2145
2404
  }
2146
2405
  function validatePredicateFields(params) {
2147
2406
  const { predicate, model, fieldNames, site, issues } = params;
2407
+ const inspection = inspectPredicate({ predicate });
2408
+ if (inspection.checkOnly && (params.usage !== "check" || !DIALECT_CAPABILITIES[params.dialect].schemaCheckExpressions)) {
2409
+ issues.push({ rule: "predicate-check-capability", message: `${site}: computed schema predicates require a PostgreSQL CHECK`, model: model.name });
2410
+ }
2411
+ const expressionType = (expression) => {
2412
+ if (expression.kind === "field")
2413
+ return model.fields.find((field) => field.name === expression.name)?.type;
2414
+ if (expression.kind === "jsonText") {
2415
+ const field = model.fields.find((candidate) => candidate.name === expression.field.name);
2416
+ if (field !== undefined && (field.type !== "Json" || field.isUnsupported === true))
2417
+ issues.push({ rule: "predicate-type-mismatch", message: `${site}: jsonText requires a Json field`, model: model.name, field: field.name });
2418
+ if (!Array.isArray(expression.path) || Array.from(expression.path).some((segment) => typeof segment !== "string" || segment.includes("\x00")))
2419
+ issues.push({ rule: "predicate-json-path", message: `${site}: jsonText requires a string-only path without NUL bytes`, model: model.name });
2420
+ return "String";
2421
+ }
2422
+ if (expressionType(expression.expression) !== "String")
2423
+ issues.push({ rule: "predicate-type-mismatch", message: `${site}: length requires a text expression`, model: model.name });
2424
+ return "Int";
2425
+ };
2426
+ const validateComputedComparisons = (node) => {
2427
+ if (node.kind === "compare") {
2428
+ if (node.left.kind !== "field" || node.right.kind !== "value" && node.right.kind !== "field") {
2429
+ const left = expressionType(node.left);
2430
+ const right = node.right.kind === "value" ? undefined : expressionType(node.right);
2431
+ if (left !== undefined && right !== undefined && left !== right)
2432
+ issues.push({ rule: "predicate-type-mismatch", message: `${site}: computed comparison operands must have the same scalar type`, model: model.name });
2433
+ if (node.right.kind === "value" && left === "String" && typeof node.right.value !== "string")
2434
+ issues.push({ rule: "predicate-type-mismatch", message: `${site}: a text expression requires a string literal`, model: model.name });
2435
+ }
2436
+ } else if (node.kind === "and" || node.kind === "or")
2437
+ node.operands.forEach(validateComputedComparisons);
2438
+ else if (node.kind === "not" || node.kind === "isTrue")
2439
+ validateComputedComparisons(node.operand);
2440
+ };
2441
+ validateComputedComparisons(predicate);
2148
2442
  for (const name of predicateFieldNames({ predicate })) {
2149
2443
  if (!fieldNames.has(name)) {
2150
2444
  issues.push({
@@ -2155,7 +2449,10 @@ function validatePredicateFields(params) {
2155
2449
  });
2156
2450
  continue;
2157
2451
  }
2158
- if (model.fields.find((field) => field.name === name)?.isList === true) {
2452
+ const referenced = model.fields.find((field) => field.name === name);
2453
+ if (inspection.checkOnly && referenced?.isUnsupported === true)
2454
+ issues.push({ rule: "predicate-unsupported-type", message: `${site}: computed predicates cannot reference unsupported fields`, model: model.name, field: name });
2455
+ if (referenced?.isList === true) {
2159
2456
  issues.push({
2160
2457
  rule: "predicate-list-field",
2161
2458
  message: `${site} on "${model.name}" compares scalar list "${name}" \u2014 use a raw expression for array operators`,
@@ -2167,6 +2464,8 @@ function validatePredicateFields(params) {
2167
2464
  }
2168
2465
  function assertValidSchemaIR(params) {
2169
2466
  const dialect = providerToDialect({ provider: params.schema.datasource.provider });
2467
+ assertNativeRlsAdmission({ schema: params.schema, dialect });
2468
+ assertExtensionPlacement({ schema: params.schema, dialect });
2170
2469
  assertIndexOperatorClasses({ schema: params.schema, dialect });
2171
2470
  for (const entry of [...params.schema.models, ...params.schema.enums])
2172
2471
  if (entry.namespace !== undefined)
@@ -2182,6 +2481,12 @@ function assertValidSchemaIR(params) {
2182
2481
  throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Enum reference namespace differs from its declared native type", meta: { rule: "enum-namespace", model: model.name, field: field.name } });
2183
2482
  }
2184
2483
  for (const model of params.schema.models) {
2484
+ for (const check of model.checks ?? [])
2485
+ if (check.predicate !== undefined)
2486
+ assertPredicateCapabilities({ predicate: check.predicate, dialect, usage: "check" });
2487
+ for (const index of model.indexes)
2488
+ if (index.wherePredicate !== undefined)
2489
+ assertPredicateCapabilities({ predicate: index.wherePredicate, dialect, usage: "indexPredicate" });
2185
2490
  if (model.exclusions !== undefined && !Array.isArray(model.exclusions))
2186
2491
  throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Exclusions must be an array", meta: { rule: "exclusion-definition" } });
2187
2492
  if ((model.exclusions?.length ?? 0) === 0)
@@ -2195,6 +2500,8 @@ function assertValidSchemaIR(params) {
2195
2500
  throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Invalid exclusion declaration", meta: { rule: "exclusion-definition" } });
2196
2501
  const source = exclusion.source;
2197
2502
  if (source !== undefined) {
2503
+ if (source.where !== undefined)
2504
+ assertPredicateCapabilities({ predicate: source.where, dialect, usage: "exclusionPredicate" });
2198
2505
  const references = [...source.keys.flatMap((key) => ("field" in key) ? [key.field] : key.fields), ...[source.range.lower, source.range.upper].flatMap((endpoint) => ("field" in endpoint) ? [endpoint.field] : []), ...source.where === undefined ? [] : predicateFieldNames({ predicate: source.where })];
2199
2506
  if (references.some((name) => !model.fields.some((field) => field.name === name && !field.isList)))
2200
2507
  throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Exclusion references an unknown or list field", meta: { rule: "exclusion-field", model: model.name } });
@@ -2206,9 +2513,6 @@ function assertValidSchemaIR(params) {
2206
2513
  }
2207
2514
  if (params.schema.migrationObjects !== undefined)
2208
2515
  assertMigrationObjects({ objects: params.schema.migrationObjects });
2209
- if (params.schema.rls !== undefined && !DIALECT_CAPABILITIES[dialect].nativeRowLevelSecurity) {
2210
- throw unsupportedCapability({ dialect, feature: "nativeRowLevelSecurity" });
2211
- }
2212
2516
  for (const model of params.schema.models) {
2213
2517
  const counters = model.fields.filter((field) => field.default?.kind === "autoincrement");
2214
2518
  if (dialect === "mysql" && counters.length > 1)
@@ -2454,9 +2758,14 @@ export {
2454
2758
  ScopedQueryError,
2455
2759
  VIBE_ERROR_CODES,
2456
2760
  VibeError,
2761
+ assertExtensionModelPlacement,
2762
+ assertExtensionPlacement,
2457
2763
  assertIdentifierFits,
2458
2764
  assertIndexOperatorClasses,
2765
+ assertJsonSchemaRef,
2459
2766
  assertMigrationObjects,
2767
+ assertNativeRlsAdmission,
2768
+ assertPredicateCapabilities,
2460
2769
  assertSqlNamespace,
2461
2770
  assertValidSchemaIR,
2462
2771
  buildComputedManifest,
@@ -2475,9 +2784,11 @@ export {
2475
2784
  extensionOptionsHash,
2476
2785
  fieldTypeIdentity,
2477
2786
  fitDefaultName,
2787
+ hasJsonSchemaAnnotation,
2478
2788
  identifierLength,
2479
2789
  implicitJoinTable,
2480
2790
  implicitJoinTableObjectNames,
2791
+ inspectPredicate,
2481
2792
  internalError,
2482
2793
  invalidStoredData,
2483
2794
  isEnumType,
@@ -2487,7 +2798,12 @@ export {
2487
2798
  isReservedDelegateName,
2488
2799
  isSqlObjectDefinition,
2489
2800
  isSqlObjectIdentity,
2801
+ jsonSchemaRefIssue,
2802
+ jsonTypeAnnotationIssue,
2803
+ jsonTypeAnnotationRef,
2490
2804
  mergeExtensionDeclarations,
2805
+ modelSqlNamespace,
2806
+ normalizeJsonSchemaRef,
2491
2807
  postgresGeneratedObjectName,
2492
2808
  predicateFieldNames,
2493
2809
  predicateKey,
@@ -2506,4 +2822,4 @@ export {
2506
2822
  validateSchemaIR
2507
2823
  };
2508
2824
 
2509
- //# debugId=99BBBD6C3E58D9DD64756E2164756E21
2825
+ //# debugId=721E026706902A1F64756E2164756E21