@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.
- package/dist/capabilities.d.ts +8 -0
- package/dist/errors.d.ts +8 -0
- package/dist/extension.d.ts +19 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +273 -200
- package/dist/index.js.map +11 -9
- package/dist/model-sql-namespace.d.ts +8 -0
- package/dist/native-rls-admission.d.ts +7 -0
- package/package.json +1 -1
package/dist/capabilities.d.ts
CHANGED
|
@@ -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
|
/**
|
package/dist/extension.d.ts
CHANGED
|
@@ -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
|
|
331
|
-
*
|
|
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=
|
|
2832
|
+
//# debugId=315C4C10E208512464756E2164756E21
|