@sequenceholdings/orm 2.8.4 → 2.9.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/authoring.cjs +142 -56
- package/dist/authoring.cjs.map +1 -1
- package/dist/authoring.d.cts +7 -3
- package/dist/authoring.d.ts +7 -3
- package/dist/authoring.js +142 -56
- package/dist/authoring.js.map +1 -1
- package/dist/canonical.cjs +63 -13
- package/dist/canonical.cjs.map +1 -1
- package/dist/canonical.d.cts +77 -72
- package/dist/canonical.d.ts +77 -72
- package/dist/canonical.js +63 -13
- package/dist/canonical.js.map +1 -1
- package/dist/cli.cjs +170 -72
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +23 -1
- package/dist/cli.d.ts +23 -1
- package/dist/cli.js +167 -73
- package/dist/cli.js.map +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js.map +1 -1
- package/dist/compiler.cjs +77 -20
- package/dist/compiler.cjs.map +1 -1
- package/dist/compiler.d.cts +2 -2
- package/dist/compiler.d.ts +2 -2
- package/dist/compiler.js +77 -20
- package/dist/compiler.js.map +1 -1
- package/dist/{constants-Dou1RkTo.d.cts → constants-D2o0vK6U.d.cts} +8 -1
- package/dist/{constants-Dou1RkTo.d.ts → constants-D2o0vK6U.d.ts} +8 -1
- package/dist/dynamic-input.d.cts +1 -1
- package/dist/dynamic-input.d.ts +1 -1
- package/dist/index.cjs +193 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -59
- package/dist/index.d.ts +62 -59
- package/dist/index.js +192 -83
- package/dist/index.js.map +1 -1
- package/dist/ir.cjs +70 -15
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +76 -76
- package/dist/ir.d.ts +76 -76
- package/dist/ir.js +70 -15
- package/dist/ir.js.map +1 -1
- package/dist/predicate-v2.cjs +70 -15
- package/dist/predicate-v2.cjs.map +1 -1
- package/dist/predicate-v2.d.cts +1 -1
- package/dist/predicate-v2.d.ts +1 -1
- package/dist/predicate-v2.js +70 -15
- package/dist/predicate-v2.js.map +1 -1
- package/dist/sdl.cjs +70 -15
- 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 +70 -15
- package/dist/sdl.js.map +1 -1
- package/package.json +3 -3
- package/templates/orm-namespace/package.json +2 -1
- package/dist/{definition-DjfC7Lll.d.cts → definition-B99BsOAO.d.cts} +4 -4
- package/dist/{definition-DjfC7Lll.d.ts → definition-B99BsOAO.d.ts} +4 -4
package/dist/cli.d.cts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
/** Set by the seq-studio delegate; direct users export them. */
|
|
2
2
|
declare const BASE_URL_ENV = "SEQUENCE_ORM_BASE_URL";
|
|
3
3
|
declare const TOKEN_ENV = "SEQUENCE_ORM_TOKEN";
|
|
4
|
+
/** JSON `{repository_id, commit, path, dirty?}` naming the Git Service source of this apply; set by the seq-studio delegate. */
|
|
5
|
+
declare const SOURCE_ENV = "SEQUENCE_ORM_SOURCE";
|
|
6
|
+
/** Namespace-admin override reason for an apply whose source does not verify; set by `seq-studio orm apply --unverified-source`. */
|
|
7
|
+
declare const UNVERIFIED_SOURCE_REASON_ENV = "SEQUENCE_ORM_UNVERIFIED_SOURCE_REASON";
|
|
8
|
+
/** The apply body's `source` claim, when the delegate supplied one. Atlas verifies it; it never authorizes anything. */
|
|
9
|
+
declare function applySourceFromEnv(env?: NodeJS.ProcessEnv): {
|
|
10
|
+
source?: Record<string, unknown>;
|
|
11
|
+
unverified_source_reason?: string;
|
|
12
|
+
};
|
|
4
13
|
/**
|
|
5
14
|
* Test/dev hook mirroring `LoadNamespaceOptions.ormModulePath`: when set, bare
|
|
6
15
|
* `'@sequenceholdings/orm'` imports in consumer files resolve to this path
|
|
@@ -12,6 +21,8 @@ type TokenProvider = () => Promise<string | null>;
|
|
|
12
21
|
/** Hand the CLI a refreshing token source (used by the seq-studio delegate). */
|
|
13
22
|
declare function setTokenProvider(provider: TokenProvider): void;
|
|
14
23
|
declare function runCli(argv: readonly string[]): Promise<number>;
|
|
24
|
+
/** Where the live version came from: `live since <time> from <commit> on <branch> at <path> by <actor>`. */
|
|
25
|
+
declare function describeActivation(activation: RegistryActivation): string;
|
|
15
26
|
interface ApplyWait {
|
|
16
27
|
wait: boolean;
|
|
17
28
|
timeoutMs: number;
|
|
@@ -24,5 +35,16 @@ declare function parseApplyWait(rest: readonly string[]): ApplyWait;
|
|
|
24
35
|
* details name the variable, column, constraint, or requirement at fault.
|
|
25
36
|
*/
|
|
26
37
|
declare function errorDetail(payload: unknown): string | null;
|
|
38
|
+
/** One row of the namespace's activation history, as the detail route returns it (absent on older Atlas). */
|
|
39
|
+
interface RegistryActivation {
|
|
40
|
+
id: string;
|
|
41
|
+
provenance_class: string;
|
|
42
|
+
source_commit: string | null;
|
|
43
|
+
source_branch: string | null;
|
|
44
|
+
source_path: string | null;
|
|
45
|
+
reported_source: Record<string, unknown> | null;
|
|
46
|
+
actor_sub: string;
|
|
47
|
+
activated_at: string;
|
|
48
|
+
}
|
|
27
49
|
|
|
28
|
-
export { BASE_URL_ENV, ORM_MODULE_PATH_ENV, TOKEN_ENV, errorDetail, parseApplyWait, runCli, setTokenProvider };
|
|
50
|
+
export { BASE_URL_ENV, ORM_MODULE_PATH_ENV, type RegistryActivation, SOURCE_ENV, TOKEN_ENV, UNVERIFIED_SOURCE_REASON_ENV, applySourceFromEnv, describeActivation, errorDetail, parseApplyWait, runCli, setTokenProvider };
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
/** Set by the seq-studio delegate; direct users export them. */
|
|
2
2
|
declare const BASE_URL_ENV = "SEQUENCE_ORM_BASE_URL";
|
|
3
3
|
declare const TOKEN_ENV = "SEQUENCE_ORM_TOKEN";
|
|
4
|
+
/** JSON `{repository_id, commit, path, dirty?}` naming the Git Service source of this apply; set by the seq-studio delegate. */
|
|
5
|
+
declare const SOURCE_ENV = "SEQUENCE_ORM_SOURCE";
|
|
6
|
+
/** Namespace-admin override reason for an apply whose source does not verify; set by `seq-studio orm apply --unverified-source`. */
|
|
7
|
+
declare const UNVERIFIED_SOURCE_REASON_ENV = "SEQUENCE_ORM_UNVERIFIED_SOURCE_REASON";
|
|
8
|
+
/** The apply body's `source` claim, when the delegate supplied one. Atlas verifies it; it never authorizes anything. */
|
|
9
|
+
declare function applySourceFromEnv(env?: NodeJS.ProcessEnv): {
|
|
10
|
+
source?: Record<string, unknown>;
|
|
11
|
+
unverified_source_reason?: string;
|
|
12
|
+
};
|
|
4
13
|
/**
|
|
5
14
|
* Test/dev hook mirroring `LoadNamespaceOptions.ormModulePath`: when set, bare
|
|
6
15
|
* `'@sequenceholdings/orm'` imports in consumer files resolve to this path
|
|
@@ -12,6 +21,8 @@ type TokenProvider = () => Promise<string | null>;
|
|
|
12
21
|
/** Hand the CLI a refreshing token source (used by the seq-studio delegate). */
|
|
13
22
|
declare function setTokenProvider(provider: TokenProvider): void;
|
|
14
23
|
declare function runCli(argv: readonly string[]): Promise<number>;
|
|
24
|
+
/** Where the live version came from: `live since <time> from <commit> on <branch> at <path> by <actor>`. */
|
|
25
|
+
declare function describeActivation(activation: RegistryActivation): string;
|
|
15
26
|
interface ApplyWait {
|
|
16
27
|
wait: boolean;
|
|
17
28
|
timeoutMs: number;
|
|
@@ -24,5 +35,16 @@ declare function parseApplyWait(rest: readonly string[]): ApplyWait;
|
|
|
24
35
|
* details name the variable, column, constraint, or requirement at fault.
|
|
25
36
|
*/
|
|
26
37
|
declare function errorDetail(payload: unknown): string | null;
|
|
38
|
+
/** One row of the namespace's activation history, as the detail route returns it (absent on older Atlas). */
|
|
39
|
+
interface RegistryActivation {
|
|
40
|
+
id: string;
|
|
41
|
+
provenance_class: string;
|
|
42
|
+
source_commit: string | null;
|
|
43
|
+
source_branch: string | null;
|
|
44
|
+
source_path: string | null;
|
|
45
|
+
reported_source: Record<string, unknown> | null;
|
|
46
|
+
actor_sub: string;
|
|
47
|
+
activated_at: string;
|
|
48
|
+
}
|
|
27
49
|
|
|
28
|
-
export { BASE_URL_ENV, ORM_MODULE_PATH_ENV, TOKEN_ENV, errorDetail, parseApplyWait, runCli, setTokenProvider };
|
|
50
|
+
export { BASE_URL_ENV, ORM_MODULE_PATH_ENV, type RegistryActivation, SOURCE_ENV, TOKEN_ENV, UNVERIFIED_SOURCE_REASON_ENV, applySourceFromEnv, describeActivation, errorDetail, parseApplyWait, runCli, setTokenProvider };
|
package/dist/cli.js
CHANGED
|
@@ -102,6 +102,8 @@ function materializedRefreshFunctionName(table) {
|
|
|
102
102
|
function materializeLockKey(schema, table) {
|
|
103
103
|
return `orm:materialize:${schema}.${table}`;
|
|
104
104
|
}
|
|
105
|
+
var OPERATION_DOCUMENTS_FILE = "operations.documents.json";
|
|
106
|
+
var OPERATION_DOCUMENTS_VERSION = 1;
|
|
105
107
|
|
|
106
108
|
// src/hash.ts
|
|
107
109
|
function stableJson(value) {
|
|
@@ -412,6 +414,48 @@ var policyExpressionSchema = z.union([
|
|
|
412
414
|
variants: z.array(policyVariantSchema).max(10).default([])
|
|
413
415
|
})
|
|
414
416
|
]);
|
|
417
|
+
|
|
418
|
+
// src/canonical/resolved-rows.ts
|
|
419
|
+
function logicalKey(table) {
|
|
420
|
+
return (table.primaryKey ?? []).map((columnName) => table.columns.find((c) => c.columnName === columnName)?.name ?? columnName);
|
|
421
|
+
}
|
|
422
|
+
function sameKey(key, expected) {
|
|
423
|
+
return key.length === expected.length && key.every((name2) => expected.includes(name2));
|
|
424
|
+
}
|
|
425
|
+
function validateRowIdentity(input) {
|
|
426
|
+
validateExistsIn(input);
|
|
427
|
+
if (input.keys.length > 1) validateCompositeKey(input);
|
|
428
|
+
}
|
|
429
|
+
function validateExistsIn({ resolved, sourceByName, fail: fail4 }) {
|
|
430
|
+
const name2 = resolved.existsIn?.[0];
|
|
431
|
+
if (name2 === void 0) return;
|
|
432
|
+
if (sourceByName.get(name2)?.kind !== "etl") fail4(`existsIn '${name2}' must be an etl source`);
|
|
433
|
+
if (resolved.policies.insert !== void 0 || resolved.policies.write !== void 0) {
|
|
434
|
+
fail4("existsIn allows only an update policy: rows come from the etl source");
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
function validateCompositeKey({ resolved, keys, tableByName, fail: fail4 }) {
|
|
438
|
+
const keyList = `(${keys.join(", ")})`;
|
|
439
|
+
if (resolved.existsIn === void 0) {
|
|
440
|
+
fail4(`requires exactly one primary-key column: composite key ${keyList} requires existsIn`);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const writes = ["insert", "update", "write"].filter((kind) => resolved.policies[kind] !== void 0);
|
|
444
|
+
if (writes.length > 0) fail4(`composite key ${keyList} requires a read-only table: remove the ${writes.join("/")} policy`);
|
|
445
|
+
if (resolved.materialized) fail4(`composite key ${keyList} cannot be materialized`);
|
|
446
|
+
for (const source of resolved.sources) {
|
|
447
|
+
if (source.kind === "prefetch") {
|
|
448
|
+
fail4(`composite key ${keyList} cannot have prefetch source '${source.name}'`);
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
const table = tableByName.get(source.table);
|
|
452
|
+
if (table && !sameKey(logicalKey(table), keys)) {
|
|
453
|
+
fail4(`composite key ${keyList} requires ${source.kind} source '${source.name}' to be keyed by it; table '${source.table}' is keyed by (${logicalKey(table).join(", ")})`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// src/canonical/resolved.ts
|
|
415
459
|
function parseDurationMs(input) {
|
|
416
460
|
if (typeof input === "number") return Number.isInteger(input) && input > 0 ? input : void 0;
|
|
417
461
|
const match = /^(\d+(?:\.\d+)?)(ms|s|m)$/.exec(input);
|
|
@@ -488,6 +532,13 @@ var canonicalResolvedSchema = z.object({
|
|
|
488
532
|
resolve: z.array(canonicalResolveRuleSchema).max(500),
|
|
489
533
|
/** Sources whose tombstone hides the resolved row (v1: the manual source only). */
|
|
490
534
|
deletedBy: z.array(identifier).max(1).default([]),
|
|
535
|
+
/**
|
|
536
|
+
* `rows.existsIn`: the one etl source whose rows are the table's rows (read
|
|
537
|
+
* directly as the row source). Only an update policy may write. It is also
|
|
538
|
+
* what admits a composite primary key: a read-only table keyed exactly like
|
|
539
|
+
* that source.
|
|
540
|
+
*/
|
|
541
|
+
existsIn: z.array(identifier).length(1).optional(),
|
|
491
542
|
/**
|
|
492
543
|
* Materialized: the view reads an indexed internal table (`table`) that the
|
|
493
544
|
* data plane keeps in step with `compute`, the internal view holding the
|
|
@@ -506,8 +557,8 @@ function validateResolvedView({ view, tables, views = [] }) {
|
|
|
506
557
|
if (!resolved) return [];
|
|
507
558
|
const errors = [];
|
|
508
559
|
const fail4 = (message) => errors.push(`resolved table '${view.view}': ${message}`);
|
|
509
|
-
const
|
|
510
|
-
if (
|
|
560
|
+
const keyColumns = (view.primaryKey ?? []).map((columnName) => view.columns.find((c) => c.columnName === columnName)).filter((column) => column !== void 0);
|
|
561
|
+
if (keyColumns.length === 0 || keyColumns.length !== view.primaryKey?.length) {
|
|
511
562
|
fail4("requires exactly one primary-key column");
|
|
512
563
|
return errors;
|
|
513
564
|
}
|
|
@@ -515,7 +566,7 @@ function validateResolvedView({ view, tables, views = [] }) {
|
|
|
515
566
|
const scope = {
|
|
516
567
|
view,
|
|
517
568
|
resolved,
|
|
518
|
-
|
|
569
|
+
keys: new Map(keyColumns.map((column) => [column.name, column])),
|
|
519
570
|
dataColumns: new Map(view.columns.filter((c) => !systemNames.has(c.name) && c.cell === void 0).map((c) => [c.name, c])),
|
|
520
571
|
sourceByName: /* @__PURE__ */ new Map(),
|
|
521
572
|
fail: fail4
|
|
@@ -529,6 +580,7 @@ function validateResolvedView({ view, tables, views = [] }) {
|
|
|
529
580
|
if (!table) fail4(`source '${source.name}' references unknown table '${source.table}'`);
|
|
530
581
|
else validateSource({ scope, source, table });
|
|
531
582
|
}
|
|
583
|
+
validateRowIdentity({ resolved, keys: [...scope.keys.keys()], sourceByName: scope.sourceByName, tableByName, fail: fail4 });
|
|
532
584
|
validateRules(scope);
|
|
533
585
|
validateViewCells(scope);
|
|
534
586
|
if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
|
|
@@ -558,7 +610,7 @@ function validateSource({ scope, source, table }) {
|
|
|
558
610
|
validatePrefetchFields({ scope, source });
|
|
559
611
|
}
|
|
560
612
|
function validatePrefetchFields({ scope, source }) {
|
|
561
|
-
const { fail: fail4,
|
|
613
|
+
const { fail: fail4, keys } = scope;
|
|
562
614
|
if (source.kind !== "prefetch") {
|
|
563
615
|
if (source.key || source.fetch || source.function || source.prefetchDefaults || source.writeback) {
|
|
564
616
|
fail4(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
|
|
@@ -567,7 +619,7 @@ function validatePrefetchFields({ scope, source }) {
|
|
|
567
619
|
}
|
|
568
620
|
if (!source.key || !source.fetch || !source.function || !source.prefetchDefaults) {
|
|
569
621
|
fail4(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
|
|
570
|
-
} else if (source.key
|
|
622
|
+
} else if (!keys.has(source.key) && !source.columns.includes(source.key)) {
|
|
571
623
|
fail4(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
|
|
572
624
|
}
|
|
573
625
|
for (const column of source.writeback?.columns ?? []) {
|
|
@@ -575,19 +627,19 @@ function validatePrefetchFields({ scope, source }) {
|
|
|
575
627
|
}
|
|
576
628
|
}
|
|
577
629
|
function validateSourceColumns({ scope, source, byName }) {
|
|
578
|
-
const { fail: fail4,
|
|
579
|
-
for (const column of [
|
|
580
|
-
const own = column
|
|
630
|
+
const { fail: fail4, keys, dataColumns } = scope;
|
|
631
|
+
for (const column of [...keys.keys(), ...source.columns]) {
|
|
632
|
+
const own = keys.get(column) ?? dataColumns.get(column);
|
|
581
633
|
const theirs = byName.get(column);
|
|
582
634
|
if (!own) fail4(`source '${source.name}' supplies unknown column '${column}'`);
|
|
583
635
|
else if (!theirs) fail4(`source '${source.name}' table '${source.table}' has no column '${column}'`);
|
|
584
636
|
else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail4(`column '${column}' has a different type on source '${source.name}'`);
|
|
585
637
|
}
|
|
586
638
|
}
|
|
587
|
-
function validateRules({ resolved,
|
|
639
|
+
function validateRules({ resolved, keys, dataColumns, sourceByName, fail: fail4 }) {
|
|
588
640
|
const ruled = /* @__PURE__ */ new Set();
|
|
589
641
|
for (const rule of resolved.resolve) {
|
|
590
|
-
if (!dataColumns.has(rule.column) || rule.column
|
|
642
|
+
if (!dataColumns.has(rule.column) || keys.has(rule.column)) {
|
|
591
643
|
fail4(`resolve rule targets unknown column '${rule.column}'`);
|
|
592
644
|
continue;
|
|
593
645
|
}
|
|
@@ -604,7 +656,7 @@ function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail: fa
|
|
|
604
656
|
}
|
|
605
657
|
}
|
|
606
658
|
for (const column of dataColumns.keys()) {
|
|
607
|
-
if (column
|
|
659
|
+
if (!keys.has(column) && !ruled.has(column)) fail4(`column '${column}' has no resolve rule`);
|
|
608
660
|
}
|
|
609
661
|
}
|
|
610
662
|
var VIEW_CELL_TYPES = {
|
|
@@ -613,10 +665,10 @@ var VIEW_CELL_TYPES = {
|
|
|
613
665
|
by: "text",
|
|
614
666
|
conflicts: "jsonb"
|
|
615
667
|
};
|
|
616
|
-
function validateViewCells({ view, dataColumns,
|
|
668
|
+
function validateViewCells({ view, dataColumns, keys, fail: fail4 }) {
|
|
617
669
|
const expected = /* @__PURE__ */ new Map();
|
|
618
670
|
for (const column of dataColumns.values()) {
|
|
619
|
-
if (column.name
|
|
671
|
+
if (keys.has(column.name)) continue;
|
|
620
672
|
for (const part of RESOLVED_VIEW_CELL_PARTS) {
|
|
621
673
|
expected.set(cellColumnName(column.name, part), { part, columnName: cellColumnName(column.columnName, part) });
|
|
622
674
|
}
|
|
@@ -1842,8 +1894,13 @@ var resolvedSourceIrSchema = z.object({
|
|
|
1842
1894
|
}).optional()
|
|
1843
1895
|
});
|
|
1844
1896
|
var resolvedMetaIrSchema = z.object({
|
|
1845
|
-
/**
|
|
1846
|
-
|
|
1897
|
+
/**
|
|
1898
|
+
* Logical pk column of the resolved view. Absent for a composite key, which
|
|
1899
|
+
* only a read-only `existsIn` table may declare: it has no writes, prefetch
|
|
1900
|
+
* or materialization (the paths that address one key column), and its reads
|
|
1901
|
+
* use the view's `primaryKey` like any keyed view.
|
|
1902
|
+
*/
|
|
1903
|
+
pk: z.string().optional(),
|
|
1847
1904
|
/** Source name of the manual source. */
|
|
1848
1905
|
manual: z.string(),
|
|
1849
1906
|
sources: z.array(resolvedSourceIrSchema),
|
|
@@ -3268,7 +3325,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
3268
3325
|
appendable: readOnly ? [] : active.filter((c) => c.type === "jsonb").map((c) => c.name),
|
|
3269
3326
|
...touchOnUpdate.length > 0 ? { touchOnUpdate } : {},
|
|
3270
3327
|
...cells ? { cells } : {},
|
|
3271
|
-
...resolved ? { resolved: resolvedMeta({ resolved,
|
|
3328
|
+
...resolved ? { resolved: resolvedMeta({ resolved, primaryKey: primaryKey2 }) } : {},
|
|
3272
3329
|
constraints: isView ? [] : constraints,
|
|
3273
3330
|
dynamicFilterMaxDepth: 6,
|
|
3274
3331
|
dynamicFilterMaxNodes: 64
|
|
@@ -3550,10 +3607,10 @@ function resolvedWritable(entries) {
|
|
|
3550
3607
|
const system = new Set(Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).map((column) => column.name));
|
|
3551
3608
|
return entries.filter((entry) => !entry.deprecated && !system.has(entry.name)).map((entry) => entry.name);
|
|
3552
3609
|
}
|
|
3553
|
-
function resolvedMeta({ resolved,
|
|
3610
|
+
function resolvedMeta({ resolved, primaryKey: primaryKey2 }) {
|
|
3554
3611
|
const manual = resolved.sources.find((source) => source.kind === "manual");
|
|
3555
3612
|
return {
|
|
3556
|
-
pk,
|
|
3613
|
+
...primaryKey2.length === 1 ? { pk: primaryKey2[0] } : {},
|
|
3557
3614
|
manual: manual?.name ?? "",
|
|
3558
3615
|
deletedBy: [...resolved.deletedBy],
|
|
3559
3616
|
...resolved.materialized ? { materialized: { ...resolved.materialized } } : {},
|
|
@@ -3679,7 +3736,9 @@ function applyPrefetchDirectives({ node, root, meta }) {
|
|
|
3679
3736
|
if (directive.maxStaleness !== void 0 && maxStalenessMs === void 0) {
|
|
3680
3737
|
fail(`@prefetch(maxStaleness: "${directive.maxStaleness}") must look like '500ms', '30s' or '5m'`, "invalid-source-directive");
|
|
3681
3738
|
}
|
|
3682
|
-
const
|
|
3739
|
+
const pk = meta.resolved?.pk;
|
|
3740
|
+
if (pk === void 0) return fail(`@prefetch on '${root.alias}' needs a single-column primary key`, "invalid-source-directive");
|
|
3741
|
+
const key = root.kind === "byPk" ? { one: root.pk[0].value } : selectKey({ where: root.where, pk, source, alias: root.alias });
|
|
3683
3742
|
return { source: source.name, ...maxStalenessMs !== void 0 ? { maxStalenessMs } : {}, key };
|
|
3684
3743
|
});
|
|
3685
3744
|
return { ...root, prefetch: plans };
|
|
@@ -6575,6 +6634,7 @@ function buildCanonicalResolved(resolved) {
|
|
|
6575
6634
|
})),
|
|
6576
6635
|
resolve: resolved.rules.map((rule) => ({ ...rule, candidates: [...rule.candidates] })),
|
|
6577
6636
|
deletedBy: [...resolved.deletedBy],
|
|
6637
|
+
...resolved.existsIn !== void 0 ? { existsIn: [...resolved.existsIn] } : {},
|
|
6578
6638
|
...resolved.materialized ? { materialized: { ...resolved.materialized } } : {},
|
|
6579
6639
|
policies
|
|
6580
6640
|
};
|
|
@@ -10285,6 +10345,42 @@ async function readSnapshot(dir, ordinal) {
|
|
|
10285
10345
|
const path = join(dir, MIGRATIONS_DIRNAME, META_DIRNAME, `${pad(ordinal)}.snapshot.json`);
|
|
10286
10346
|
return parseJson(path, await readSnapshotText(dir, ordinal));
|
|
10287
10347
|
}
|
|
10348
|
+
async function verifyCommittedLedger(dir, entries) {
|
|
10349
|
+
const firstRequiredSnapshot = entries.length - 2;
|
|
10350
|
+
let previousId3;
|
|
10351
|
+
let previousSnapshotPresent = false;
|
|
10352
|
+
let previousHash = null;
|
|
10353
|
+
for (const [index3, entry] of entries.entries()) {
|
|
10354
|
+
const sql4 = await readMigrationSql(dir, entry);
|
|
10355
|
+
if (checksumOf(sql4) !== entry.checksum) {
|
|
10356
|
+
return `${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`;
|
|
10357
|
+
}
|
|
10358
|
+
const snapshotText = index3 >= firstRequiredSnapshot ? await readSnapshotText(dir, entry.ordinal) : await readPrunableSnapshotText(dir, entry.ordinal);
|
|
10359
|
+
if (snapshotText !== void 0) {
|
|
10360
|
+
if (checksumOf(snapshotText) !== entry.snapshot_checksum) {
|
|
10361
|
+
return `the snapshot for ${entry.name} does not match its journal checksum \u2014 committed snapshots must not be edited (the next diff builds on it)`;
|
|
10362
|
+
}
|
|
10363
|
+
const snapshot = JSON.parse(snapshotText);
|
|
10364
|
+
if (previousSnapshotPresent && snapshot.prevId !== previousId3) {
|
|
10365
|
+
return `snapshot chain broken at ${entry.name} \u2014 regenerate the workspace with \`seq-studio orm diff\``;
|
|
10366
|
+
}
|
|
10367
|
+
previousId3 = snapshot.id;
|
|
10368
|
+
}
|
|
10369
|
+
previousSnapshotPresent = snapshotText !== void 0;
|
|
10370
|
+
if (entry.previous_content_hash !== previousHash) {
|
|
10371
|
+
return `content-hash chain broken at ${entry.name} \u2014 migrations must be appended in order by \`seq-studio orm diff\``;
|
|
10372
|
+
}
|
|
10373
|
+
previousHash = entry.content_hash;
|
|
10374
|
+
}
|
|
10375
|
+
return void 0;
|
|
10376
|
+
}
|
|
10377
|
+
async function readPrunableSnapshotText(dir, ordinal) {
|
|
10378
|
+
const path = join(dir, MIGRATIONS_DIRNAME, META_DIRNAME, `${pad(ordinal)}.snapshot.json`);
|
|
10379
|
+
return readFile(path, "utf8").catch((error) => {
|
|
10380
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
|
|
10381
|
+
throw error;
|
|
10382
|
+
});
|
|
10383
|
+
}
|
|
10288
10384
|
async function readMigrationSql(dir, entry) {
|
|
10289
10385
|
const path = join(dir, MIGRATIONS_DIRNAME, `${entry.name}.sql`);
|
|
10290
10386
|
return readFile(path, "utf8").catch(() => {
|
|
@@ -10445,30 +10541,10 @@ async function checkWorkspace({
|
|
|
10445
10541
|
log(`no committed migrations for '${loaded.definition.namespace}' \u2014 run \`seq-studio orm diff\` and commit the result`);
|
|
10446
10542
|
return 1;
|
|
10447
10543
|
}
|
|
10448
|
-
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
|
|
10452
|
-
if (checksumOf(sql4) !== entry.checksum) {
|
|
10453
|
-
log(`${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`);
|
|
10454
|
-
return 1;
|
|
10455
|
-
}
|
|
10456
|
-
const snapshotText = await readSnapshotText(dir, entry.ordinal);
|
|
10457
|
-
if (checksumOf(snapshotText) !== entry.snapshot_checksum) {
|
|
10458
|
-
log(`the snapshot for ${entry.name} does not match its journal checksum \u2014 committed snapshots must not be edited (the next diff builds on it)`);
|
|
10459
|
-
return 1;
|
|
10460
|
-
}
|
|
10461
|
-
const snapshot = JSON.parse(snapshotText);
|
|
10462
|
-
if (entry.ordinal > 1 && snapshot.prevId !== previousId3) {
|
|
10463
|
-
log(`snapshot chain broken at ${entry.name} \u2014 regenerate the workspace with \`seq-studio orm diff\``);
|
|
10464
|
-
return 1;
|
|
10465
|
-
}
|
|
10466
|
-
if (entry.previous_content_hash !== previousHash) {
|
|
10467
|
-
log(`content-hash chain broken at ${entry.name} \u2014 migrations must be appended in order by \`seq-studio orm diff\``);
|
|
10468
|
-
return 1;
|
|
10469
|
-
}
|
|
10470
|
-
previousId3 = snapshot.id;
|
|
10471
|
-
previousHash = entry.content_hash;
|
|
10544
|
+
const broken = await verifyCommittedLedger(dir, workspace.journal.entries);
|
|
10545
|
+
if (broken) {
|
|
10546
|
+
log(broken);
|
|
10547
|
+
return 1;
|
|
10472
10548
|
}
|
|
10473
10549
|
if (tip.content_hash !== loaded.contentHash) {
|
|
10474
10550
|
log(`definitions changed since ${tip.name} \u2014 run \`seq-studio orm diff\` and commit the new migration`);
|
|
@@ -11347,6 +11423,22 @@ function parseArgs(argv) {
|
|
|
11347
11423
|
// src/cli.ts
|
|
11348
11424
|
var BASE_URL_ENV = "SEQUENCE_ORM_BASE_URL";
|
|
11349
11425
|
var TOKEN_ENV = "SEQUENCE_ORM_TOKEN";
|
|
11426
|
+
var SOURCE_ENV = "SEQUENCE_ORM_SOURCE";
|
|
11427
|
+
var UNVERIFIED_SOURCE_REASON_ENV = "SEQUENCE_ORM_UNVERIFIED_SOURCE_REASON";
|
|
11428
|
+
function applySourceFromEnv(env = process.env) {
|
|
11429
|
+
const reason = env[UNVERIFIED_SOURCE_REASON_ENV]?.trim();
|
|
11430
|
+
const override = reason ? { unverified_source_reason: reason } : {};
|
|
11431
|
+
const raw = env[SOURCE_ENV];
|
|
11432
|
+
if (!raw) return override;
|
|
11433
|
+
let parsed;
|
|
11434
|
+
try {
|
|
11435
|
+
parsed = JSON.parse(raw);
|
|
11436
|
+
} catch {
|
|
11437
|
+
throw new Error(`${SOURCE_ENV} is not valid JSON`);
|
|
11438
|
+
}
|
|
11439
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`${SOURCE_ENV} must be a JSON object`);
|
|
11440
|
+
return { source: Object.fromEntries(Object.entries(parsed)), ...override };
|
|
11441
|
+
}
|
|
11350
11442
|
var ORM_MODULE_PATH_ENV = "SEQUENCE_ORM_MODULE_PATH";
|
|
11351
11443
|
var USAGE = `usage:
|
|
11352
11444
|
seq-studio orm init <dir> scaffold a namespace package (v2: TypeScript schema + persisted operations)
|
|
@@ -11664,15 +11756,21 @@ async function generateCommand(rest) {
|
|
|
11664
11756
|
await writeGenerateOutputs(dir, compiled.project);
|
|
11665
11757
|
const { loaded, manifest } = compiled.project;
|
|
11666
11758
|
console.log(
|
|
11667
|
-
`wrote ${SCHEMA_GRAPHQL_FILE}, ${OPERATIONS_MANIFEST_FILE}, ${TYPE_POLICIES_FILE}, sql-operations.gen.ts, namespace.contract.json (${loaded.canonical.tables.length} table(s), ${loaded.canonical.views?.length ?? 0} view(s), ${Object.keys(manifest.operations).length} operation(s))`
|
|
11759
|
+
`wrote ${SCHEMA_GRAPHQL_FILE}, ${OPERATIONS_MANIFEST_FILE}, ${OPERATION_DOCUMENTS_FILE}, ${TYPE_POLICIES_FILE}, sql-operations.gen.ts, namespace.contract.json (${loaded.canonical.tables.length} table(s), ${loaded.canonical.views?.length ?? 0} view(s), ${Object.keys(manifest.operations).length} operation(s))`
|
|
11668
11760
|
);
|
|
11669
11761
|
return runConsumerCodegen(dir);
|
|
11670
11762
|
}
|
|
11671
11763
|
async function writeGenerateOutputs(dir, project) {
|
|
11672
|
-
const { loaded, sdl, manifest } = project;
|
|
11764
|
+
const { loaded, sdl, manifest, documents } = project;
|
|
11673
11765
|
await writeFile(join(dir, SCHEMA_GRAPHQL_FILE), sdl);
|
|
11674
11766
|
await writeFile(join(dir, "namespace.contract.json"), `${JSON.stringify(loaded.canonical, null, 2)}
|
|
11675
11767
|
`);
|
|
11768
|
+
const sortedDocuments = [...documents].sort((a, b) => a.path.localeCompare(b.path)).map(({ path, contents }) => ({ path, contents }));
|
|
11769
|
+
await writeFile(
|
|
11770
|
+
join(dir, OPERATION_DOCUMENTS_FILE),
|
|
11771
|
+
`${JSON.stringify({ documentsVersion: OPERATION_DOCUMENTS_VERSION, documents: sortedDocuments }, null, 2)}
|
|
11772
|
+
`
|
|
11773
|
+
);
|
|
11676
11774
|
const operations = Object.entries(manifest.operations).map(([name2, op]) => ({
|
|
11677
11775
|
name: name2,
|
|
11678
11776
|
kind: op.kind,
|
|
@@ -11878,8 +11976,20 @@ function reportRegistryState(registry) {
|
|
|
11878
11976
|
if (match && match.id === registry.activeVersionId) console.log("-- registry: this content is the ACTIVE version");
|
|
11879
11977
|
else if (match) console.log(`-- registry: registered (status ${match.status}), not active`);
|
|
11880
11978
|
else console.log(`-- registry: new content (active is ${active ? active.content_hash.slice(0, 12) : "none"})`);
|
|
11979
|
+
if (registry.activeActivation) console.log(`-- registry: ${describeActivation(registry.activeActivation)}`);
|
|
11881
11980
|
return 0;
|
|
11882
11981
|
}
|
|
11982
|
+
function describeActivation(activation) {
|
|
11983
|
+
const since = `live since ${activation.activated_at.slice(0, 16).replace("T", " ")}Z`;
|
|
11984
|
+
const by = `by ${activation.actor_sub.replace(/^google-apps\|/, "")}`;
|
|
11985
|
+
if (activation.source_commit && activation.source_branch) {
|
|
11986
|
+
const at = activation.source_path ? ` at ${activation.source_path}` : "";
|
|
11987
|
+
return `${since} from ${activation.source_commit.slice(0, 12)} on ${activation.source_branch}${at} (${activation.provenance_class}) ${by}`;
|
|
11988
|
+
}
|
|
11989
|
+
const failure = activation.reported_source?.verification_failure;
|
|
11990
|
+
const why = typeof failure === "string" ? `: ${failure}` : "";
|
|
11991
|
+
return `${since} from an untraced source (${activation.provenance_class}${why}) ${by}`;
|
|
11992
|
+
}
|
|
11883
11993
|
var META_DIRNAME2 = "meta";
|
|
11884
11994
|
var JOURNAL_FILENAME2 = "journal.json";
|
|
11885
11995
|
var padOrdinal = (ordinal) => String(ordinal).padStart(4, "0");
|
|
@@ -11967,30 +12077,10 @@ async function checkWorkspaceV2({
|
|
|
11967
12077
|
log(`no committed migrations for '${namespace}' \u2014 run \`seq-studio orm diff\` and commit the result`);
|
|
11968
12078
|
return 1;
|
|
11969
12079
|
}
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
if (checksumOf(sql4) !== entry.checksum) {
|
|
11975
|
-
log(`${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`);
|
|
11976
|
-
return 1;
|
|
11977
|
-
}
|
|
11978
|
-
const snapshotText = await readSnapshotText(dir, entry.ordinal);
|
|
11979
|
-
if (checksumOf(snapshotText) !== entry.snapshot_checksum) {
|
|
11980
|
-
log(`the snapshot for ${entry.name} does not match its journal checksum \u2014 committed snapshots must not be edited (the next diff builds on it)`);
|
|
11981
|
-
return 1;
|
|
11982
|
-
}
|
|
11983
|
-
const snapshot = JSON.parse(snapshotText);
|
|
11984
|
-
if (entry.ordinal > 1 && snapshot.prevId !== previousId3) {
|
|
11985
|
-
log(`snapshot chain broken at ${entry.name} \u2014 regenerate the workspace with \`seq-studio orm diff\``);
|
|
11986
|
-
return 1;
|
|
11987
|
-
}
|
|
11988
|
-
if (entry.previous_content_hash !== previousHash) {
|
|
11989
|
-
log(`content-hash chain broken at ${entry.name} \u2014 migrations must be appended in order by \`seq-studio orm diff\``);
|
|
11990
|
-
return 1;
|
|
11991
|
-
}
|
|
11992
|
-
previousId3 = snapshot.id;
|
|
11993
|
-
previousHash = entry.content_hash;
|
|
12080
|
+
const broken = await verifyCommittedLedger(dir, workspace.journal.entries);
|
|
12081
|
+
if (broken) {
|
|
12082
|
+
log(broken);
|
|
12083
|
+
return 1;
|
|
11994
12084
|
}
|
|
11995
12085
|
if (tip.content_hash !== loaded.hash) {
|
|
11996
12086
|
log(`definitions changed since ${tip.name} \u2014 run \`seq-studio orm diff\` and commit the new migration`);
|
|
@@ -12104,7 +12194,8 @@ async function applyV2Command(rest, dir) {
|
|
|
12104
12194
|
const applied = await apiRequestStrict("POST", `/api/orm/namespaces/${namespace}/apply`, {
|
|
12105
12195
|
version_id: registered.version_id,
|
|
12106
12196
|
...allowDestructive ? { allow_destructive: true } : {},
|
|
12107
|
-
...schemaAlreadyActive && registered.operation_set_id ? { operation_set_id: registered.operation_set_id } : {}
|
|
12197
|
+
...schemaAlreadyActive && registered.operation_set_id ? { operation_set_id: registered.operation_set_id } : {},
|
|
12198
|
+
...applySourceFromEnv()
|
|
12108
12199
|
});
|
|
12109
12200
|
const live = { namespace, contentHash: registered.content_hash };
|
|
12110
12201
|
if ("ops_only" in applied) {
|
|
@@ -12561,7 +12652,8 @@ async function applyCommand(rest) {
|
|
|
12561
12652
|
if (dryRun) return dryRunRehearsal({ namespace, versionId: registered.version_id, contentHash: registered.content_hash });
|
|
12562
12653
|
const applied = await apiRequestStrict("POST", `/api/orm/namespaces/${namespace}/apply`, {
|
|
12563
12654
|
version_id: registered.version_id,
|
|
12564
|
-
...allowDestructive ? { allow_destructive: true } : {}
|
|
12655
|
+
...allowDestructive ? { allow_destructive: true } : {},
|
|
12656
|
+
...applySourceFromEnv()
|
|
12565
12657
|
});
|
|
12566
12658
|
if ("status" in applied) {
|
|
12567
12659
|
const verdict = await awaitQueuedApply({ dir, namespace, contentHash: registered.content_hash, runId: applied.run_id, applyWait });
|
|
@@ -12741,11 +12833,13 @@ async function apiRequestStrict(method, path, body) {
|
|
|
12741
12833
|
}
|
|
12742
12834
|
async function fetchRegistryState(namespace, contentHash) {
|
|
12743
12835
|
if (!process.env[BASE_URL_ENV]?.trim() || !await resolveToken()) return { kind: "offline" };
|
|
12744
|
-
const body = await apiRequest("GET", `/api/orm/namespaces/${namespace}?hash=${encodeURIComponent(contentHash)}`, void 0, { allow404: true });
|
|
12836
|
+
const body = await apiRequest("GET", `/api/orm/namespaces/${namespace}?hash=${encodeURIComponent(contentHash)}&activations_limit=1`, void 0, { allow404: true });
|
|
12745
12837
|
if (!body) return { kind: "missing" };
|
|
12838
|
+
const latest = body.activations?.[0];
|
|
12746
12839
|
return {
|
|
12747
12840
|
kind: "found",
|
|
12748
12841
|
activeVersionId: body.namespace.active_version_id,
|
|
12842
|
+
activeActivation: latest && latest.id === body.namespace.active_activation_id ? latest : null,
|
|
12749
12843
|
versions: body.versions,
|
|
12750
12844
|
hashVersion: body.hash_version ?? null
|
|
12751
12845
|
};
|
|
@@ -12760,6 +12854,6 @@ function positional(rest) {
|
|
|
12760
12854
|
}
|
|
12761
12855
|
var firstLine2 = (sql4) => sql4.split("\n")[0] ?? sql4;
|
|
12762
12856
|
|
|
12763
|
-
export { BASE_URL_ENV, ORM_MODULE_PATH_ENV, TOKEN_ENV, errorDetail, parseApplyWait, runCli, setTokenProvider };
|
|
12857
|
+
export { BASE_URL_ENV, ORM_MODULE_PATH_ENV, SOURCE_ENV, TOKEN_ENV, UNVERIFIED_SOURCE_REASON_ENV, applySourceFromEnv, describeActivation, errorDetail, parseApplyWait, runCli, setTokenProvider };
|
|
12764
12858
|
//# sourceMappingURL=cli.js.map
|
|
12765
12859
|
//# sourceMappingURL=cli.js.map
|