@sequenceholdings/orm 0.1.3 → 2.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/README.md +108 -38
- package/dist/authoring.cjs +128 -16
- package/dist/authoring.cjs.map +1 -1
- package/dist/authoring.d.cts +21 -4
- package/dist/authoring.d.ts +21 -4
- package/dist/authoring.js +128 -16
- package/dist/authoring.js.map +1 -1
- package/dist/canonical.cjs +83 -9
- package/dist/canonical.cjs.map +1 -1
- package/dist/canonical.d.cts +47 -1
- package/dist/canonical.d.ts +47 -1
- package/dist/canonical.js +83 -10
- package/dist/canonical.js.map +1 -1
- package/dist/cli.cjs +816 -134
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +818 -136
- package/dist/cli.js.map +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.js.map +1 -1
- package/dist/compiler.cjs +488 -96
- package/dist/compiler.cjs.map +1 -1
- package/dist/compiler.d.cts +34 -2
- package/dist/compiler.d.ts +34 -2
- package/dist/compiler.js +489 -97
- package/dist/compiler.js.map +1 -1
- package/dist/index.cjs +420 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +266 -9
- package/dist/index.d.ts +266 -9
- package/dist/index.js +410 -73
- package/dist/index.js.map +1 -1
- package/dist/ir.cjs +146 -12
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +161 -5
- package/dist/ir.d.ts +161 -5
- package/dist/ir.js +144 -13
- package/dist/ir.js.map +1 -1
- package/dist/{naming-CTvWSRTf.d.ts → naming-DCn94LJ4.d.ts} +15 -1
- package/dist/{naming-DRyinU4u.d.cts → naming-DPglwzkB.d.cts} +15 -1
- package/dist/predicate-v2.cjs +136 -11
- package/dist/predicate-v2.cjs.map +1 -1
- package/dist/predicate-v2.d.cts +3 -0
- package/dist/predicate-v2.d.ts +3 -0
- package/dist/predicate-v2.js +136 -11
- package/dist/predicate-v2.js.map +1 -1
- package/dist/sdl.cjs +460 -13
- package/dist/sdl.cjs.map +1 -1
- package/dist/sdl.d.cts +1 -1
- package/dist/sdl.d.ts +1 -1
- package/dist/sdl.js +459 -14
- package/dist/sdl.js.map +1 -1
- package/package.json +6 -4
- package/src/codegen-gql/preset.cjs +64 -0
- package/src/codegen-gql/server-fns.cjs +11 -6
- package/templates/orm-namespace/codegen.preset.cjs +17 -0
- package/templates/orm-namespace/codegen.ts +1 -1
- package/templates/orm-namespace/package.json +3 -0
- package/templates/orm-namespace/schema/tables.ts +1 -1
- package/templates/orm-namespace/sequence.config.ts +1 -1
package/dist/authoring.d.cts
CHANGED
|
@@ -48,6 +48,12 @@ declare class ColumnBuilder {
|
|
|
48
48
|
* (`'gen_random_uuid()'` / `'now()'`), which serialize as `defaultFn`.
|
|
49
49
|
*/
|
|
50
50
|
default(value: ColumnDefaultLiteral): this;
|
|
51
|
+
/**
|
|
52
|
+
* Stamp this column with Postgres `now()` on every UPDATE. Only valid on
|
|
53
|
+
* timestamptz/date; the compiler drops it from SetInput so callers cannot
|
|
54
|
+
* supply a value.
|
|
55
|
+
*/
|
|
56
|
+
onUpdate(value: 'now()'): this;
|
|
51
57
|
description(text: string): this;
|
|
52
58
|
/** Migration rename hint: the previous Postgres column name. */
|
|
53
59
|
renamedFrom(previousColumnName: string): this;
|
|
@@ -324,6 +330,15 @@ interface IndexInput {
|
|
|
324
330
|
unique?: boolean;
|
|
325
331
|
name?: string;
|
|
326
332
|
}
|
|
333
|
+
/** Database integrity constraint; independent of GraphQL relationship traversal. */
|
|
334
|
+
interface ForeignKeyInput {
|
|
335
|
+
name: string;
|
|
336
|
+
columns: readonly string[];
|
|
337
|
+
target: TableRef;
|
|
338
|
+
references: readonly string[];
|
|
339
|
+
/** Omit for immediate, non-deferrable PostgreSQL enforcement. */
|
|
340
|
+
deferrable?: 'initially-immediate' | 'initially-deferred';
|
|
341
|
+
}
|
|
327
342
|
interface TableConfig {
|
|
328
343
|
description?: string;
|
|
329
344
|
/** Table-level migration rename hint (new in v2). */
|
|
@@ -331,6 +346,7 @@ interface TableConfig {
|
|
|
331
346
|
/** Composite primary key: property names (or column names). Mutually exclusive with column-level `.primaryKey()`. */
|
|
332
347
|
primaryKey?: readonly string[];
|
|
333
348
|
indexes?: readonly IndexInput[];
|
|
349
|
+
foreignKeys?: readonly ForeignKeyInput[];
|
|
334
350
|
links?: Record<string, SeqLink>;
|
|
335
351
|
policies?: TablePoliciesInput;
|
|
336
352
|
/**
|
|
@@ -491,9 +507,10 @@ declare const NAMESPACE_CONFIG_FILE = "sequence.config.ts";
|
|
|
491
507
|
declare const SCHEMA_DIR = "schema";
|
|
492
508
|
interface LoadNamespaceOptions {
|
|
493
509
|
/**
|
|
494
|
-
* Test/dev hook: absolute path that
|
|
495
|
-
* in consumer files resolve to (jiti
|
|
496
|
-
* the
|
|
510
|
+
* Test/dev hook: absolute path that package-root and `/authoring` imports
|
|
511
|
+
* in consumer files resolve to (jiti aliases). `/drizzle` still resolves to
|
|
512
|
+
* the SDK-pinned query utilities. Leave unset in production — the consumer's
|
|
513
|
+
* own installed package resolves naturally.
|
|
497
514
|
*/
|
|
498
515
|
ormModulePath?: string;
|
|
499
516
|
}
|
|
@@ -536,4 +553,4 @@ declare function beginAuthoringContext(): AuthoringContext;
|
|
|
536
553
|
declare function endAuthoringContext(context: AuthoringContext): void;
|
|
537
554
|
declare function activeAuthoringContext(): AuthoringContext | undefined;
|
|
538
555
|
|
|
539
|
-
export { type AuthoringContext, type AuthoringRegistration, COLUMN_BRAND, COLUMN_DEFAULT_FNS, ColumnBuilder, type ColumnDefaultFn, type ColumnDefaultLiteral, type ColumnTypeSpec, type CowMutationInput, type DrizzleRelation, ENUM_BRAND, type EnumRef, type IndexInput, LINK_BRAND, type LinkKind, type LoadNamespaceOptions, type LoadedNamespaceV2, NAMESPACE_CONFIG_BRAND, NAMESPACE_CONFIG_FILE, type NamespaceCapabilitiesInput, type NamespaceConfigInput, ORM_TO_PIPELINE_TYPE, type OrmContractColumnMap, type OrmContractPipelineColumn, type OrmSyncedTableContract, type OrmToPipelineType, type OverlayTableRef, type OverlayWinsUnionInput, type OverrideRowMutationInput, type PolicyInput, type PolicyVariantInput, type PolicyWithVariantsInput, type RelationRef, type RevertOverlayMutationInput, SCHEMA_DIR, SYNCED_CONTRACT_HASH_DOMAIN, type SeqLink, type SeqNamespaceConfig, type SeqTable, type SeqView, type SerializeNamespaceInput, type SyncedTableConfig, type SyncedTablePoliciesInput, TABLE_BRAND, type TableConfig, type TablePoliciesInput, type TableRef, type ToManyOptions, type ToOneOptions, type ToPlatformUserOptions, type TypedSyncedTableConfig, VIEW_BRAND, type ViewConfig, type ViewPoliciesInput, type ViewQueryContext, type ViewSelectQuery, activeAuthoringContext, beginAuthoringContext, computeCanonicalHash, computeSyncedContractHash, defineEnum, defineNamespace, defineSyncedTable, defineTable, defineView, deriveColumnName, endAuthoringContext, extractOrmSyncedTableContract, isColumnBuilder, isEnumRef, isSeqLink, isSeqNamespaceConfig, isSeqTable, isSeqView, loadNamespaceFromDir, overlayWinsUnion, overrideRowMutation, revertOverlayMutation, serializeNamespace, syncedContractSurface, t, toMany, toOne, toPlatformUser };
|
|
556
|
+
export { type AuthoringContext, type AuthoringRegistration, COLUMN_BRAND, COLUMN_DEFAULT_FNS, ColumnBuilder, type ColumnDefaultFn, type ColumnDefaultLiteral, type ColumnTypeSpec, type CowMutationInput, type DrizzleRelation, ENUM_BRAND, type EnumRef, type ForeignKeyInput, type IndexInput, LINK_BRAND, type LinkKind, type LoadNamespaceOptions, type LoadedNamespaceV2, NAMESPACE_CONFIG_BRAND, NAMESPACE_CONFIG_FILE, type NamespaceCapabilitiesInput, type NamespaceConfigInput, ORM_TO_PIPELINE_TYPE, type OrmContractColumnMap, type OrmContractPipelineColumn, type OrmSyncedTableContract, type OrmToPipelineType, type OverlayTableRef, type OverlayWinsUnionInput, type OverrideRowMutationInput, type PolicyInput, type PolicyVariantInput, type PolicyWithVariantsInput, type RelationRef, type RevertOverlayMutationInput, SCHEMA_DIR, SYNCED_CONTRACT_HASH_DOMAIN, type SeqLink, type SeqNamespaceConfig, type SeqTable, type SeqView, type SerializeNamespaceInput, type SyncedTableConfig, type SyncedTablePoliciesInput, TABLE_BRAND, type TableConfig, type TablePoliciesInput, type TableRef, type ToManyOptions, type ToOneOptions, type ToPlatformUserOptions, type TypedSyncedTableConfig, VIEW_BRAND, type ViewConfig, type ViewPoliciesInput, type ViewQueryContext, type ViewSelectQuery, activeAuthoringContext, beginAuthoringContext, computeCanonicalHash, computeSyncedContractHash, defineEnum, defineNamespace, defineSyncedTable, defineTable, defineView, deriveColumnName, endAuthoringContext, extractOrmSyncedTableContract, isColumnBuilder, isEnumRef, isSeqLink, isSeqNamespaceConfig, isSeqTable, isSeqView, loadNamespaceFromDir, overlayWinsUnion, overrideRowMutation, revertOverlayMutation, serializeNamespace, syncedContractSurface, t, toMany, toOne, toPlatformUser };
|
package/dist/authoring.d.ts
CHANGED
|
@@ -48,6 +48,12 @@ declare class ColumnBuilder {
|
|
|
48
48
|
* (`'gen_random_uuid()'` / `'now()'`), which serialize as `defaultFn`.
|
|
49
49
|
*/
|
|
50
50
|
default(value: ColumnDefaultLiteral): this;
|
|
51
|
+
/**
|
|
52
|
+
* Stamp this column with Postgres `now()` on every UPDATE. Only valid on
|
|
53
|
+
* timestamptz/date; the compiler drops it from SetInput so callers cannot
|
|
54
|
+
* supply a value.
|
|
55
|
+
*/
|
|
56
|
+
onUpdate(value: 'now()'): this;
|
|
51
57
|
description(text: string): this;
|
|
52
58
|
/** Migration rename hint: the previous Postgres column name. */
|
|
53
59
|
renamedFrom(previousColumnName: string): this;
|
|
@@ -324,6 +330,15 @@ interface IndexInput {
|
|
|
324
330
|
unique?: boolean;
|
|
325
331
|
name?: string;
|
|
326
332
|
}
|
|
333
|
+
/** Database integrity constraint; independent of GraphQL relationship traversal. */
|
|
334
|
+
interface ForeignKeyInput {
|
|
335
|
+
name: string;
|
|
336
|
+
columns: readonly string[];
|
|
337
|
+
target: TableRef;
|
|
338
|
+
references: readonly string[];
|
|
339
|
+
/** Omit for immediate, non-deferrable PostgreSQL enforcement. */
|
|
340
|
+
deferrable?: 'initially-immediate' | 'initially-deferred';
|
|
341
|
+
}
|
|
327
342
|
interface TableConfig {
|
|
328
343
|
description?: string;
|
|
329
344
|
/** Table-level migration rename hint (new in v2). */
|
|
@@ -331,6 +346,7 @@ interface TableConfig {
|
|
|
331
346
|
/** Composite primary key: property names (or column names). Mutually exclusive with column-level `.primaryKey()`. */
|
|
332
347
|
primaryKey?: readonly string[];
|
|
333
348
|
indexes?: readonly IndexInput[];
|
|
349
|
+
foreignKeys?: readonly ForeignKeyInput[];
|
|
334
350
|
links?: Record<string, SeqLink>;
|
|
335
351
|
policies?: TablePoliciesInput;
|
|
336
352
|
/**
|
|
@@ -491,9 +507,10 @@ declare const NAMESPACE_CONFIG_FILE = "sequence.config.ts";
|
|
|
491
507
|
declare const SCHEMA_DIR = "schema";
|
|
492
508
|
interface LoadNamespaceOptions {
|
|
493
509
|
/**
|
|
494
|
-
* Test/dev hook: absolute path that
|
|
495
|
-
* in consumer files resolve to (jiti
|
|
496
|
-
* the
|
|
510
|
+
* Test/dev hook: absolute path that package-root and `/authoring` imports
|
|
511
|
+
* in consumer files resolve to (jiti aliases). `/drizzle` still resolves to
|
|
512
|
+
* the SDK-pinned query utilities. Leave unset in production — the consumer's
|
|
513
|
+
* own installed package resolves naturally.
|
|
497
514
|
*/
|
|
498
515
|
ormModulePath?: string;
|
|
499
516
|
}
|
|
@@ -536,4 +553,4 @@ declare function beginAuthoringContext(): AuthoringContext;
|
|
|
536
553
|
declare function endAuthoringContext(context: AuthoringContext): void;
|
|
537
554
|
declare function activeAuthoringContext(): AuthoringContext | undefined;
|
|
538
555
|
|
|
539
|
-
export { type AuthoringContext, type AuthoringRegistration, COLUMN_BRAND, COLUMN_DEFAULT_FNS, ColumnBuilder, type ColumnDefaultFn, type ColumnDefaultLiteral, type ColumnTypeSpec, type CowMutationInput, type DrizzleRelation, ENUM_BRAND, type EnumRef, type IndexInput, LINK_BRAND, type LinkKind, type LoadNamespaceOptions, type LoadedNamespaceV2, NAMESPACE_CONFIG_BRAND, NAMESPACE_CONFIG_FILE, type NamespaceCapabilitiesInput, type NamespaceConfigInput, ORM_TO_PIPELINE_TYPE, type OrmContractColumnMap, type OrmContractPipelineColumn, type OrmSyncedTableContract, type OrmToPipelineType, type OverlayTableRef, type OverlayWinsUnionInput, type OverrideRowMutationInput, type PolicyInput, type PolicyVariantInput, type PolicyWithVariantsInput, type RelationRef, type RevertOverlayMutationInput, SCHEMA_DIR, SYNCED_CONTRACT_HASH_DOMAIN, type SeqLink, type SeqNamespaceConfig, type SeqTable, type SeqView, type SerializeNamespaceInput, type SyncedTableConfig, type SyncedTablePoliciesInput, TABLE_BRAND, type TableConfig, type TablePoliciesInput, type TableRef, type ToManyOptions, type ToOneOptions, type ToPlatformUserOptions, type TypedSyncedTableConfig, VIEW_BRAND, type ViewConfig, type ViewPoliciesInput, type ViewQueryContext, type ViewSelectQuery, activeAuthoringContext, beginAuthoringContext, computeCanonicalHash, computeSyncedContractHash, defineEnum, defineNamespace, defineSyncedTable, defineTable, defineView, deriveColumnName, endAuthoringContext, extractOrmSyncedTableContract, isColumnBuilder, isEnumRef, isSeqLink, isSeqNamespaceConfig, isSeqTable, isSeqView, loadNamespaceFromDir, overlayWinsUnion, overrideRowMutation, revertOverlayMutation, serializeNamespace, syncedContractSurface, t, toMany, toOne, toPlatformUser };
|
|
556
|
+
export { type AuthoringContext, type AuthoringRegistration, COLUMN_BRAND, COLUMN_DEFAULT_FNS, ColumnBuilder, type ColumnDefaultFn, type ColumnDefaultLiteral, type ColumnTypeSpec, type CowMutationInput, type DrizzleRelation, ENUM_BRAND, type EnumRef, type ForeignKeyInput, type IndexInput, LINK_BRAND, type LinkKind, type LoadNamespaceOptions, type LoadedNamespaceV2, NAMESPACE_CONFIG_BRAND, NAMESPACE_CONFIG_FILE, type NamespaceCapabilitiesInput, type NamespaceConfigInput, ORM_TO_PIPELINE_TYPE, type OrmContractColumnMap, type OrmContractPipelineColumn, type OrmSyncedTableContract, type OrmToPipelineType, type OverlayTableRef, type OverlayWinsUnionInput, type OverrideRowMutationInput, type PolicyInput, type PolicyVariantInput, type PolicyWithVariantsInput, type RelationRef, type RevertOverlayMutationInput, SCHEMA_DIR, SYNCED_CONTRACT_HASH_DOMAIN, type SeqLink, type SeqNamespaceConfig, type SeqTable, type SeqView, type SerializeNamespaceInput, type SyncedTableConfig, type SyncedTablePoliciesInput, TABLE_BRAND, type TableConfig, type TablePoliciesInput, type TableRef, type ToManyOptions, type ToOneOptions, type ToPlatformUserOptions, type TypedSyncedTableConfig, VIEW_BRAND, type ViewConfig, type ViewPoliciesInput, type ViewQueryContext, type ViewSelectQuery, activeAuthoringContext, beginAuthoringContext, computeCanonicalHash, computeSyncedContractHash, defineEnum, defineNamespace, defineSyncedTable, defineTable, defineView, deriveColumnName, endAuthoringContext, extractOrmSyncedTableContract, isColumnBuilder, isEnumRef, isSeqLink, isSeqNamespaceConfig, isSeqTable, isSeqView, loadNamespaceFromDir, overlayWinsUnion, overrideRowMutation, revertOverlayMutation, serializeNamespace, syncedContractSurface, t, toMany, toOne, toPlatformUser };
|
package/dist/authoring.js
CHANGED
|
@@ -30,6 +30,7 @@ var ColumnBuilder = class {
|
|
|
30
30
|
hasDefault: false,
|
|
31
31
|
defaultValue: void 0,
|
|
32
32
|
defaultFn: void 0,
|
|
33
|
+
onUpdateFn: void 0,
|
|
33
34
|
description: void 0,
|
|
34
35
|
renamedFrom: void 0,
|
|
35
36
|
cast: void 0,
|
|
@@ -65,6 +66,15 @@ var ColumnBuilder = class {
|
|
|
65
66
|
this.state.defaultFn = void 0;
|
|
66
67
|
return this;
|
|
67
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Stamp this column with Postgres `now()` on every UPDATE. Only valid on
|
|
71
|
+
* timestamptz/date; the compiler drops it from SetInput so callers cannot
|
|
72
|
+
* supply a value.
|
|
73
|
+
*/
|
|
74
|
+
onUpdate(value) {
|
|
75
|
+
this.state.onUpdateFn = value;
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
68
78
|
description(text2) {
|
|
69
79
|
this.state.description = text2;
|
|
70
80
|
return this;
|
|
@@ -108,6 +118,7 @@ var ColumnBuilder = class {
|
|
|
108
118
|
};
|
|
109
119
|
if (this.state.hasDefault) column.default = this.state.defaultValue ?? null;
|
|
110
120
|
if (this.state.defaultFn !== void 0) column.defaultFn = this.state.defaultFn;
|
|
121
|
+
if (this.state.onUpdateFn !== void 0) column.onUpdateFn = this.state.onUpdateFn;
|
|
111
122
|
if (this.state.description !== void 0) column.description = this.state.description;
|
|
112
123
|
if (this.state.renamedFrom !== void 0) column.renamedFrom = this.state.renamedFrom;
|
|
113
124
|
if (this.state.cast !== void 0) column.cast = this.state.cast;
|
|
@@ -135,6 +146,7 @@ var t = {
|
|
|
135
146
|
// src/constants.ts
|
|
136
147
|
var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
|
|
137
148
|
var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
149
|
+
var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
|
|
138
150
|
var MAX_IDENTIFIER_LENGTH = 63;
|
|
139
151
|
var SYNCED_SOURCE_PATTERN = /^gold\.[a-z][a-z0-9_]*$/;
|
|
140
152
|
var HASH_DOMAIN = "sequence-orm-definitions\0";
|
|
@@ -237,10 +249,10 @@ function isSeqLink(value) {
|
|
|
237
249
|
function isSeqTable(value) {
|
|
238
250
|
return typeof value === "object" && value !== null && value.kind === TABLE_BRAND;
|
|
239
251
|
}
|
|
240
|
-
function assertIdentifier({ value, what, file }) {
|
|
241
|
-
if (!
|
|
252
|
+
function assertIdentifier({ value, what, file, pattern = IDENTIFIER_PATTERN }) {
|
|
253
|
+
if (!pattern.test(value) || value.length > MAX_IDENTIFIER_LENGTH) {
|
|
242
254
|
throw new OrmDefinitionError(
|
|
243
|
-
`${what} "${value}" must match ${
|
|
255
|
+
`${what} "${value}" must match ${pattern} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
|
|
244
256
|
file
|
|
245
257
|
);
|
|
246
258
|
}
|
|
@@ -261,7 +273,7 @@ function defineTable(name, columns, config = {}) {
|
|
|
261
273
|
);
|
|
262
274
|
}
|
|
263
275
|
const columnName = builder.columnNameFor(propertyName);
|
|
264
|
-
assertIdentifier({ value: columnName, what: `table "${name}": column "${propertyName}" column name`, file });
|
|
276
|
+
assertIdentifier({ value: columnName, what: `table "${name}": column "${propertyName}" column name`, file, pattern: COLUMN_IDENTIFIER_PATTERN });
|
|
265
277
|
const existing = seenColumnNames.get(columnName);
|
|
266
278
|
if (existing !== void 0) {
|
|
267
279
|
throw new OrmDefinitionError(
|
|
@@ -301,7 +313,8 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
|
|
|
301
313
|
assertIdentifier({
|
|
302
314
|
value: columnName,
|
|
303
315
|
what: `synced table "${name}": column "${propertyName}" column name`,
|
|
304
|
-
file
|
|
316
|
+
file,
|
|
317
|
+
pattern: COLUMN_IDENTIFIER_PATTERN
|
|
305
318
|
});
|
|
306
319
|
const existing = seenColumnNames.get(columnName);
|
|
307
320
|
if (existing !== void 0) {
|
|
@@ -390,7 +403,7 @@ function defineView(name, columns, config) {
|
|
|
390
403
|
);
|
|
391
404
|
}
|
|
392
405
|
const columnName = builder.columnNameFor(propertyName);
|
|
393
|
-
if (!
|
|
406
|
+
if (!COLUMN_IDENTIFIER_PATTERN.test(columnName) || columnName.length > MAX_IDENTIFIER_LENGTH) {
|
|
394
407
|
throw new OrmDefinitionError(`view "${name}": column "${propertyName}" has invalid column name "${columnName}"`, file);
|
|
395
408
|
}
|
|
396
409
|
const existing = seenColumnNames.get(columnName);
|
|
@@ -839,6 +852,7 @@ function scalarColumnType(table, property, helper) {
|
|
|
839
852
|
var CANONICAL_FORMAT = 2;
|
|
840
853
|
var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
|
|
841
854
|
var identifier = z.string().min(1).max(63).regex(IDENTIFIER_RE);
|
|
855
|
+
var columnIdentifier = z.string().min(1).max(63).regex(COLUMN_IDENTIFIER_PATTERN);
|
|
842
856
|
var COLUMN_TYPES = [
|
|
843
857
|
"uuid",
|
|
844
858
|
"text",
|
|
@@ -859,18 +873,33 @@ var canonicalEnumSchema = z.object({
|
|
|
859
873
|
var canonicalColumnSchema = z.object({
|
|
860
874
|
/** Property name in TS (camelCase); columnName is the Postgres name. */
|
|
861
875
|
name: z.string().min(1).max(63),
|
|
862
|
-
columnName:
|
|
876
|
+
columnName: columnIdentifier,
|
|
863
877
|
type: z.union([columnTypeSchema, z.object({ enum: identifier })]),
|
|
864
878
|
nullable: z.boolean().default(true),
|
|
865
879
|
unique: z.boolean().default(false),
|
|
866
880
|
/** Literal default, or one of the allowlisted SQL functions. */
|
|
867
881
|
default: z.union([z.string(), z.number(), z.boolean(), z.null()]).optional(),
|
|
868
882
|
defaultFn: z.enum(["gen_random_uuid()", "now()"]).optional(),
|
|
883
|
+
/**
|
|
884
|
+
* Stamp this column from the database clock on UPDATE (`now()`). The
|
|
885
|
+
* column stays insertable (defaults fill gaps) but is omitted from
|
|
886
|
+
* SetInput / settable so callers cannot backdate it.
|
|
887
|
+
*/
|
|
888
|
+
onUpdateFn: z.enum(["now()"]).optional(),
|
|
869
889
|
description: z.string().max(500).optional(),
|
|
870
890
|
/** Migration hints — same semantics as v1. */
|
|
871
|
-
renamedFrom:
|
|
891
|
+
renamedFrom: columnIdentifier.optional(),
|
|
872
892
|
cast: z.string().max(200).optional(),
|
|
873
893
|
deprecated: z.boolean().default(false)
|
|
894
|
+
}).superRefine((column, ctx) => {
|
|
895
|
+
if (column.onUpdateFn === void 0) return;
|
|
896
|
+
if (typeof column.type !== "string" || column.type !== "timestamptz" && column.type !== "date") {
|
|
897
|
+
ctx.addIssue({
|
|
898
|
+
code: "custom",
|
|
899
|
+
path: ["onUpdateFn"],
|
|
900
|
+
message: `onUpdateFn '${column.onUpdateFn}' is only valid on timestamptz/date columns`
|
|
901
|
+
});
|
|
902
|
+
}
|
|
874
903
|
});
|
|
875
904
|
var canonicalLinkSchema = z.object({
|
|
876
905
|
name: z.string().min(1).max(63),
|
|
@@ -878,15 +907,15 @@ var canonicalLinkSchema = z.object({
|
|
|
878
907
|
/** Target table name (namespace-local). Absent for toPlatformUser. */
|
|
879
908
|
target: identifier.optional(),
|
|
880
909
|
/** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
|
|
881
|
-
on:
|
|
910
|
+
on: columnIdentifier,
|
|
882
911
|
/** Referenced column on the target (default: target's PK / platform user `sub`). */
|
|
883
|
-
references:
|
|
912
|
+
references: columnIdentifier.optional(),
|
|
884
913
|
/** enforce: true emits a real FK constraint (same-namespace toOne only). */
|
|
885
914
|
enforce: z.boolean().default(false),
|
|
886
915
|
description: z.string().max(500).optional()
|
|
887
916
|
});
|
|
888
917
|
var canonicalIndexSchema = z.object({
|
|
889
|
-
columns: z.array(
|
|
918
|
+
columns: z.array(columnIdentifier).min(1),
|
|
890
919
|
unique: z.boolean().default(false),
|
|
891
920
|
name: identifier.optional()
|
|
892
921
|
});
|
|
@@ -977,6 +1006,13 @@ var canonicalCowMutationSchema = z.object({
|
|
|
977
1006
|
}
|
|
978
1007
|
}
|
|
979
1008
|
});
|
|
1009
|
+
var canonicalForeignKeySchema = z.object({
|
|
1010
|
+
name: identifier,
|
|
1011
|
+
columns: z.array(columnIdentifier).min(1).max(8),
|
|
1012
|
+
target: identifier,
|
|
1013
|
+
references: z.array(columnIdentifier).min(1).max(8),
|
|
1014
|
+
deferrable: z.enum(["initially-immediate", "initially-deferred"]).optional()
|
|
1015
|
+
}).strict();
|
|
980
1016
|
var canonicalTableSchema = z.object({
|
|
981
1017
|
table: identifier,
|
|
982
1018
|
description: z.string().max(2e3).optional(),
|
|
@@ -985,7 +1021,7 @@ var canonicalTableSchema = z.object({
|
|
|
985
1021
|
/** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
|
|
986
1022
|
source: z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
|
|
987
1023
|
columns: z.array(canonicalColumnSchema).default([]),
|
|
988
|
-
primaryKey: z.array(
|
|
1024
|
+
primaryKey: z.array(columnIdentifier).min(1).max(8).optional(),
|
|
989
1025
|
links: z.array(canonicalLinkSchema).default([]),
|
|
990
1026
|
indexes: z.array(canonicalIndexSchema).default([]),
|
|
991
1027
|
policies: canonicalPoliciesSchema.default({}),
|
|
@@ -996,14 +1032,16 @@ var canonicalTableSchema = z.object({
|
|
|
996
1032
|
*/
|
|
997
1033
|
actions: z.array(canonicalActionSchema).default([]),
|
|
998
1034
|
/** Optional (not defaulted) so pre-CoW canonical documents keep byte-identical hashes. */
|
|
999
|
-
cowMutations: z.array(canonicalCowMutationSchema).optional()
|
|
1035
|
+
cowMutations: z.array(canonicalCowMutationSchema).optional(),
|
|
1036
|
+
/** Omitted by old definitions, preserving their existing content hashes. */
|
|
1037
|
+
foreignKeys: z.array(canonicalForeignKeySchema).max(100).optional()
|
|
1000
1038
|
});
|
|
1001
1039
|
var canonicalViewSchema = z.object({
|
|
1002
1040
|
view: identifier,
|
|
1003
1041
|
description: z.string().max(2e3).optional(),
|
|
1004
1042
|
columns: z.array(canonicalColumnSchema).min(1),
|
|
1005
1043
|
/** Logical identity for GraphQL only; PostgreSQL views have no PK constraint. */
|
|
1006
|
-
primaryKey: z.array(
|
|
1044
|
+
primaryKey: z.array(columnIdentifier).min(1).max(8).optional(),
|
|
1007
1045
|
links: z.array(canonicalLinkSchema).default([]),
|
|
1008
1046
|
policies: z.object({ read: policyExpressionSchema }).strict(),
|
|
1009
1047
|
query: z.string().min(1).max(1e5).refine(isSingleViewQuerySql, {
|
|
@@ -1016,6 +1054,9 @@ var canonicalViewSchema = z.object({
|
|
|
1016
1054
|
if (column.default !== void 0 || column.defaultFn !== void 0) {
|
|
1017
1055
|
ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare a default` });
|
|
1018
1056
|
}
|
|
1057
|
+
if (column.onUpdateFn !== void 0) {
|
|
1058
|
+
ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare onUpdateFn` });
|
|
1059
|
+
}
|
|
1019
1060
|
if (column.unique) {
|
|
1020
1061
|
ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare unique` });
|
|
1021
1062
|
}
|
|
@@ -1049,6 +1090,45 @@ var canonicalDefinitionSchema = z.object({
|
|
|
1049
1090
|
ctx.addIssue({ code: "custom", path: ["tables", index, "table"], message: `duplicate relation '${table.table}'` });
|
|
1050
1091
|
}
|
|
1051
1092
|
seen.add(table.table);
|
|
1093
|
+
if (table.source !== void 0) {
|
|
1094
|
+
for (const [columnIndex, column] of table.columns.entries()) {
|
|
1095
|
+
if (column.onUpdateFn !== void 0) {
|
|
1096
|
+
ctx.addIssue({
|
|
1097
|
+
code: "custom",
|
|
1098
|
+
path: ["tables", index, "columns", columnIndex],
|
|
1099
|
+
message: `synced table '${table.table}' column '${column.name}' cannot declare onUpdateFn`
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
1106
|
+
for (const [tableIndex, table] of definition.tables.entries()) {
|
|
1107
|
+
const names = /* @__PURE__ */ new Set([
|
|
1108
|
+
...table.primaryKey?.length ? [`${table.table}_pkey`] : [],
|
|
1109
|
+
...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
|
|
1110
|
+
...table.links.filter((link) => link.enforce).map((link) => `fk_${table.table}_${link.name}`)
|
|
1111
|
+
]);
|
|
1112
|
+
for (const [index, fk] of (table.foreignKeys ?? []).entries()) {
|
|
1113
|
+
const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index], message });
|
|
1114
|
+
if (names.has(fk.name)) fail(`duplicate foreign key '${fk.name}'`);
|
|
1115
|
+
names.add(fk.name);
|
|
1116
|
+
const target = fkTables.get(fk.target);
|
|
1117
|
+
if (table.source || !target || target.source) {
|
|
1118
|
+
fail("foreign keys must connect owned tables in the same namespace");
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
if (fk.columns.length !== fk.references.length) fail("foreign key columns and references must have the same number of columns");
|
|
1122
|
+
if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length) fail("foreign key columns must not repeat");
|
|
1123
|
+
for (const [position, name] of fk.columns.entries()) {
|
|
1124
|
+
const sourceColumn = table.columns.find((column) => column.columnName === name);
|
|
1125
|
+
const targetColumn = target.columns.find((column) => column.columnName === fk.references[position]);
|
|
1126
|
+
if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
|
|
1127
|
+
else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
|
|
1128
|
+
}
|
|
1129
|
+
const uniqueKeys = [target.primaryKey ?? [], ...target.indexes.filter((index2) => index2.unique).map((index2) => index2.columns), ...target.columns.filter((column) => column.unique).map((column) => [column.columnName])];
|
|
1130
|
+
if (!uniqueKeys.some((key) => key.length === fk.references.length && key.every((column) => fk.references.includes(column)))) fail("foreign key target columns must form a unique key");
|
|
1131
|
+
}
|
|
1052
1132
|
}
|
|
1053
1133
|
const tableByName = new Map(definition.tables.map((table) => [table.table, table]));
|
|
1054
1134
|
for (const [tableIndex, table] of definition.tables.entries()) {
|
|
@@ -1171,12 +1251,13 @@ function normalizeCanonical(definition) {
|
|
|
1171
1251
|
...definition,
|
|
1172
1252
|
enums: sortByName(definition.enums),
|
|
1173
1253
|
tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t2) => {
|
|
1174
|
-
const { cowMutations, ...rest } = t2;
|
|
1254
|
+
const { cowMutations, foreignKeys, ...rest } = t2;
|
|
1175
1255
|
return {
|
|
1176
1256
|
...rest,
|
|
1177
1257
|
links: sortByName(t2.links),
|
|
1178
1258
|
indexes: [...t2.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
|
|
1179
1259
|
actions: sortByName(t2.actions),
|
|
1260
|
+
...foreignKeys?.length ? { foreignKeys: sortByName(foreignKeys) } : {},
|
|
1180
1261
|
// Omitted entirely when absent/empty so pre-CoW documents hash unchanged.
|
|
1181
1262
|
...cowMutations !== void 0 && cowMutations.length > 0 ? { cowMutations: sortByName(cowMutations) } : {}
|
|
1182
1263
|
};
|
|
@@ -1372,6 +1453,22 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
|
|
|
1372
1453
|
} else if (columnLevelPk.length > 0) {
|
|
1373
1454
|
candidate.primaryKey = columnLevelPk;
|
|
1374
1455
|
}
|
|
1456
|
+
if (table.config.foreignKeys?.length) {
|
|
1457
|
+
if (table.source) throw new OrmDefinitionError(`synced table "${table.tableName}" cannot enforce foreign keys`, table.file);
|
|
1458
|
+
candidate.foreignKeys = table.config.foreignKeys.map((fk) => {
|
|
1459
|
+
const target = resolveRelationRef({ ref: fk.target, fail: (message) => {
|
|
1460
|
+
throw new OrmDefinitionError(message, table.file);
|
|
1461
|
+
} });
|
|
1462
|
+
if (!isSeqTable(target) || !relationsByName.has(target.tableName)) throw new OrmDefinitionError("foreign key target must be a table in this namespace", table.file);
|
|
1463
|
+
return {
|
|
1464
|
+
name: fk.name,
|
|
1465
|
+
columns: fk.columns.map((ref) => resolveColumnRef({ relation: table, ref, role: `foreign key "${fk.name}" column` })),
|
|
1466
|
+
target: target.tableName,
|
|
1467
|
+
references: fk.references.map((ref) => resolveColumnRef({ relation: target, ref, role: `foreign key "${fk.name}" reference` })),
|
|
1468
|
+
...fk.deferrable ? { deferrable: fk.deferrable } : {}
|
|
1469
|
+
};
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1375
1472
|
for (const index of table.config.indexes ?? []) {
|
|
1376
1473
|
const entry = {
|
|
1377
1474
|
columns: index.columns.map((ref) => resolveColumnRef({ relation: table, ref, role: "index" }))
|
|
@@ -1424,6 +1521,12 @@ function buildCanonicalView({
|
|
|
1424
1521
|
if (column.default !== void 0 || column.defaultFn !== void 0) {
|
|
1425
1522
|
throw new OrmDefinitionError(`view "${view.viewName}": column "${propertyName}" cannot declare a default`, view.file);
|
|
1426
1523
|
}
|
|
1524
|
+
if (column.onUpdateFn !== void 0) {
|
|
1525
|
+
throw new OrmDefinitionError(
|
|
1526
|
+
`view "${view.viewName}": column "${propertyName}" cannot declare onUpdateFn`,
|
|
1527
|
+
view.file
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1427
1530
|
if (column.unique) {
|
|
1428
1531
|
throw new OrmDefinitionError(`view "${view.viewName}": column "${propertyName}" cannot declare unique`, view.file);
|
|
1429
1532
|
}
|
|
@@ -1682,7 +1785,16 @@ async function loadNamespaceFromDir(dir, options = {}) {
|
|
|
1682
1785
|
const jiti = createJiti(import.meta.url, {
|
|
1683
1786
|
interopDefault: true,
|
|
1684
1787
|
fsCache: false,
|
|
1685
|
-
...options.ormModulePath !== void 0 ? {
|
|
1788
|
+
...options.ormModulePath !== void 0 ? {
|
|
1789
|
+
alias: {
|
|
1790
|
+
// The public /drizzle barrel re-exports this pinned dependency.
|
|
1791
|
+
// Explicitly map it before the root alias: otherwise jiti appends
|
|
1792
|
+
// "/drizzle" to the overridden module's filename.
|
|
1793
|
+
"@sequenceholdings/orm/drizzle": createRequire(import.meta.url).resolve("drizzle-orm"),
|
|
1794
|
+
"@sequenceholdings/orm": options.ormModulePath,
|
|
1795
|
+
"@sequenceholdings/orm/authoring": options.ormModulePath
|
|
1796
|
+
}
|
|
1797
|
+
} : {}
|
|
1686
1798
|
});
|
|
1687
1799
|
const context = beginAuthoringContext();
|
|
1688
1800
|
try {
|