@vibeorm/schema 4.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/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"
@@ -340,6 +341,261 @@ var DIALECT_CAPABILITIES = Object.freeze({
340
341
  mysql: MYSQL_CAPABILITIES
341
342
  });
342
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
+ }
343
599
  // packages/schema/src/migration-objects.ts
344
600
  function sqlObjectKey(params) {
345
601
  const object = params.identity;
@@ -539,32 +795,6 @@ function assertIndexOperatorClasses(params) {
539
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 } });
540
796
  }
541
797
  }
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
798
  // packages/schema/src/module-schema.ts
569
799
  function prepareDatabaseModuleSchema(params) {
570
800
  const { schema, module } = params;
@@ -865,173 +1095,6 @@ function exactDecimalLiteral(params) {
865
1095
  const mantissa = text.replace(/[eE].*$/, "").replace("-", "").replace(".", "").replace(/^0+/, "");
866
1096
  return mantissa.length <= 15 ? Number(text) : text;
867
1097
  }
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
1098
  // packages/schema/src/computed.ts
1036
1099
  function buildComputedManifest(params) {
1037
1100
  const { computed } = params;
@@ -2401,6 +2464,8 @@ function validatePredicateFields(params) {
2401
2464
  }
2402
2465
  function assertValidSchemaIR(params) {
2403
2466
  const dialect = providerToDialect({ provider: params.schema.datasource.provider });
2467
+ assertNativeRlsAdmission({ schema: params.schema, dialect });
2468
+ assertExtensionPlacement({ schema: params.schema, dialect });
2404
2469
  assertIndexOperatorClasses({ schema: params.schema, dialect });
2405
2470
  for (const entry of [...params.schema.models, ...params.schema.enums])
2406
2471
  if (entry.namespace !== undefined)
@@ -2448,9 +2513,6 @@ function assertValidSchemaIR(params) {
2448
2513
  }
2449
2514
  if (params.schema.migrationObjects !== undefined)
2450
2515
  assertMigrationObjects({ objects: params.schema.migrationObjects });
2451
- if (params.schema.rls !== undefined && !DIALECT_CAPABILITIES[dialect].nativeRowLevelSecurity) {
2452
- throw unsupportedCapability({ dialect, feature: "nativeRowLevelSecurity" });
2453
- }
2454
2516
  for (const model of params.schema.models) {
2455
2517
  const counters = model.fields.filter((field) => field.default?.kind === "autoincrement");
2456
2518
  if (dialect === "mysql" && counters.length > 1)
@@ -2696,10 +2758,13 @@ export {
2696
2758
  ScopedQueryError,
2697
2759
  VIBE_ERROR_CODES,
2698
2760
  VibeError,
2761
+ assertExtensionModelPlacement,
2762
+ assertExtensionPlacement,
2699
2763
  assertIdentifierFits,
2700
2764
  assertIndexOperatorClasses,
2701
2765
  assertJsonSchemaRef,
2702
2766
  assertMigrationObjects,
2767
+ assertNativeRlsAdmission,
2703
2768
  assertPredicateCapabilities,
2704
2769
  assertSqlNamespace,
2705
2770
  assertValidSchemaIR,
@@ -2737,6 +2802,7 @@ export {
2737
2802
  jsonTypeAnnotationIssue,
2738
2803
  jsonTypeAnnotationRef,
2739
2804
  mergeExtensionDeclarations,
2805
+ modelSqlNamespace,
2740
2806
  normalizeJsonSchemaRef,
2741
2807
  postgresGeneratedObjectName,
2742
2808
  predicateFieldNames,
@@ -2756,4 +2822,4 @@ export {
2756
2822
  validateSchemaIR
2757
2823
  };
2758
2824
 
2759
- //# debugId=6D288D6F1DD93E5864756E2164756E21
2825
+ //# debugId=721E026706902A1F64756E2164756E21