@vibeorm/schema 4.0.0 → 4.2.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.
@@ -105,6 +105,14 @@ export type DialectCapabilities = {
105
105
  * the `Date` is written, and a read returns that time at `1970-01-01` UTC.
106
106
  */
107
107
  readonly timeOfDayTypes: readonly TimeOfDayType[];
108
+ /**
109
+ * How a `String @db.Uuid` field is stored. `"native"`: the engine's 128-bit
110
+ * uuid type, which refuses malformed input and compares case-insensitively.
111
+ * `"text"`: TEXT, like every native string type on that dialect — no format
112
+ * check, and comparison is byte-wise. `"unsupported"`: the field is refused
113
+ * when the schema is validated. Values are strings in JavaScript everywhere.
114
+ */
115
+ readonly uuidColumns: "native" | "text" | "unsupported";
108
116
  };
109
117
  export declare const POSTGRES_CAPABILITIES: DialectCapabilities;
110
118
  export declare const SQLITE_CAPABILITIES: DialectCapabilities;
package/dist/errors.d.ts CHANGED
@@ -58,6 +58,14 @@ export declare const VIBE_ERROR_CODES: readonly [
58
58
  "VIBE_RLS_CONTEXT",
59
59
  /** SQLSTATE 42501: insufficient privilege, including policy WITH CHECK failure. */
60
60
  "VIBE_PERMISSION_DENIED",
61
+ /**
62
+ * A known read was cancelled by its caller. Fixed metadata: reason:
63
+ * "caller-aborted", stage: "before-dispatch" | "waiting-for-connection" |
64
+ * "running", databaseCancelled: boolean. True requires the original
65
+ * statement's server cancellation failure, not just a signal being sent.
66
+ * Unrelated driver failures retain their original mapped codes.
67
+ */
68
+ "VIBE_CANCELLED",
61
69
  /** Driver/connection failure not mapped to a more specific code. */
62
70
  "VIBE_ADAPTER",
63
71
  /**
@@ -324,15 +324,32 @@ export declare function buildExtensionRegistry(params: {
324
324
  }): ExtensionRegistry;
325
325
  /** An empty registry — the default when no extensions are configured. */
326
326
  export declare const EMPTY_EXTENSION_REGISTRY: ExtensionRegistry;
327
+ /** Whether built-in declarations establish public-only physical ownership for this model. */
328
+ export declare function hasPublicExtensionDeclaration(params: {
329
+ schema: SchemaIR;
330
+ model: string;
331
+ }): boolean;
332
+ /** Refuse placement that built-in extension SQL and ownership cannot preserve. */
333
+ export declare function assertExtensionModelPlacement(params: {
334
+ namespace?: string;
335
+ dialect?: Dialect;
336
+ }): void;
337
+ /** Admit built-in extension declarations before generation, migration or runtime work. */
338
+ export declare function assertExtensionPlacement(params: {
339
+ schema: SchemaIR;
340
+ dialect?: Dialect;
341
+ }): void;
327
342
  /**
328
343
  * Merge two declaration fragments for the same (extension, model) — e.g. a
329
344
  * field-type lowering plus a model attribute. One level deep: object values
330
- * merge shallowly, everything else is last-wins. Used identically by the
331
- * .prisma parser and the TS DSL so both sources produce byte-identical IR.
345
+ * merge shallowly. Built-in index-bearing declarations cannot repeat their
346
+ * field list; custom extension merging retains its existing last-wins behavior.
347
+ * Used identically by the parser and TS DSL.
332
348
  */
333
349
  export declare function mergeExtensionDeclarations(params: {
334
350
  base: Readonly<Record<string, unknown>>;
335
351
  next: Readonly<Record<string, unknown>>;
352
+ extension?: string;
336
353
  }): Record<string, unknown>;
337
354
  /**
338
355
  * Per-model declarations one extension gathered from schema attributes,
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@
3
3
  * and the VibeError contract for VibeORM v2.
4
4
  */
5
5
  export { SQL_NAMESPACES, assertSqlNamespace, qualifiedSqlName, sqlNamespaceIssue } from "./namespaces.ts";
6
+ export { assertNativeRlsAdmission } from "./native-rls-admission.ts";
7
+ export { modelSqlNamespace } from "./model-sql-namespace.ts";
6
8
  export { assertIndexOperatorClasses } from "./index-operator-classes.ts";
7
9
  export type { DatabaseModuleOptions, DatabaseModuleManifest, DatabaseModuleArtifact } from "./database-module.ts";
8
10
  export { prepareDatabaseModuleSchema } from "./module-schema.ts";
@@ -19,7 +21,7 @@ export { READ_ONLY_REFUSAL_REASONS, ReadOnlyClientError, SCOPED_REFUSAL_REASONS,
19
21
  export type { ReadOnlyRefusalReason, ScopedRefusalReason, VibeErrorCode } from "./errors.ts";
20
22
  export { decodePrismaString, encodePrismaString, exactDecimalLiteral, exactIntegerLiteral } from "./prisma-literal.ts";
21
23
  export { RESERVED_CLIENT_NAMES, RESERVED_DELEGATE_NAMES, isReservedClientName, isReservedDelegateName, } from "./reserved.ts";
22
- export { EMPTY_EXTENSION_REGISTRY, buildExtensionRegistry, defineExtension, extensionDeclarations, extensionManifestEntry, extensionOptionsHash, mergeExtensionDeclarations, } from "./extension.ts";
24
+ export { assertExtensionModelPlacement, assertExtensionPlacement, EMPTY_EXTENSION_REGISTRY, buildExtensionRegistry, defineExtension, extensionDeclarations, extensionManifestEntry, extensionOptionsHash, mergeExtensionDeclarations, } from "./extension.ts";
23
25
  export type { ExtensionArtifact, ExtensionArtifactOwner, ExtensionAttributeArgs, ExtensionAttributeValue, ExtensionClientContribution, ExtensionClientPlane, ExtensionDoctorCheck, ExtensionDoctorHook, ExtensionDoctorReport, ExtensionFieldType, ExtensionImportSpec, ExtensionManifestEntry, ExtensionMigratePlane, ExtensionModelAttribute, ExtensionModelTypeNames, ExtensionRegistry, ExtensionRuntimePlane, ExtensionSchemaContext, ExtensionSqlExecutor, ExtensionSqlPlane, ExtensionWhereOperator, SqlFragmentPart, SqlFragmentSpec, VibeExtension, } from "./extension.ts";
24
26
  export { buildComputedManifest, validateComputedConfig } from "./computed.ts";
25
27
  export type { ComputedConfig, ComputedFieldSpec, ComputedManifestEntry } from "./computed.ts";
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"
@@ -278,7 +279,8 @@ var POSTGRES_CAPABILITIES = Object.freeze({
278
279
  rowLockStrengths: Object.freeze(["update", "noKeyUpdate", "share", "keyShare"]),
279
280
  rowLockWaitPolicies: Object.freeze(["wait", "nowait", "skipLocked"]),
280
281
  advisoryLocks: true,
281
- timeOfDayTypes: Object.freeze(["time", "timetz"])
282
+ timeOfDayTypes: Object.freeze(["time", "timetz"]),
283
+ uuidColumns: "native"
282
284
  });
283
285
  var SQLITE_CAPABILITIES = Object.freeze({
284
286
  fixedNamespaces: false,
@@ -305,7 +307,8 @@ var SQLITE_CAPABILITIES = Object.freeze({
305
307
  rowLockStrengths: Object.freeze([]),
306
308
  rowLockWaitPolicies: Object.freeze([]),
307
309
  advisoryLocks: false,
308
- timeOfDayTypes: Object.freeze([])
310
+ timeOfDayTypes: Object.freeze([]),
311
+ uuidColumns: "text"
309
312
  });
310
313
  var MYSQL_CAPABILITIES = Object.freeze({
311
314
  fixedNamespaces: false,
@@ -332,7 +335,8 @@ var MYSQL_CAPABILITIES = Object.freeze({
332
335
  rowLockStrengths: Object.freeze(["update", "share"]),
333
336
  rowLockWaitPolicies: Object.freeze(["wait", "nowait", "skipLocked"]),
334
337
  advisoryLocks: false,
335
- timeOfDayTypes: Object.freeze(["time"])
338
+ timeOfDayTypes: Object.freeze(["time"]),
339
+ uuidColumns: "unsupported"
336
340
  });
337
341
  var DIALECT_CAPABILITIES = Object.freeze({
338
342
  postgres: POSTGRES_CAPABILITIES,
@@ -340,6 +344,261 @@ var DIALECT_CAPABILITIES = Object.freeze({
340
344
  mysql: MYSQL_CAPABILITIES
341
345
  });
342
346
 
347
+ // packages/schema/src/ir.ts
348
+ var DIALECTS = ["postgres", "sqlite", "mysql"];
349
+ function providerToDialect(params) {
350
+ const { provider } = params;
351
+ switch (provider) {
352
+ case "postgresql":
353
+ case "pglite":
354
+ return "postgres";
355
+ case "sqlite":
356
+ return "sqlite";
357
+ case "mysql":
358
+ return "mysql";
359
+ }
360
+ }
361
+ function isEnumType(fieldType) {
362
+ return typeof fieldType === "object" && "enum" in fieldType;
363
+ }
364
+ function fieldTypeIdentity(params) {
365
+ const { type } = params;
366
+ return isEnumType(type) ? type.namespace === undefined || type.namespace === "public" ? `enum:${type.enum}` : JSON.stringify(["enum", type.namespace, type.enum]) : type;
367
+ }
368
+ var ENUM_STORAGES = ["database", "string"];
369
+ function enumStorageOf(params) {
370
+ return params.enumDef.storage ?? "database";
371
+ }
372
+
373
+ // packages/schema/src/native-rls-admission.ts
374
+ function assertNativeRlsAdmission(params) {
375
+ const { schema } = params;
376
+ if (schema.rls === undefined && !schema.models.some((model) => model.rls !== undefined))
377
+ return;
378
+ const dialect = params.dialect ?? providerToDialect({ provider: schema.datasource.provider });
379
+ if (!DIALECT_CAPABILITIES[dialect].nativeRowLevelSecurity) {
380
+ throw unsupportedCapability({ dialect, feature: "nativeRowLevelSecurity" });
381
+ }
382
+ for (const model of schema.models) {
383
+ const rule = model.rls;
384
+ if (rule === undefined || rule === "unprotected")
385
+ continue;
386
+ const parentName = rule !== null && typeof rule === "object" && "through" in rule ? model.relations.find((relation) => relation.name === rule.through)?.targetModel : undefined;
387
+ const parent = parentName === undefined ? undefined : schema.models.find((entry) => entry.name === parentName);
388
+ if (model.namespace !== undefined && model.namespace !== "public" || parent?.namespace !== undefined && parent.namespace !== "public") {
389
+ throw unsupportedCapability({
390
+ dialect,
391
+ feature: "fixedNamespaceNativeRls",
392
+ hint: "Native RLS protects public tables only, including deny-protected models and through-policy parents"
393
+ });
394
+ }
395
+ }
396
+ }
397
+ // packages/schema/src/reserved.ts
398
+ var RESERVED_DELEGATE_NAMES = [
399
+ "findMany",
400
+ "findPage",
401
+ "findFirst",
402
+ "findUnique",
403
+ "findFirstOrThrow",
404
+ "findUniqueOrThrow",
405
+ "create",
406
+ "createMany",
407
+ "createManyAndReturn",
408
+ "update",
409
+ "updateMany",
410
+ "updateManyAndReturn",
411
+ "upsert",
412
+ "upsertMany",
413
+ "delete",
414
+ "deleteMany",
415
+ "count",
416
+ "aggregate",
417
+ "groupBy",
418
+ "fields"
419
+ ];
420
+ var RESERVED_CLIENT_NAMES = [
421
+ "$connect",
422
+ "$disconnect",
423
+ "$transaction",
424
+ "$querySql",
425
+ "$querySqlResult",
426
+ "$executeSql",
427
+ "$queryRaw",
428
+ "$queryRawUnsafe",
429
+ "$executeRaw",
430
+ "$executeRawUnsafe",
431
+ "$on",
432
+ "$extends",
433
+ "$defineView",
434
+ "$withPolicy",
435
+ "$bypassPolicy",
436
+ "$scoped",
437
+ "$readOnly",
438
+ "$models",
439
+ "$withContext",
440
+ "$advisoryLock",
441
+ "$tryAdvisoryLock"
442
+ ];
443
+ var RESERVED_COLUMN_PREFIX = "__vibe_";
444
+ function isReservedDelegateName(params) {
445
+ return RESERVED_DELEGATE_NAMES.includes(params.name);
446
+ }
447
+ function isReservedClientName(params) {
448
+ const withPrefix = params.name.startsWith("$") ? params.name : `$${params.name}`;
449
+ return RESERVED_CLIENT_NAMES.includes(withPrefix);
450
+ }
451
+
452
+ // packages/schema/src/extension.ts
453
+ function stableSerialize(value) {
454
+ if (value === null || typeof value !== "object")
455
+ return JSON.stringify(value) ?? "undefined";
456
+ if (Array.isArray(value))
457
+ return `[${value.map(stableSerialize).join(",")}]`;
458
+ 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)}`);
459
+ return `{${entries.join(",")}}`;
460
+ }
461
+ function extensionOptionsHash(params) {
462
+ const text = stableSerialize(params.options);
463
+ let hash = 2166136261;
464
+ for (let index = 0;index < text.length; index += 1) {
465
+ hash ^= text.charCodeAt(index);
466
+ hash = Math.imul(hash, 16777619);
467
+ }
468
+ return (hash >>> 0).toString(16).padStart(8, "0");
469
+ }
470
+ function extensionManifestEntry(params) {
471
+ const { extension } = params;
472
+ return {
473
+ name: extension.name,
474
+ version: extension.version,
475
+ optionsHash: extensionOptionsHash({ options: extension.options })
476
+ };
477
+ }
478
+ var EXTENSION_NAME_PATTERN = /^[a-z][a-zA-Z0-9]*$/;
479
+ function defineExtension(extension) {
480
+ const { name } = extension;
481
+ if (!EXTENSION_NAME_PATTERN.test(name)) {
482
+ throw new VibeError({
483
+ code: "VIBE_CONFIG",
484
+ message: `extension name "${name}" is invalid \u2014 must match ${EXTENSION_NAME_PATTERN} (it becomes a client identifier)`,
485
+ meta: { name }
486
+ });
487
+ }
488
+ if (RESERVED_DELEGATE_NAMES.includes(name)) {
489
+ throw new VibeError({
490
+ code: "VIBE_CONFIG",
491
+ message: `extension name "${name}" collides with a core delegate method`,
492
+ meta: { name }
493
+ });
494
+ }
495
+ if (RESERVED_CLIENT_NAMES.includes(`$${name}`)) {
496
+ throw new VibeError({
497
+ code: "VIBE_CONFIG",
498
+ message: `extension name "${name}" collides with the core client method "$${name}"`,
499
+ meta: { name }
500
+ });
501
+ }
502
+ return Object.freeze(extension);
503
+ }
504
+ function buildExtensionRegistry(params) {
505
+ const byName = new Map;
506
+ const modelAttributes = new Map;
507
+ const fieldTypes = new Map;
508
+ for (const extension of params.extensions) {
509
+ if (byName.has(extension.name)) {
510
+ throw new VibeError({
511
+ code: "VIBE_CONFIG",
512
+ message: `duplicate extension name "${extension.name}" in the configured extension list`,
513
+ meta: { name: extension.name }
514
+ });
515
+ }
516
+ byName.set(extension.name, extension);
517
+ for (const attribute of extension.schema?.modelAttributes ?? []) {
518
+ const existing = modelAttributes.get(attribute.name);
519
+ if (existing !== undefined) {
520
+ throw new VibeError({
521
+ code: "VIBE_CONFIG",
522
+ message: `model attribute "@@${attribute.name}" is claimed by both "${existing.extension.name}" and "${extension.name}"`,
523
+ meta: { attribute: attribute.name }
524
+ });
525
+ }
526
+ modelAttributes.set(attribute.name, { extension, attribute });
527
+ }
528
+ for (const fieldType of extension.schema?.fieldTypes ?? []) {
529
+ const existing = fieldTypes.get(fieldType.name);
530
+ if (existing !== undefined) {
531
+ throw new VibeError({
532
+ code: "VIBE_CONFIG",
533
+ message: `field type "${fieldType.name}" is claimed by both "${existing.extension.name}" and "${extension.name}"`,
534
+ meta: { fieldType: fieldType.name }
535
+ });
536
+ }
537
+ fieldTypes.set(fieldType.name, { extension, fieldType });
538
+ }
539
+ }
540
+ return { extensions: params.extensions, byName, modelAttributes, fieldTypes };
541
+ }
542
+ var EMPTY_EXTENSION_REGISTRY = buildExtensionRegistry({
543
+ extensions: []
544
+ });
545
+ var PUBLIC_ONLY_EXTENSIONS = ["search", "vector", "trgm"];
546
+ function hasPublicExtensionDeclaration(params) {
547
+ return PUBLIC_ONLY_EXTENSIONS.some((name) => Object.hasOwn(extensionDeclarations({ schema: params.schema, name }), params.model));
548
+ }
549
+ function assertExtensionModelPlacement(params) {
550
+ if (params.namespace !== undefined && params.namespace !== "public") {
551
+ throw unsupportedCapability({ dialect: params.dialect ?? "postgres", feature: "fixedNamespaceExtensions", hint: "Search, vector and trigram extensions currently require public table placement" });
552
+ }
553
+ }
554
+ function assertExtensionPlacement(params) {
555
+ const dialect = params.dialect ?? providerToDialect({ provider: params.schema.datasource.provider });
556
+ for (const extension of params.schema.extensions) {
557
+ if (!PUBLIC_ONLY_EXTENSIONS.includes(extension.name))
558
+ continue;
559
+ const declarations = extensionDeclarations({ schema: params.schema, name: extension.name });
560
+ for (const model of params.schema.models) {
561
+ if (Object.hasOwn(declarations, model.name))
562
+ assertExtensionModelPlacement({ namespace: model.namespace, dialect });
563
+ }
564
+ }
565
+ }
566
+ function isPlainObject(value) {
567
+ return typeof value === "object" && value !== null && !Array.isArray(value);
568
+ }
569
+ function mergeExtensionDeclarations(params) {
570
+ if (params.extension !== undefined && PUBLIC_ONLY_EXTENSIONS.includes(params.extension) && Array.isArray(params.base.fields) && Array.isArray(params.next.fields)) {
571
+ 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 } });
572
+ }
573
+ const out = { ...params.base };
574
+ for (const [key, value] of Object.entries(params.next)) {
575
+ const existing = out[key];
576
+ out[key] = isPlainObject(existing) && isPlainObject(value) ? { ...existing, ...value } : value;
577
+ }
578
+ return out;
579
+ }
580
+ function extensionDeclarations(params) {
581
+ const decl = params.schema.extensions.find((entry) => entry.name === params.name);
582
+ if (decl === undefined)
583
+ return {};
584
+ const models = decl.options["models"];
585
+ if (models === null || typeof models !== "object" || Array.isArray(models))
586
+ return {};
587
+ return models;
588
+ }
589
+
590
+ // packages/schema/src/model-sql-namespace.ts
591
+ function modelSqlNamespace(params) {
592
+ const { schema, model } = params;
593
+ if (model.namespace !== undefined || (params.dialect ?? providerToDialect({ provider: schema.datasource.provider })) !== "postgres")
594
+ return model.namespace;
595
+ const protectedModel = model.rls !== undefined && model.rls !== "unprotected";
596
+ const throughParent = schema.models.some((child) => {
597
+ const rule = child.rls;
598
+ return rule !== null && typeof rule === "object" && "through" in rule && child.relations.some((relation) => relation.name === rule.through && relation.targetModel === model.name);
599
+ });
600
+ return protectedModel || throughParent || hasPublicExtensionDeclaration({ schema, model: model.name }) ? SQL_NAMESPACES.postgres : undefined;
601
+ }
343
602
  // packages/schema/src/migration-objects.ts
344
603
  function sqlObjectKey(params) {
345
604
  const object = params.identity;
@@ -539,32 +798,6 @@ function assertIndexOperatorClasses(params) {
539
798
  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 } });
540
799
  }
541
800
  }
542
- // packages/schema/src/ir.ts
543
- var DIALECTS = ["postgres", "sqlite", "mysql"];
544
- function providerToDialect(params) {
545
- const { provider } = params;
546
- switch (provider) {
547
- case "postgresql":
548
- case "pglite":
549
- return "postgres";
550
- case "sqlite":
551
- return "sqlite";
552
- case "mysql":
553
- return "mysql";
554
- }
555
- }
556
- function isEnumType(fieldType) {
557
- return typeof fieldType === "object" && "enum" in fieldType;
558
- }
559
- function fieldTypeIdentity(params) {
560
- const { type } = params;
561
- return isEnumType(type) ? type.namespace === undefined || type.namespace === "public" ? `enum:${type.enum}` : JSON.stringify(["enum", type.namespace, type.enum]) : type;
562
- }
563
- var ENUM_STORAGES = ["database", "string"];
564
- function enumStorageOf(params) {
565
- return params.enumDef.storage ?? "database";
566
- }
567
-
568
801
  // packages/schema/src/module-schema.ts
569
802
  function prepareDatabaseModuleSchema(params) {
570
803
  const { schema, module } = params;
@@ -865,173 +1098,6 @@ function exactDecimalLiteral(params) {
865
1098
  const mantissa = text.replace(/[eE].*$/, "").replace("-", "").replace(".", "").replace(/^0+/, "");
866
1099
  return mantissa.length <= 15 ? Number(text) : text;
867
1100
  }
868
- // packages/schema/src/reserved.ts
869
- var RESERVED_DELEGATE_NAMES = [
870
- "findMany",
871
- "findPage",
872
- "findFirst",
873
- "findUnique",
874
- "findFirstOrThrow",
875
- "findUniqueOrThrow",
876
- "create",
877
- "createMany",
878
- "createManyAndReturn",
879
- "update",
880
- "updateMany",
881
- "updateManyAndReturn",
882
- "upsert",
883
- "upsertMany",
884
- "delete",
885
- "deleteMany",
886
- "count",
887
- "aggregate",
888
- "groupBy",
889
- "fields"
890
- ];
891
- var RESERVED_CLIENT_NAMES = [
892
- "$connect",
893
- "$disconnect",
894
- "$transaction",
895
- "$querySql",
896
- "$querySqlResult",
897
- "$executeSql",
898
- "$queryRaw",
899
- "$queryRawUnsafe",
900
- "$executeRaw",
901
- "$executeRawUnsafe",
902
- "$on",
903
- "$extends",
904
- "$defineView",
905
- "$withPolicy",
906
- "$bypassPolicy",
907
- "$scoped",
908
- "$readOnly",
909
- "$models",
910
- "$withContext",
911
- "$advisoryLock",
912
- "$tryAdvisoryLock"
913
- ];
914
- var RESERVED_COLUMN_PREFIX = "__vibe_";
915
- function isReservedDelegateName(params) {
916
- return RESERVED_DELEGATE_NAMES.includes(params.name);
917
- }
918
- function isReservedClientName(params) {
919
- const withPrefix = params.name.startsWith("$") ? params.name : `$${params.name}`;
920
- return RESERVED_CLIENT_NAMES.includes(withPrefix);
921
- }
922
- // packages/schema/src/extension.ts
923
- function stableSerialize(value) {
924
- if (value === null || typeof value !== "object")
925
- return JSON.stringify(value) ?? "undefined";
926
- if (Array.isArray(value))
927
- return `[${value.map(stableSerialize).join(",")}]`;
928
- 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)}`);
929
- return `{${entries.join(",")}}`;
930
- }
931
- function extensionOptionsHash(params) {
932
- const text = stableSerialize(params.options);
933
- let hash = 2166136261;
934
- for (let index = 0;index < text.length; index += 1) {
935
- hash ^= text.charCodeAt(index);
936
- hash = Math.imul(hash, 16777619);
937
- }
938
- return (hash >>> 0).toString(16).padStart(8, "0");
939
- }
940
- function extensionManifestEntry(params) {
941
- const { extension } = params;
942
- return {
943
- name: extension.name,
944
- version: extension.version,
945
- optionsHash: extensionOptionsHash({ options: extension.options })
946
- };
947
- }
948
- var EXTENSION_NAME_PATTERN = /^[a-z][a-zA-Z0-9]*$/;
949
- function defineExtension(extension) {
950
- const { name } = extension;
951
- if (!EXTENSION_NAME_PATTERN.test(name)) {
952
- throw new VibeError({
953
- code: "VIBE_CONFIG",
954
- message: `extension name "${name}" is invalid \u2014 must match ${EXTENSION_NAME_PATTERN} (it becomes a client identifier)`,
955
- meta: { name }
956
- });
957
- }
958
- if (RESERVED_DELEGATE_NAMES.includes(name)) {
959
- throw new VibeError({
960
- code: "VIBE_CONFIG",
961
- message: `extension name "${name}" collides with a core delegate method`,
962
- meta: { name }
963
- });
964
- }
965
- if (RESERVED_CLIENT_NAMES.includes(`$${name}`)) {
966
- throw new VibeError({
967
- code: "VIBE_CONFIG",
968
- message: `extension name "${name}" collides with the core client method "$${name}"`,
969
- meta: { name }
970
- });
971
- }
972
- return Object.freeze(extension);
973
- }
974
- function buildExtensionRegistry(params) {
975
- const byName = new Map;
976
- const modelAttributes = new Map;
977
- const fieldTypes = new Map;
978
- for (const extension of params.extensions) {
979
- if (byName.has(extension.name)) {
980
- throw new VibeError({
981
- code: "VIBE_CONFIG",
982
- message: `duplicate extension name "${extension.name}" in the configured extension list`,
983
- meta: { name: extension.name }
984
- });
985
- }
986
- byName.set(extension.name, extension);
987
- for (const attribute of extension.schema?.modelAttributes ?? []) {
988
- const existing = modelAttributes.get(attribute.name);
989
- if (existing !== undefined) {
990
- throw new VibeError({
991
- code: "VIBE_CONFIG",
992
- message: `model attribute "@@${attribute.name}" is claimed by both "${existing.extension.name}" and "${extension.name}"`,
993
- meta: { attribute: attribute.name }
994
- });
995
- }
996
- modelAttributes.set(attribute.name, { extension, attribute });
997
- }
998
- for (const fieldType of extension.schema?.fieldTypes ?? []) {
999
- const existing = fieldTypes.get(fieldType.name);
1000
- if (existing !== undefined) {
1001
- throw new VibeError({
1002
- code: "VIBE_CONFIG",
1003
- message: `field type "${fieldType.name}" is claimed by both "${existing.extension.name}" and "${extension.name}"`,
1004
- meta: { fieldType: fieldType.name }
1005
- });
1006
- }
1007
- fieldTypes.set(fieldType.name, { extension, fieldType });
1008
- }
1009
- }
1010
- return { extensions: params.extensions, byName, modelAttributes, fieldTypes };
1011
- }
1012
- var EMPTY_EXTENSION_REGISTRY = buildExtensionRegistry({
1013
- extensions: []
1014
- });
1015
- function isPlainObject(value) {
1016
- return typeof value === "object" && value !== null && !Array.isArray(value);
1017
- }
1018
- function mergeExtensionDeclarations(params) {
1019
- const out = { ...params.base };
1020
- for (const [key, value] of Object.entries(params.next)) {
1021
- const existing = out[key];
1022
- out[key] = isPlainObject(existing) && isPlainObject(value) ? { ...existing, ...value } : value;
1023
- }
1024
- return out;
1025
- }
1026
- function extensionDeclarations(params) {
1027
- const decl = params.schema.extensions.find((entry) => entry.name === params.name);
1028
- if (decl === undefined)
1029
- return {};
1030
- const models = decl.options["models"];
1031
- if (models === null || typeof models !== "object" || Array.isArray(models))
1032
- return {};
1033
- return models;
1034
- }
1035
1101
  // packages/schema/src/computed.ts
1036
1102
  function buildComputedManifest(params) {
1037
1103
  const { computed } = params;
@@ -2401,6 +2467,8 @@ function validatePredicateFields(params) {
2401
2467
  }
2402
2468
  function assertValidSchemaIR(params) {
2403
2469
  const dialect = providerToDialect({ provider: params.schema.datasource.provider });
2470
+ assertNativeRlsAdmission({ schema: params.schema, dialect });
2471
+ assertExtensionPlacement({ schema: params.schema, dialect });
2404
2472
  assertIndexOperatorClasses({ schema: params.schema, dialect });
2405
2473
  for (const entry of [...params.schema.models, ...params.schema.enums])
2406
2474
  if (entry.namespace !== undefined)
@@ -2448,9 +2516,6 @@ function assertValidSchemaIR(params) {
2448
2516
  }
2449
2517
  if (params.schema.migrationObjects !== undefined)
2450
2518
  assertMigrationObjects({ objects: params.schema.migrationObjects });
2451
- if (params.schema.rls !== undefined && !DIALECT_CAPABILITIES[dialect].nativeRowLevelSecurity) {
2452
- throw unsupportedCapability({ dialect, feature: "nativeRowLevelSecurity" });
2453
- }
2454
2519
  for (const model of params.schema.models) {
2455
2520
  const counters = model.fields.filter((field) => field.default?.kind === "autoincrement");
2456
2521
  if (dialect === "mysql" && counters.length > 1)
@@ -2462,6 +2527,10 @@ function assertValidSchemaIR(params) {
2462
2527
  const error = unsupportedCapability({ dialect, feature: "timeOfDayTypes", hint: `${model.name}.${field.name} declares @db.${field.nativeType ?? ""} (${timeOfDay}) \u2014 ${instead}` });
2463
2528
  throw new VibeError({ code: error.code, message: error.message, meta: { ...error.meta, model: model.name, field: field.name, nativeType: field.nativeType } });
2464
2529
  }
2530
+ if (field.type === "String" && field.isUnsupported !== true && field.nativeType?.trim().toLowerCase() === "uuid" && DIALECT_CAPABILITIES[dialect].uuidColumns === "unsupported") {
2531
+ const error = unsupportedCapability({ dialect, feature: "uuidColumns", hint: `${model.name}.${field.name} declares @db.Uuid \u2014 ${dialect} has no uuid column type; store it as a plain String (or @db.Char(36))` });
2532
+ throw new VibeError({ code: error.code, message: error.message, meta: { ...error.meta, model: model.name, field: field.name, nativeType: field.nativeType } });
2533
+ }
2465
2534
  if (timeOfDay !== undefined && dialect === "mysql" && field.default?.kind === "now") {
2466
2535
  const error = unsupportedCapability({ dialect, feature: "@default(now()) on a TIME column", hint: `${model.name}.${field.name} \u2014 MySQL has no current-time default for TIME; drop the default and write the value (or use @updatedAt)` });
2467
2536
  throw new VibeError({ code: error.code, message: error.message, meta: { ...error.meta, model: model.name, field: field.name, nativeType: field.nativeType } });
@@ -2696,10 +2765,13 @@ export {
2696
2765
  ScopedQueryError,
2697
2766
  VIBE_ERROR_CODES,
2698
2767
  VibeError,
2768
+ assertExtensionModelPlacement,
2769
+ assertExtensionPlacement,
2699
2770
  assertIdentifierFits,
2700
2771
  assertIndexOperatorClasses,
2701
2772
  assertJsonSchemaRef,
2702
2773
  assertMigrationObjects,
2774
+ assertNativeRlsAdmission,
2703
2775
  assertPredicateCapabilities,
2704
2776
  assertSqlNamespace,
2705
2777
  assertValidSchemaIR,
@@ -2737,6 +2809,7 @@ export {
2737
2809
  jsonTypeAnnotationIssue,
2738
2810
  jsonTypeAnnotationRef,
2739
2811
  mergeExtensionDeclarations,
2812
+ modelSqlNamespace,
2740
2813
  normalizeJsonSchemaRef,
2741
2814
  postgresGeneratedObjectName,
2742
2815
  predicateFieldNames,
@@ -2756,4 +2829,4 @@ export {
2756
2829
  validateSchemaIR
2757
2830
  };
2758
2831
 
2759
- //# debugId=6D288D6F1DD93E5864756E2164756E21
2832
+ //# debugId=315C4C10E208512464756E2164756E21