@supacloud/compiler 0.59.0 → 0.60.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 +12 -6
- package/dist/cli.js +111 -64
- package/dist/database-contracts.d.ts +4 -0
- package/dist/database-governance-runtime.d.ts +1 -1
- package/dist/database-governance-worker.js +29 -6
- package/dist/database-governance.d.ts +1 -1
- package/dist/execution-snapshot.d.ts +1 -0
- package/dist/index.js +111 -64
- package/dist/inspect.d.ts +2 -0
- package/dist/migration-elysia-version.d.ts +1 -1
- package/dist/types.d.ts +9 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -86,7 +86,8 @@ attempts. The default policy requires `requireRouteEffects`,
|
|
|
86
86
|
## Unified Database Contracts
|
|
87
87
|
|
|
88
88
|
Database contracts are protocol-aware: configure only the database sources the
|
|
89
|
-
application uses (`postgrestTypes`, `modelSchema`,
|
|
89
|
+
application uses (`postgrestTypes`, `modelSchema`, `graphql`, `rpcRegistry` or
|
|
90
|
+
`rpcCatalog`). The generated
|
|
90
91
|
`database.manifest.json` is a TypeBox-validated, fingerprinted build boundary;
|
|
91
92
|
consumers must reject it when its schema or migration fingerprint is stale.
|
|
92
93
|
|
|
@@ -99,6 +100,8 @@ type entry point while preserving PostgREST, ZModel and GraphQL native types:
|
|
|
99
100
|
"outDir": "generated",
|
|
100
101
|
"postgrestTypes": "database.types.ts",
|
|
101
102
|
"modelSchema": "zenstack/generated/schema.ts",
|
|
103
|
+
"rpcRegistry": "db/rpc-registry.json",
|
|
104
|
+
"rpcCatalog": "db/contracts/rpc-catalog.json",
|
|
102
105
|
"role": "authenticated",
|
|
103
106
|
"graphql": {
|
|
104
107
|
"schema": "graphql/schema.graphql",
|
|
@@ -114,11 +117,14 @@ snapshot and a role-scoped `pg_graphql` schema snapshot. The generated barrel
|
|
|
114
117
|
exports `Database`, `QueryData`, ZModel schema types and GraphQL contracts.
|
|
115
118
|
Consumers need `@supabase/supabase-js` and their ZenStack dependencies.
|
|
116
119
|
|
|
117
|
-
`--check` detects drift in supplied snapshots, local ZModel imports,
|
|
118
|
-
content/order and generated artifacts without
|
|
119
|
-
not proof of live database parity or
|
|
120
|
-
intended database and role before
|
|
121
|
-
boundaries; protocol result shapes
|
|
120
|
+
`--check` detects drift in supplied snapshots, local ZModel imports, RPC
|
|
121
|
+
registry/catalog inputs, migration content/order and generated artifacts without
|
|
122
|
+
writing. This is an offline check, not proof of live database parity or
|
|
123
|
+
authorization. Refresh snapshots from the intended database and role before
|
|
124
|
+
running it. Keep runtime decoders at untrusted boundaries; protocol result shapes
|
|
125
|
+
are not interchangeable. When `databaseGovernance.rpcBoundary` is configured,
|
|
126
|
+
the compiler also fails if a named Command RPC capability has no registry/catalog
|
|
127
|
+
source or explicit historical entry.
|
|
122
128
|
|
|
123
129
|
Architecture references: Micronaut for compile-time AOP, NestJS for feature modules,
|
|
124
130
|
and Angular for DI semantics. Compiled DI requires constructor injection and generated scope factories.
|
package/dist/cli.js
CHANGED
|
@@ -2097,6 +2097,47 @@ function allAspects(module) {
|
|
|
2097
2097
|
...(module.jobs ?? []).flatMap((job) => job.aspects ?? [])
|
|
2098
2098
|
];
|
|
2099
2099
|
}
|
|
2100
|
+
function uniqueStages(paths) {
|
|
2101
|
+
return [...new Set(paths.flat())];
|
|
2102
|
+
}
|
|
2103
|
+
function commandStagePaths(command, aspects) {
|
|
2104
|
+
const prefix = ["commandExecutor", "authorize"];
|
|
2105
|
+
if (command.branches?.length) {
|
|
2106
|
+
const branchPaths = command.branches.map((branch) => [
|
|
2107
|
+
...prefix,
|
|
2108
|
+
`rpc:${branch.rpc}`,
|
|
2109
|
+
...aspects,
|
|
2110
|
+
"handler"
|
|
2111
|
+
]);
|
|
2112
|
+
if (command.branchesFallback === "command") {
|
|
2113
|
+
branchPaths.push([
|
|
2114
|
+
...prefix,
|
|
2115
|
+
...command.idempotency === "required" ? ["idempotency"] : [],
|
|
2116
|
+
...command.transaction === "required" ? ["transaction"] : [],
|
|
2117
|
+
...aspects,
|
|
2118
|
+
"handler",
|
|
2119
|
+
...command.audit ? ["audit"] : []
|
|
2120
|
+
]);
|
|
2121
|
+
}
|
|
2122
|
+
return branchPaths;
|
|
2123
|
+
}
|
|
2124
|
+
return [[
|
|
2125
|
+
...prefix,
|
|
2126
|
+
...command.rpc ? [`rpc:${command.rpc}`] : [
|
|
2127
|
+
...command.idempotency === "required" ? ["idempotency"] : [],
|
|
2128
|
+
...command.transaction === "required" ? ["transaction"] : []
|
|
2129
|
+
],
|
|
2130
|
+
...aspects,
|
|
2131
|
+
"handler",
|
|
2132
|
+
...command.audit && !command.rpc ? ["audit"] : []
|
|
2133
|
+
]];
|
|
2134
|
+
}
|
|
2135
|
+
function planStages(paths, branches) {
|
|
2136
|
+
const stages = uniqueStages(paths);
|
|
2137
|
+
if (branches && !stages.includes("rpc:selected-branch"))
|
|
2138
|
+
stages.splice(2, 0, "rpc:selected-branch");
|
|
2139
|
+
return paths.length > 1 ? { stages, stagePaths: paths } : { stages };
|
|
2140
|
+
}
|
|
2100
2141
|
function createExecutionPlans(graph) {
|
|
2101
2142
|
const aspects = (boundary, refs = []) => refs.map((ref, index) => `${boundary}.aspect[${index}]:${ref.name}`);
|
|
2102
2143
|
return graph.modules.flatMap((module) => [
|
|
@@ -2106,46 +2147,34 @@ function createExecutionPlans(graph) {
|
|
|
2106
2147
|
const command = module.commands.find((item) => item.className === commandRef || item.name === commandRef);
|
|
2107
2148
|
const boundCommand = route.command === undefined ? undefined : command;
|
|
2108
2149
|
const path = `${controller.path}/${route.path}`.replace(/\/+/g, "/");
|
|
2150
|
+
const routeAspects = [
|
|
2151
|
+
...aspects(`module:${module.name}`, module.aspects),
|
|
2152
|
+
...aspects("route", route.aspects),
|
|
2153
|
+
...aspects("command", boundCommand?.aspects)
|
|
2154
|
+
];
|
|
2155
|
+
const stagePaths = boundCommand === undefined ? undefined : commandStagePaths(boundCommand, routeAspects);
|
|
2156
|
+
const plannedStages = stagePaths === undefined ? { stages: [...routeAspects, "handler"] } : planStages(stagePaths, Boolean(boundCommand?.branches?.length));
|
|
2109
2157
|
return {
|
|
2110
2158
|
module: module.name,
|
|
2111
2159
|
kind: "route",
|
|
2112
2160
|
name: `${route.method} ${path.length > 1 ? path.replace(/\/+$/, "") : path}`,
|
|
2113
2161
|
...command ? { command: command.name } : {},
|
|
2114
|
-
|
|
2115
|
-
...boundCommand ? [
|
|
2116
|
-
"commandExecutor",
|
|
2117
|
-
"authorize",
|
|
2118
|
-
...boundCommand.branches?.length ? ["rpc:selected-branch"] : boundCommand.rpc ? [`rpc:${boundCommand.rpc}`] : [
|
|
2119
|
-
...boundCommand.idempotency === "required" ? ["idempotency"] : [],
|
|
2120
|
-
...boundCommand.transaction === "required" ? ["transaction"] : []
|
|
2121
|
-
]
|
|
2122
|
-
] : [],
|
|
2123
|
-
...aspects(`module:${module.name}`, module.aspects),
|
|
2124
|
-
...aspects("route", route.aspects),
|
|
2125
|
-
...aspects("command", boundCommand?.aspects),
|
|
2126
|
-
"handler",
|
|
2127
|
-
...boundCommand?.audit && !boundCommand.rpc && !boundCommand.branches?.length ? ["audit"] : []
|
|
2128
|
-
]
|
|
2162
|
+
...plannedStages
|
|
2129
2163
|
};
|
|
2130
2164
|
})),
|
|
2131
|
-
...module.commands.map((command) =>
|
|
2132
|
-
|
|
2133
|
-
kind: "command",
|
|
2134
|
-
name: command.name,
|
|
2135
|
-
command: command.name,
|
|
2136
|
-
stages: [
|
|
2137
|
-
"commandExecutor",
|
|
2138
|
-
"authorize",
|
|
2139
|
-
...command.branches?.length ? ["rpc:selected-branch"] : command.rpc ? [`rpc:${command.rpc}`] : [
|
|
2140
|
-
...command.idempotency === "required" ? ["idempotency"] : [],
|
|
2141
|
-
...command.transaction === "required" ? ["transaction"] : []
|
|
2142
|
-
],
|
|
2165
|
+
...module.commands.map((command) => {
|
|
2166
|
+
const stagePaths = commandStagePaths(command, [
|
|
2143
2167
|
...aspects(`module:${module.name}`, module.aspects),
|
|
2144
|
-
...aspects("command", command.aspects)
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2168
|
+
...aspects("command", command.aspects)
|
|
2169
|
+
]);
|
|
2170
|
+
return {
|
|
2171
|
+
module: module.name,
|
|
2172
|
+
kind: "command",
|
|
2173
|
+
name: command.name,
|
|
2174
|
+
command: command.name,
|
|
2175
|
+
...planStages(stagePaths, Boolean(command.branches?.length))
|
|
2176
|
+
};
|
|
2177
|
+
}),
|
|
2149
2178
|
...(module.jobs ?? []).map((job) => ({
|
|
2150
2179
|
module: module.name,
|
|
2151
2180
|
kind: "job",
|
|
@@ -2480,26 +2509,29 @@ function renderApplication(graph, options) {
|
|
|
2480
2509
|
}
|
|
2481
2510
|
};
|
|
2482
2511
|
}));
|
|
2483
|
-
const commands = module.commands.map((command) =>
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2512
|
+
const commands = module.commands.map((command) => {
|
|
2513
|
+
const fallbackCommand = command.branchesFallback === "command";
|
|
2514
|
+
return {
|
|
2515
|
+
name: command.name,
|
|
2516
|
+
className: command.className,
|
|
2517
|
+
executor: "commandExecutor",
|
|
2518
|
+
adapter: command.rpc ?? null,
|
|
2519
|
+
permission: command.permission ?? null,
|
|
2520
|
+
audit: command.audit ?? null,
|
|
2521
|
+
transaction: command.transaction,
|
|
2522
|
+
idempotency: command.idempotency,
|
|
2523
|
+
branches: command.branches ?? [],
|
|
2524
|
+
branchesFallback: command.branchesFallback ?? "reject",
|
|
2525
|
+
...command.tenantScope === undefined ? {} : { tenantScope: command.tenantScope },
|
|
2526
|
+
requiredCapabilities: {
|
|
2527
|
+
permission: true,
|
|
2528
|
+
audit: Boolean(command.audit) && fallbackCommand || (command.branches?.length ? command.branches.some((branch) => Boolean(branch.audit)) : Boolean(command.audit)),
|
|
2529
|
+
idempotency: command.idempotency === "required" && fallbackCommand || (command.branches?.length ? command.branches.some((branch) => branch.idempotency === "required") : command.idempotency === "required"),
|
|
2530
|
+
transaction: command.transaction === "required" && fallbackCommand || (command.branches?.length ? command.branches.some((branch) => branch.transaction === "required") : command.transaction === "required")
|
|
2531
|
+
},
|
|
2532
|
+
aspects: (command.aspects ?? []).map((aspect) => aspect.name)
|
|
2533
|
+
};
|
|
2534
|
+
}).sort((left, right) => left.name.localeCompare(right.name));
|
|
2503
2535
|
const jobs = (module.jobs ?? []).map((job) => ({
|
|
2504
2536
|
name: job.name,
|
|
2505
2537
|
className: job.className,
|
|
@@ -15823,7 +15855,8 @@ var init_execution_snapshot = __esm(() => {
|
|
|
15823
15855
|
kind,
|
|
15824
15856
|
name: text,
|
|
15825
15857
|
command: Optional(text),
|
|
15826
|
-
stages: _Array_(text)
|
|
15858
|
+
stages: _Array_(text),
|
|
15859
|
+
stagePaths: Optional(_Array_(_Array_(text)))
|
|
15827
15860
|
}, closed)),
|
|
15828
15861
|
commandAliases: _Array_(_Object_({ module: text, name: text, alias: text }, closed)),
|
|
15829
15862
|
contexts: _Array_(_Object_({
|
|
@@ -15992,7 +16025,8 @@ function buildTimeline(events, matchedPlans) {
|
|
|
15992
16025
|
attempts: [],
|
|
15993
16026
|
omittedAttempts: 0
|
|
15994
16027
|
},
|
|
15995
|
-
attempts: new Map
|
|
16028
|
+
attempts: new Map,
|
|
16029
|
+
...plan === undefined ? {} : { plan }
|
|
15996
16030
|
};
|
|
15997
16031
|
groups.set(key, group);
|
|
15998
16032
|
}
|
|
@@ -16020,13 +16054,16 @@ function buildTimeline(events, matchedPlans) {
|
|
|
16020
16054
|
const declared = group.entry.declaredStages;
|
|
16021
16055
|
const attempts = [...group.attempts.values()].sort((left, right) => Number(hasFailure(right)) - Number(hasFailure(left)) || left.attempt - right.attempt).slice(0, EXECUTION_CONTEXT_LIMITS.timelineAttempts).sort((left, right) => left.attempt - right.attempt).map((attempt) => {
|
|
16022
16056
|
const stages = [...attempt.stages].sort((left, right) => Number(right.phase === "failed") - Number(left.phase === "failed") || left.index - right.index).slice(0, EXECUTION_CONTEXT_LIMITS.timelineStages).sort((left, right) => left.index - right.index);
|
|
16023
|
-
const
|
|
16057
|
+
const paths = group.plan?.stagePaths && group.plan.stagePaths.length > 0 ? group.plan.stagePaths : [declared];
|
|
16058
|
+
const bestPath = [...paths].sort((left, right) => right.filter((stage) => attempt.observed.has(stage)).length - left.filter((stage) => attempt.observed.has(stage)).length)[0] ?? declared;
|
|
16059
|
+
const terminal = bestPath[bestPath.length - 1];
|
|
16060
|
+
const complete = terminal !== undefined && attempt.stages.some((stage) => stage.stage === terminal && stage.phase === "succeeded");
|
|
16024
16061
|
return {
|
|
16025
16062
|
attempt: attempt.attempt,
|
|
16026
16063
|
stages,
|
|
16027
16064
|
failed: hasFailure(attempt),
|
|
16028
|
-
complete
|
|
16029
|
-
missingStages:
|
|
16065
|
+
complete,
|
|
16066
|
+
missingStages: bestPath.filter((stage) => !attempt.observed.has(stage)),
|
|
16030
16067
|
unexpectedStages: [],
|
|
16031
16068
|
traceIds: [...attempt.traceIds].sort(),
|
|
16032
16069
|
omittedStages: attempt.stages.length - stages.length
|
|
@@ -18553,7 +18590,7 @@ function importPath(out, path) {
|
|
|
18553
18590
|
function parseDatabaseContractsOptions(value, directory) {
|
|
18554
18591
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
18555
18592
|
throw new TypeError("Expected database contracts configuration");
|
|
18556
|
-
const allowed = ["rootDir", "outDir", "postgrestTypes", "modelSchema", "role", "graphql", "migrations"];
|
|
18593
|
+
const allowed = ["rootDir", "outDir", "postgrestTypes", "modelSchema", "role", "graphql", "rpcRegistry", "rpcCatalog", "migrations"];
|
|
18557
18594
|
if (Object.keys(value).some((name) => !allowed.includes(name)))
|
|
18558
18595
|
throw new TypeError("Unknown database contracts option");
|
|
18559
18596
|
const field = (name) => {
|
|
@@ -18569,6 +18606,8 @@ function parseDatabaseContractsOptions(value, directory) {
|
|
|
18569
18606
|
};
|
|
18570
18607
|
const postgrestTypes = optionalPath("postgrestTypes");
|
|
18571
18608
|
const modelSchema = optionalPath("modelSchema");
|
|
18609
|
+
const rpcRegistry = optionalPath("rpcRegistry");
|
|
18610
|
+
const rpcCatalog = optionalPath("rpcCatalog");
|
|
18572
18611
|
const graphqlPresent = Object.hasOwn(value, "graphql");
|
|
18573
18612
|
if (!postgrestTypes && !modelSchema && !graphqlPresent) {
|
|
18574
18613
|
throw new TypeError("Database contracts require postgrestTypes, modelSchema, or graphql");
|
|
@@ -18590,6 +18629,8 @@ function parseDatabaseContractsOptions(value, directory) {
|
|
|
18590
18629
|
...modelSchema ? { modelSchema: resolve25(directory, modelSchema) } : {},
|
|
18591
18630
|
role: field("role"),
|
|
18592
18631
|
...graphql ? { graphql } : {},
|
|
18632
|
+
...rpcRegistry ? { rpcRegistry: resolve25(directory, rpcRegistry) } : {},
|
|
18633
|
+
...rpcCatalog ? { rpcCatalog: resolve25(directory, rpcCatalog) } : {},
|
|
18593
18634
|
migrations: migrations.map((file) => resolve25(directory, file))
|
|
18594
18635
|
};
|
|
18595
18636
|
}
|
|
@@ -18665,6 +18706,10 @@ async function generateDatabaseContracts(options, check = false) {
|
|
|
18665
18706
|
}
|
|
18666
18707
|
if (options.graphql)
|
|
18667
18708
|
await addInput(resolve25(rootDir, options.graphql.schema));
|
|
18709
|
+
if (options.rpcRegistry)
|
|
18710
|
+
await addInput(options.rpcRegistry);
|
|
18711
|
+
if (options.rpcCatalog)
|
|
18712
|
+
await addInput(options.rpcCatalog);
|
|
18668
18713
|
if (new Set(options.migrations.map((path) => resolve25(rootDir, path))).size !== options.migrations.length) {
|
|
18669
18714
|
throw new Error("Duplicate migration in database contracts configuration");
|
|
18670
18715
|
}
|
|
@@ -23516,11 +23561,13 @@ function assertDatabaseGovernance(value) {
|
|
|
23516
23561
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
23517
23562
|
throw new Error("Invalid databaseGovernance configuration.");
|
|
23518
23563
|
const entry = value;
|
|
23519
|
-
|
|
23564
|
+
const rpcBoundary = entry["rpcBoundary"];
|
|
23565
|
+
const validRpcBoundary = rpcBoundary === undefined || rpcBoundary !== null && typeof rpcBoundary === "object" && !Array.isArray(rpcBoundary) && Object.keys(rpcBoundary).every((key) => ["registry", "catalog", "legacy"].includes(key)) && Object.values(rpcBoundary).length > 0 && Object.values(rpcBoundary).every((path) => typeof path === "string" && path.trim() !== "" && !path.includes("\\") && !path.startsWith("/"));
|
|
23566
|
+
if (entry["mode"] !== "off" && entry["mode"] !== "check" && entry["mode"] !== "enforce" || Object.keys(entry).some((key) => !["mode", "projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv", "schemas", "rpcBoundary"].includes(key)) || ["projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv"].some((key) => entry[key] !== undefined && (typeof entry[key] !== "string" || !entry[key].trim())) || entry["databaseUrlEnv"] !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(String(entry["databaseUrlEnv"])) || entry["schemas"] !== undefined && (!Array.isArray(entry["schemas"]) || !entry["schemas"].length || !entry["schemas"].every((schema) => typeof schema === "string" && schema.trim())) || !validRpcBoundary) {
|
|
23520
23567
|
throw new Error("Invalid databaseGovernance mode, paths, connection or schemas.");
|
|
23521
23568
|
}
|
|
23522
23569
|
}
|
|
23523
|
-
async function checkDatabaseGovernance(rootDir, value) {
|
|
23570
|
+
async function checkDatabaseGovernance(rootDir, value, rpcCapabilities = []) {
|
|
23524
23571
|
if (value === undefined)
|
|
23525
23572
|
return [];
|
|
23526
23573
|
const failure = () => [{
|
|
@@ -23537,7 +23584,7 @@ async function checkDatabaseGovernance(rootDir, value) {
|
|
|
23537
23584
|
const output = await new Promise((accept, reject) => {
|
|
23538
23585
|
const child = execFile(process.versions.bun ? process.execPath : "bun", ["--no-env-file", worker], { timeout: 30000, killSignal: "SIGKILL", maxBuffer: 1024 * 1024 }, (error, stdout) => error ? reject(error) : accept(stdout));
|
|
23539
23586
|
child.stdin?.on("error", reject);
|
|
23540
|
-
child.stdin?.end(JSON.stringify(options));
|
|
23587
|
+
child.stdin?.end(JSON.stringify({ options, rpcCapabilities }));
|
|
23541
23588
|
});
|
|
23542
23589
|
const report = JSON.parse(output);
|
|
23543
23590
|
if (!report || typeof report !== "object" || !("diagnostics" in report) || !Array.isArray(report.diagnostics))
|
|
@@ -23583,7 +23630,7 @@ async function compileProject(options) {
|
|
|
23583
23630
|
...validateEffectPolicies(graph, options.effect),
|
|
23584
23631
|
...validateJobPolicies(graph)
|
|
23585
23632
|
];
|
|
23586
|
-
const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance);
|
|
23633
|
+
const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance, Object.keys(options.commandCapabilities?.rpc ?? {}));
|
|
23587
23634
|
diagnostics.push(...databaseDiagnostics);
|
|
23588
23635
|
if (databaseDiagnostics.length)
|
|
23589
23636
|
return { diagnostics, graph, written: [] };
|
|
@@ -23653,7 +23700,7 @@ async function checkProject(options) {
|
|
|
23653
23700
|
...validateEffectPolicies(graph, options.effect),
|
|
23654
23701
|
...validateJobPolicies(graph)
|
|
23655
23702
|
];
|
|
23656
|
-
const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance);
|
|
23703
|
+
const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance, Object.keys(options.commandCapabilities?.rpc ?? {}));
|
|
23657
23704
|
diagnostics.push(...databaseDiagnostics);
|
|
23658
23705
|
if (databaseDiagnostics.length) {
|
|
23659
23706
|
return { diagnostics, graph, upToDate: false, mismatches: ["Database governance verification failed."] };
|
|
@@ -26513,7 +26560,7 @@ import { resolve as resolve19 } from "node:path";
|
|
|
26513
26560
|
var MIGRATION_APP_VERSION = "0.39.1";
|
|
26514
26561
|
|
|
26515
26562
|
// src/migration-elysia-version.ts
|
|
26516
|
-
var MIGRATION_ELYSIA_VERSION = "0.42.
|
|
26563
|
+
var MIGRATION_ELYSIA_VERSION = "0.42.1";
|
|
26517
26564
|
|
|
26518
26565
|
// src/migration-policy.ts
|
|
26519
26566
|
var cachedCompilerVersion;
|
|
@@ -11,6 +11,10 @@ export interface DatabaseContractsOptions {
|
|
|
11
11
|
role: string;
|
|
12
12
|
/** Role-scoped pg_graphql snapshot exported by graphql-schema. Omit when GraphQL is unused. */
|
|
13
13
|
graphql?: GraphqlOptions;
|
|
14
|
+
/** Registry emitted by application or database code generation. Tracked for contract freshness. */
|
|
15
|
+
rpcRegistry?: string;
|
|
16
|
+
/** Generated or exported RPC catalog. Tracked for contract freshness. */
|
|
17
|
+
rpcCatalog?: string;
|
|
14
18
|
/** Ordered complete migration history. */
|
|
15
19
|
migrations: readonly string[];
|
|
16
20
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { DatabaseGovernanceOptions, Diagnostic } from "./types";
|
|
2
|
-
export declare function enforceDatabaseGovernance(options: DatabaseGovernanceOptions): Promise<{
|
|
2
|
+
export declare function enforceDatabaseGovernance(options: DatabaseGovernanceOptions, rpcCapabilities?: readonly string[]): Promise<{
|
|
3
3
|
diagnostics: Diagnostic[];
|
|
4
4
|
}>;
|
|
@@ -3,6 +3,7 @@ import { readFile, realpath } from "node:fs/promises";
|
|
|
3
3
|
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import {
|
|
6
|
+
checkRpcBoundary,
|
|
6
7
|
lintModule,
|
|
7
8
|
readCatalog,
|
|
8
9
|
reconcileModule
|
|
@@ -34,13 +35,31 @@ function governanceDiagnostic(message, code = "database-governance") {
|
|
|
34
35
|
suggestion: "Fix the database source contract, module manifest or live catalog before compiling."
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
|
-
async function enforceDatabaseGovernance(options) {
|
|
38
|
+
async function enforceDatabaseGovernance(options, rpcCapabilities = []) {
|
|
38
39
|
const projectRoot = await realpath(options.projectRoot ?? ".");
|
|
39
40
|
const sourceReport = await databaseSources(projectRoot, "check");
|
|
40
41
|
const diagnostics = sourceReport.findings.map((finding) => governanceDiagnostic(`${finding.file}: ${finding.message}`, "database-source-contract"));
|
|
41
42
|
if (!sourceReport.ok && diagnostics.length === 0) {
|
|
42
43
|
diagnostics.push(governanceDiagnostic("Database source contract check failed without findings."));
|
|
43
44
|
}
|
|
45
|
+
if (diagnostics.length > 0)
|
|
46
|
+
return { diagnostics };
|
|
47
|
+
if (options.rpcBoundary) {
|
|
48
|
+
try {
|
|
49
|
+
const readJson = async (path) => path === undefined ? undefined : JSON.parse(await readFile(await withinRoot(projectRoot, path), "utf8"));
|
|
50
|
+
const report = checkRpcBoundary({
|
|
51
|
+
capabilities: rpcCapabilities,
|
|
52
|
+
registry: await readJson(options.rpcBoundary.registry),
|
|
53
|
+
catalog: await readJson(options.rpcBoundary.catalog),
|
|
54
|
+
legacy: await readJson(options.rpcBoundary.legacy)
|
|
55
|
+
});
|
|
56
|
+
if (!report.ok) {
|
|
57
|
+
diagnostics.push(governanceDiagnostic(`RPC capabilities have no registry/catalog source or historical entry: ${report.missing.join(", ")}`, "database-rpc-boundary"));
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
diagnostics.push(governanceDiagnostic("RPC boundary artifacts could not be read or parsed.", "database-rpc-boundary"));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
44
63
|
if (diagnostics.length > 0)
|
|
45
64
|
return { diagnostics };
|
|
46
65
|
if (options.mode === "check")
|
|
@@ -107,11 +126,13 @@ function assertDatabaseGovernance(value) {
|
|
|
107
126
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
108
127
|
throw new Error("Invalid databaseGovernance configuration.");
|
|
109
128
|
const entry = value;
|
|
110
|
-
|
|
129
|
+
const rpcBoundary = entry["rpcBoundary"];
|
|
130
|
+
const validRpcBoundary = rpcBoundary === undefined || rpcBoundary !== null && typeof rpcBoundary === "object" && !Array.isArray(rpcBoundary) && Object.keys(rpcBoundary).every((key) => ["registry", "catalog", "legacy"].includes(key)) && Object.values(rpcBoundary).length > 0 && Object.values(rpcBoundary).every((path) => typeof path === "string" && path.trim() !== "" && !path.includes("\\") && !path.startsWith("/"));
|
|
131
|
+
if (entry["mode"] !== "off" && entry["mode"] !== "check" && entry["mode"] !== "enforce" || Object.keys(entry).some((key) => !["mode", "projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv", "schemas", "rpcBoundary"].includes(key)) || ["projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv"].some((key) => entry[key] !== undefined && (typeof entry[key] !== "string" || !entry[key].trim())) || entry["databaseUrlEnv"] !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(String(entry["databaseUrlEnv"])) || entry["schemas"] !== undefined && (!Array.isArray(entry["schemas"]) || !entry["schemas"].length || !entry["schemas"].every((schema) => typeof schema === "string" && schema.trim())) || !validRpcBoundary) {
|
|
111
132
|
throw new Error("Invalid databaseGovernance mode, paths, connection or schemas.");
|
|
112
133
|
}
|
|
113
134
|
}
|
|
114
|
-
async function checkDatabaseGovernance(rootDir, value) {
|
|
135
|
+
async function checkDatabaseGovernance(rootDir, value, rpcCapabilities = []) {
|
|
115
136
|
if (value === undefined)
|
|
116
137
|
return [];
|
|
117
138
|
const failure = () => [{
|
|
@@ -128,7 +149,7 @@ async function checkDatabaseGovernance(rootDir, value) {
|
|
|
128
149
|
const output = await new Promise((accept, reject) => {
|
|
129
150
|
const child = execFile(process.versions.bun ? process.execPath : "bun", ["--no-env-file", worker], { timeout: 30000, killSignal: "SIGKILL", maxBuffer: 1024 * 1024 }, (error, stdout) => error ? reject(error) : accept(stdout));
|
|
130
151
|
child.stdin?.on("error", reject);
|
|
131
|
-
child.stdin?.end(JSON.stringify(options));
|
|
152
|
+
child.stdin?.end(JSON.stringify({ options, rpcCapabilities }));
|
|
132
153
|
});
|
|
133
154
|
const report = JSON.parse(output);
|
|
134
155
|
if (!report || typeof report !== "object" || !("diagnostics" in report) || !Array.isArray(report.diagnostics))
|
|
@@ -147,9 +168,11 @@ async function checkDatabaseGovernance(rootDir, value) {
|
|
|
147
168
|
|
|
148
169
|
// src/database-governance-worker.ts
|
|
149
170
|
try {
|
|
150
|
-
const
|
|
171
|
+
const payload = JSON.parse(await Bun.stdin.text());
|
|
172
|
+
const options = payload && typeof payload === "object" && !Array.isArray(payload) && "options" in payload ? payload.options : payload;
|
|
173
|
+
const rpcCapabilities = payload && typeof payload === "object" && !Array.isArray(payload) && "rpcCapabilities" in payload && Array.isArray(payload.rpcCapabilities) ? payload.rpcCapabilities : [];
|
|
151
174
|
assertDatabaseGovernance(options);
|
|
152
|
-
process.stdout.write(JSON.stringify(await enforceDatabaseGovernance(options)));
|
|
175
|
+
process.stdout.write(JSON.stringify(await enforceDatabaseGovernance(options, rpcCapabilities)));
|
|
153
176
|
} catch {
|
|
154
177
|
process.stdout.write(JSON.stringify({ diagnostics: [{
|
|
155
178
|
severity: "error",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { DatabaseGovernanceOptions, Diagnostic } from "./types";
|
|
2
2
|
export declare function assertDatabaseGovernance(value: unknown): asserts value is DatabaseGovernanceOptions;
|
|
3
3
|
/** A fresh, bounded process avoids stale module imports during incremental compilation. */
|
|
4
|
-
export declare function checkDatabaseGovernance(rootDir: string, value: DatabaseGovernanceOptions | undefined): Promise<Diagnostic[]>;
|
|
4
|
+
export declare function checkDatabaseGovernance(rootDir: string, value: DatabaseGovernanceOptions | undefined, rpcCapabilities?: readonly string[]): Promise<Diagnostic[]>;
|
|
@@ -8,6 +8,7 @@ declare const SnapshotSchema: Type.TObject<{
|
|
|
8
8
|
name: Type.TString;
|
|
9
9
|
command: Type.TOptional<Type.TString>;
|
|
10
10
|
stages: Type.TArray<Type.TString>;
|
|
11
|
+
stagePaths: Type.TOptional<Type.TArray<Type.TArray<Type.TString>>>;
|
|
11
12
|
}>>;
|
|
12
13
|
commandAliases: Type.TArray<Type.TObject<{
|
|
13
14
|
module: Type.TString;
|
package/dist/index.js
CHANGED
|
@@ -2096,6 +2096,47 @@ function allAspects(module) {
|
|
|
2096
2096
|
...(module.jobs ?? []).flatMap((job) => job.aspects ?? [])
|
|
2097
2097
|
];
|
|
2098
2098
|
}
|
|
2099
|
+
function uniqueStages(paths) {
|
|
2100
|
+
return [...new Set(paths.flat())];
|
|
2101
|
+
}
|
|
2102
|
+
function commandStagePaths(command, aspects) {
|
|
2103
|
+
const prefix = ["commandExecutor", "authorize"];
|
|
2104
|
+
if (command.branches?.length) {
|
|
2105
|
+
const branchPaths = command.branches.map((branch) => [
|
|
2106
|
+
...prefix,
|
|
2107
|
+
`rpc:${branch.rpc}`,
|
|
2108
|
+
...aspects,
|
|
2109
|
+
"handler"
|
|
2110
|
+
]);
|
|
2111
|
+
if (command.branchesFallback === "command") {
|
|
2112
|
+
branchPaths.push([
|
|
2113
|
+
...prefix,
|
|
2114
|
+
...command.idempotency === "required" ? ["idempotency"] : [],
|
|
2115
|
+
...command.transaction === "required" ? ["transaction"] : [],
|
|
2116
|
+
...aspects,
|
|
2117
|
+
"handler",
|
|
2118
|
+
...command.audit ? ["audit"] : []
|
|
2119
|
+
]);
|
|
2120
|
+
}
|
|
2121
|
+
return branchPaths;
|
|
2122
|
+
}
|
|
2123
|
+
return [[
|
|
2124
|
+
...prefix,
|
|
2125
|
+
...command.rpc ? [`rpc:${command.rpc}`] : [
|
|
2126
|
+
...command.idempotency === "required" ? ["idempotency"] : [],
|
|
2127
|
+
...command.transaction === "required" ? ["transaction"] : []
|
|
2128
|
+
],
|
|
2129
|
+
...aspects,
|
|
2130
|
+
"handler",
|
|
2131
|
+
...command.audit && !command.rpc ? ["audit"] : []
|
|
2132
|
+
]];
|
|
2133
|
+
}
|
|
2134
|
+
function planStages(paths, branches) {
|
|
2135
|
+
const stages = uniqueStages(paths);
|
|
2136
|
+
if (branches && !stages.includes("rpc:selected-branch"))
|
|
2137
|
+
stages.splice(2, 0, "rpc:selected-branch");
|
|
2138
|
+
return paths.length > 1 ? { stages, stagePaths: paths } : { stages };
|
|
2139
|
+
}
|
|
2099
2140
|
function createExecutionPlans(graph) {
|
|
2100
2141
|
const aspects = (boundary, refs = []) => refs.map((ref, index) => `${boundary}.aspect[${index}]:${ref.name}`);
|
|
2101
2142
|
return graph.modules.flatMap((module) => [
|
|
@@ -2105,46 +2146,34 @@ function createExecutionPlans(graph) {
|
|
|
2105
2146
|
const command = module.commands.find((item) => item.className === commandRef || item.name === commandRef);
|
|
2106
2147
|
const boundCommand = route.command === undefined ? undefined : command;
|
|
2107
2148
|
const path = `${controller.path}/${route.path}`.replace(/\/+/g, "/");
|
|
2149
|
+
const routeAspects = [
|
|
2150
|
+
...aspects(`module:${module.name}`, module.aspects),
|
|
2151
|
+
...aspects("route", route.aspects),
|
|
2152
|
+
...aspects("command", boundCommand?.aspects)
|
|
2153
|
+
];
|
|
2154
|
+
const stagePaths = boundCommand === undefined ? undefined : commandStagePaths(boundCommand, routeAspects);
|
|
2155
|
+
const plannedStages = stagePaths === undefined ? { stages: [...routeAspects, "handler"] } : planStages(stagePaths, Boolean(boundCommand?.branches?.length));
|
|
2108
2156
|
return {
|
|
2109
2157
|
module: module.name,
|
|
2110
2158
|
kind: "route",
|
|
2111
2159
|
name: `${route.method} ${path.length > 1 ? path.replace(/\/+$/, "") : path}`,
|
|
2112
2160
|
...command ? { command: command.name } : {},
|
|
2113
|
-
|
|
2114
|
-
...boundCommand ? [
|
|
2115
|
-
"commandExecutor",
|
|
2116
|
-
"authorize",
|
|
2117
|
-
...boundCommand.branches?.length ? ["rpc:selected-branch"] : boundCommand.rpc ? [`rpc:${boundCommand.rpc}`] : [
|
|
2118
|
-
...boundCommand.idempotency === "required" ? ["idempotency"] : [],
|
|
2119
|
-
...boundCommand.transaction === "required" ? ["transaction"] : []
|
|
2120
|
-
]
|
|
2121
|
-
] : [],
|
|
2122
|
-
...aspects(`module:${module.name}`, module.aspects),
|
|
2123
|
-
...aspects("route", route.aspects),
|
|
2124
|
-
...aspects("command", boundCommand?.aspects),
|
|
2125
|
-
"handler",
|
|
2126
|
-
...boundCommand?.audit && !boundCommand.rpc && !boundCommand.branches?.length ? ["audit"] : []
|
|
2127
|
-
]
|
|
2161
|
+
...plannedStages
|
|
2128
2162
|
};
|
|
2129
2163
|
})),
|
|
2130
|
-
...module.commands.map((command) =>
|
|
2131
|
-
|
|
2132
|
-
kind: "command",
|
|
2133
|
-
name: command.name,
|
|
2134
|
-
command: command.name,
|
|
2135
|
-
stages: [
|
|
2136
|
-
"commandExecutor",
|
|
2137
|
-
"authorize",
|
|
2138
|
-
...command.branches?.length ? ["rpc:selected-branch"] : command.rpc ? [`rpc:${command.rpc}`] : [
|
|
2139
|
-
...command.idempotency === "required" ? ["idempotency"] : [],
|
|
2140
|
-
...command.transaction === "required" ? ["transaction"] : []
|
|
2141
|
-
],
|
|
2164
|
+
...module.commands.map((command) => {
|
|
2165
|
+
const stagePaths = commandStagePaths(command, [
|
|
2142
2166
|
...aspects(`module:${module.name}`, module.aspects),
|
|
2143
|
-
...aspects("command", command.aspects)
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2167
|
+
...aspects("command", command.aspects)
|
|
2168
|
+
]);
|
|
2169
|
+
return {
|
|
2170
|
+
module: module.name,
|
|
2171
|
+
kind: "command",
|
|
2172
|
+
name: command.name,
|
|
2173
|
+
command: command.name,
|
|
2174
|
+
...planStages(stagePaths, Boolean(command.branches?.length))
|
|
2175
|
+
};
|
|
2176
|
+
}),
|
|
2148
2177
|
...(module.jobs ?? []).map((job) => ({
|
|
2149
2178
|
module: module.name,
|
|
2150
2179
|
kind: "job",
|
|
@@ -2479,26 +2508,29 @@ function renderApplication(graph, options) {
|
|
|
2479
2508
|
}
|
|
2480
2509
|
};
|
|
2481
2510
|
}));
|
|
2482
|
-
const commands = module.commands.map((command) =>
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2511
|
+
const commands = module.commands.map((command) => {
|
|
2512
|
+
const fallbackCommand = command.branchesFallback === "command";
|
|
2513
|
+
return {
|
|
2514
|
+
name: command.name,
|
|
2515
|
+
className: command.className,
|
|
2516
|
+
executor: "commandExecutor",
|
|
2517
|
+
adapter: command.rpc ?? null,
|
|
2518
|
+
permission: command.permission ?? null,
|
|
2519
|
+
audit: command.audit ?? null,
|
|
2520
|
+
transaction: command.transaction,
|
|
2521
|
+
idempotency: command.idempotency,
|
|
2522
|
+
branches: command.branches ?? [],
|
|
2523
|
+
branchesFallback: command.branchesFallback ?? "reject",
|
|
2524
|
+
...command.tenantScope === undefined ? {} : { tenantScope: command.tenantScope },
|
|
2525
|
+
requiredCapabilities: {
|
|
2526
|
+
permission: true,
|
|
2527
|
+
audit: Boolean(command.audit) && fallbackCommand || (command.branches?.length ? command.branches.some((branch) => Boolean(branch.audit)) : Boolean(command.audit)),
|
|
2528
|
+
idempotency: command.idempotency === "required" && fallbackCommand || (command.branches?.length ? command.branches.some((branch) => branch.idempotency === "required") : command.idempotency === "required"),
|
|
2529
|
+
transaction: command.transaction === "required" && fallbackCommand || (command.branches?.length ? command.branches.some((branch) => branch.transaction === "required") : command.transaction === "required")
|
|
2530
|
+
},
|
|
2531
|
+
aspects: (command.aspects ?? []).map((aspect) => aspect.name)
|
|
2532
|
+
};
|
|
2533
|
+
}).sort((left, right) => left.name.localeCompare(right.name));
|
|
2502
2534
|
const jobs = (module.jobs ?? []).map((job) => ({
|
|
2503
2535
|
name: job.name,
|
|
2504
2536
|
className: job.className,
|
|
@@ -16410,7 +16442,8 @@ var init_execution_snapshot = __esm(() => {
|
|
|
16410
16442
|
kind: kind2,
|
|
16411
16443
|
name: text,
|
|
16412
16444
|
command: Optional(text),
|
|
16413
|
-
stages: _Array_(text)
|
|
16445
|
+
stages: _Array_(text),
|
|
16446
|
+
stagePaths: Optional(_Array_(_Array_(text)))
|
|
16414
16447
|
}, closed3)),
|
|
16415
16448
|
commandAliases: _Array_(_Object_({ module: text, name: text, alias: text }, closed3)),
|
|
16416
16449
|
contexts: _Array_(_Object_({
|
|
@@ -18129,7 +18162,8 @@ function buildTimeline(events, matchedPlans) {
|
|
|
18129
18162
|
attempts: [],
|
|
18130
18163
|
omittedAttempts: 0
|
|
18131
18164
|
},
|
|
18132
|
-
attempts: new Map
|
|
18165
|
+
attempts: new Map,
|
|
18166
|
+
...plan === undefined ? {} : { plan }
|
|
18133
18167
|
};
|
|
18134
18168
|
groups.set(key, group);
|
|
18135
18169
|
}
|
|
@@ -18157,13 +18191,16 @@ function buildTimeline(events, matchedPlans) {
|
|
|
18157
18191
|
const declared = group.entry.declaredStages;
|
|
18158
18192
|
const attempts = [...group.attempts.values()].sort((left, right) => Number(hasFailure(right)) - Number(hasFailure(left)) || left.attempt - right.attempt).slice(0, EXECUTION_CONTEXT_LIMITS.timelineAttempts).sort((left, right) => left.attempt - right.attempt).map((attempt) => {
|
|
18159
18193
|
const stages = [...attempt.stages].sort((left, right) => Number(right.phase === "failed") - Number(left.phase === "failed") || left.index - right.index).slice(0, EXECUTION_CONTEXT_LIMITS.timelineStages).sort((left, right) => left.index - right.index);
|
|
18160
|
-
const
|
|
18194
|
+
const paths = group.plan?.stagePaths && group.plan.stagePaths.length > 0 ? group.plan.stagePaths : [declared];
|
|
18195
|
+
const bestPath = [...paths].sort((left, right) => right.filter((stage) => attempt.observed.has(stage)).length - left.filter((stage) => attempt.observed.has(stage)).length)[0] ?? declared;
|
|
18196
|
+
const terminal = bestPath[bestPath.length - 1];
|
|
18197
|
+
const complete = terminal !== undefined && attempt.stages.some((stage) => stage.stage === terminal && stage.phase === "succeeded");
|
|
18161
18198
|
return {
|
|
18162
18199
|
attempt: attempt.attempt,
|
|
18163
18200
|
stages,
|
|
18164
18201
|
failed: hasFailure(attempt),
|
|
18165
|
-
complete
|
|
18166
|
-
missingStages:
|
|
18202
|
+
complete,
|
|
18203
|
+
missingStages: bestPath.filter((stage) => !attempt.observed.has(stage)),
|
|
18167
18204
|
unexpectedStages: [],
|
|
18168
18205
|
traceIds: [...attempt.traceIds].sort(),
|
|
18169
18206
|
omittedStages: attempt.stages.length - stages.length
|
|
@@ -18585,7 +18622,7 @@ function importPath(out, path) {
|
|
|
18585
18622
|
function parseDatabaseContractsOptions(value, directory) {
|
|
18586
18623
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
18587
18624
|
throw new TypeError("Expected database contracts configuration");
|
|
18588
|
-
const allowed = ["rootDir", "outDir", "postgrestTypes", "modelSchema", "role", "graphql", "migrations"];
|
|
18625
|
+
const allowed = ["rootDir", "outDir", "postgrestTypes", "modelSchema", "role", "graphql", "rpcRegistry", "rpcCatalog", "migrations"];
|
|
18589
18626
|
if (Object.keys(value).some((name) => !allowed.includes(name)))
|
|
18590
18627
|
throw new TypeError("Unknown database contracts option");
|
|
18591
18628
|
const field = (name) => {
|
|
@@ -18601,6 +18638,8 @@ function parseDatabaseContractsOptions(value, directory) {
|
|
|
18601
18638
|
};
|
|
18602
18639
|
const postgrestTypes = optionalPath("postgrestTypes");
|
|
18603
18640
|
const modelSchema = optionalPath("modelSchema");
|
|
18641
|
+
const rpcRegistry = optionalPath("rpcRegistry");
|
|
18642
|
+
const rpcCatalog = optionalPath("rpcCatalog");
|
|
18604
18643
|
const graphqlPresent = Object.hasOwn(value, "graphql");
|
|
18605
18644
|
if (!postgrestTypes && !modelSchema && !graphqlPresent) {
|
|
18606
18645
|
throw new TypeError("Database contracts require postgrestTypes, modelSchema, or graphql");
|
|
@@ -18622,6 +18661,8 @@ function parseDatabaseContractsOptions(value, directory) {
|
|
|
18622
18661
|
...modelSchema ? { modelSchema: resolve24(directory, modelSchema) } : {},
|
|
18623
18662
|
role: field("role"),
|
|
18624
18663
|
...graphql ? { graphql } : {},
|
|
18664
|
+
...rpcRegistry ? { rpcRegistry: resolve24(directory, rpcRegistry) } : {},
|
|
18665
|
+
...rpcCatalog ? { rpcCatalog: resolve24(directory, rpcCatalog) } : {},
|
|
18625
18666
|
migrations: migrations.map((file) => resolve24(directory, file))
|
|
18626
18667
|
};
|
|
18627
18668
|
}
|
|
@@ -18697,6 +18738,10 @@ async function generateDatabaseContracts(options, check = false) {
|
|
|
18697
18738
|
}
|
|
18698
18739
|
if (options.graphql)
|
|
18699
18740
|
await addInput(resolve24(rootDir, options.graphql.schema));
|
|
18741
|
+
if (options.rpcRegistry)
|
|
18742
|
+
await addInput(options.rpcRegistry);
|
|
18743
|
+
if (options.rpcCatalog)
|
|
18744
|
+
await addInput(options.rpcCatalog);
|
|
18700
18745
|
if (new Set(options.migrations.map((path) => resolve24(rootDir, path))).size !== options.migrations.length) {
|
|
18701
18746
|
throw new Error("Duplicate migration in database contracts configuration");
|
|
18702
18747
|
}
|
|
@@ -23477,11 +23522,13 @@ function assertDatabaseGovernance(value) {
|
|
|
23477
23522
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
23478
23523
|
throw new Error("Invalid databaseGovernance configuration.");
|
|
23479
23524
|
const entry = value;
|
|
23480
|
-
|
|
23525
|
+
const rpcBoundary = entry["rpcBoundary"];
|
|
23526
|
+
const validRpcBoundary = rpcBoundary === undefined || rpcBoundary !== null && typeof rpcBoundary === "object" && !Array.isArray(rpcBoundary) && Object.keys(rpcBoundary).every((key) => ["registry", "catalog", "legacy"].includes(key)) && Object.values(rpcBoundary).length > 0 && Object.values(rpcBoundary).every((path) => typeof path === "string" && path.trim() !== "" && !path.includes("\\") && !path.startsWith("/"));
|
|
23527
|
+
if (entry["mode"] !== "off" && entry["mode"] !== "check" && entry["mode"] !== "enforce" || Object.keys(entry).some((key) => !["mode", "projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv", "schemas", "rpcBoundary"].includes(key)) || ["projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv"].some((key) => entry[key] !== undefined && (typeof entry[key] !== "string" || !entry[key].trim())) || entry["databaseUrlEnv"] !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(String(entry["databaseUrlEnv"])) || entry["schemas"] !== undefined && (!Array.isArray(entry["schemas"]) || !entry["schemas"].length || !entry["schemas"].every((schema) => typeof schema === "string" && schema.trim())) || !validRpcBoundary) {
|
|
23481
23528
|
throw new Error("Invalid databaseGovernance mode, paths, connection or schemas.");
|
|
23482
23529
|
}
|
|
23483
23530
|
}
|
|
23484
|
-
async function checkDatabaseGovernance(rootDir, value) {
|
|
23531
|
+
async function checkDatabaseGovernance(rootDir, value, rpcCapabilities = []) {
|
|
23485
23532
|
if (value === undefined)
|
|
23486
23533
|
return [];
|
|
23487
23534
|
const failure = () => [{
|
|
@@ -23498,7 +23545,7 @@ async function checkDatabaseGovernance(rootDir, value) {
|
|
|
23498
23545
|
const output = await new Promise((accept, reject) => {
|
|
23499
23546
|
const child = execFile(process.versions.bun ? process.execPath : "bun", ["--no-env-file", worker], { timeout: 30000, killSignal: "SIGKILL", maxBuffer: 1024 * 1024 }, (error, stdout) => error ? reject(error) : accept(stdout));
|
|
23500
23547
|
child.stdin?.on("error", reject);
|
|
23501
|
-
child.stdin?.end(JSON.stringify(options));
|
|
23548
|
+
child.stdin?.end(JSON.stringify({ options, rpcCapabilities }));
|
|
23502
23549
|
});
|
|
23503
23550
|
const report = JSON.parse(output);
|
|
23504
23551
|
if (!report || typeof report !== "object" || !("diagnostics" in report) || !Array.isArray(report.diagnostics))
|
|
@@ -23544,7 +23591,7 @@ async function compileProject(options) {
|
|
|
23544
23591
|
...validateEffectPolicies(graph, options.effect),
|
|
23545
23592
|
...validateJobPolicies(graph)
|
|
23546
23593
|
];
|
|
23547
|
-
const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance);
|
|
23594
|
+
const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance, Object.keys(options.commandCapabilities?.rpc ?? {}));
|
|
23548
23595
|
diagnostics.push(...databaseDiagnostics);
|
|
23549
23596
|
if (databaseDiagnostics.length)
|
|
23550
23597
|
return { diagnostics, graph, written: [] };
|
|
@@ -23614,7 +23661,7 @@ async function checkProject(options) {
|
|
|
23614
23661
|
...validateEffectPolicies(graph, options.effect),
|
|
23615
23662
|
...validateJobPolicies(graph)
|
|
23616
23663
|
];
|
|
23617
|
-
const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance);
|
|
23664
|
+
const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance, Object.keys(options.commandCapabilities?.rpc ?? {}));
|
|
23618
23665
|
diagnostics.push(...databaseDiagnostics);
|
|
23619
23666
|
if (databaseDiagnostics.length) {
|
|
23620
23667
|
return { diagnostics, graph, upToDate: false, mismatches: ["Database governance verification failed."] };
|
|
@@ -25765,7 +25812,7 @@ import { resolve as resolve17 } from "node:path";
|
|
|
25765
25812
|
var MIGRATION_APP_VERSION = "0.39.1";
|
|
25766
25813
|
|
|
25767
25814
|
// src/migration-elysia-version.ts
|
|
25768
|
-
var MIGRATION_ELYSIA_VERSION = "0.42.
|
|
25815
|
+
var MIGRATION_ELYSIA_VERSION = "0.42.1";
|
|
25769
25816
|
|
|
25770
25817
|
// src/migration-policy.ts
|
|
25771
25818
|
var cachedCompilerVersion;
|
package/dist/inspect.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export interface ExecutionPlan {
|
|
|
7
7
|
command?: string;
|
|
8
8
|
/** Standard governance contract. Audit success follows the handler; custom executors may short-circuit. */
|
|
9
9
|
stages: string[];
|
|
10
|
+
/** Alternative stage sequences for commands with conditional execution paths. */
|
|
11
|
+
stagePaths?: string[][];
|
|
10
12
|
}
|
|
11
13
|
export interface ContextPack {
|
|
12
14
|
version: 1;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MIGRATION_ELYSIA_VERSION = "0.42.
|
|
1
|
+
export declare const MIGRATION_ELYSIA_VERSION = "0.42.1";
|
package/dist/types.d.ts
CHANGED
|
@@ -775,6 +775,15 @@ export interface DatabaseGovernanceOptions {
|
|
|
775
775
|
databaseUrlEnv?: string;
|
|
776
776
|
/** Schemas included in the live Catalog query. */
|
|
777
777
|
schemas?: string[];
|
|
778
|
+
/** Optional offline gate connecting named RPC capabilities to maintained artifacts. */
|
|
779
|
+
rpcBoundary?: {
|
|
780
|
+
/** JSON registry produced by application or database code generation. */
|
|
781
|
+
registry?: string;
|
|
782
|
+
/** Generated or exported RPC catalog JSON. */
|
|
783
|
+
catalog?: string;
|
|
784
|
+
/** JSON list/object containing explicitly historical RPC capability names. */
|
|
785
|
+
legacy?: string;
|
|
786
|
+
};
|
|
778
787
|
}
|
|
779
788
|
export interface ModuleBoundaryRule {
|
|
780
789
|
/** Source module tag pattern or tag (for example, 'type:ui', 'scope:case', or '*'). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.60.0",
|
|
4
4
|
"description": "Static compiler for @supacloud/app metadata: builds the application graph from AST, validates it, and generates reflection-free factory code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"directory": "packages/compiler"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@supacloud/db": "0.
|
|
60
|
+
"@supacloud/db": "0.24.0",
|
|
61
61
|
"@supacloud/delivery": "0.26.0",
|
|
62
62
|
"typebox": "1.4.3",
|
|
63
63
|
"@graphql-codegen/core": "^6.2.2",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"typescript": "7.1.0-beta"
|
|
69
69
|
},
|
|
70
70
|
"overrides": {
|
|
71
|
-
"@supacloud/contracts": "0.16.
|
|
71
|
+
"@supacloud/contracts": "0.16.1",
|
|
72
72
|
"@graphql-tools/utils": "^12.0.4",
|
|
73
73
|
"@whatwg-node/promise-helpers": "2.0.0",
|
|
74
74
|
"deepmerge-ts": "8.0.2",
|