@supacloud/compiler 0.58.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 +178 -71
- package/dist/contract-manifest.d.ts +1 -0
- 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/delivery-bundle-protocol.d.ts +1 -0
- package/dist/delivery-bundle-worker.js +29 -2
- package/dist/execution-snapshot.d.ts +1 -0
- package/dist/index.js +178 -71
- package/dist/inspect.d.ts +2 -0
- package/dist/migration-app-version.d.ts +1 -1
- package/dist/migration-elysia-version.d.ts +1 -1
- package/dist/types.d.ts +11 -0
- package/package.json +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[]>;
|
|
@@ -19,6 +19,7 @@ declare const requestSchema: Type.TObject<{
|
|
|
19
19
|
definitionVersion: Type.TString;
|
|
20
20
|
replicas: Type.TInteger;
|
|
21
21
|
maxReplicas: Type.TInteger;
|
|
22
|
+
shardCount: Type.TOptional<Type.TInteger>;
|
|
22
23
|
concurrencyPerReplica: Type.TInteger;
|
|
23
24
|
resources: Type.TObject<{
|
|
24
25
|
cpuLimit: Type.TNumber;
|
|
@@ -10103,23 +10103,49 @@ var init_build2 = __esm(() => {
|
|
|
10103
10103
|
});
|
|
10104
10104
|
|
|
10105
10105
|
// node_modules/@supacloud/delivery/src/worker-execution.ts
|
|
10106
|
+
function workerShardCount(group) {
|
|
10107
|
+
return group.shardCount ?? 1;
|
|
10108
|
+
}
|
|
10109
|
+
function shardSuffix(shard) {
|
|
10110
|
+
if (!Number.isSafeInteger(shard) || shard < 0 || shard >= 64) {
|
|
10111
|
+
throw new Error("WORKER_SHARD_INVALID");
|
|
10112
|
+
}
|
|
10113
|
+
return `s${shard.toString(36).padStart(2, "0")}`;
|
|
10114
|
+
}
|
|
10115
|
+
function workerShardQueue(group, shard) {
|
|
10116
|
+
const count = workerShardCount(group);
|
|
10117
|
+
if (!Number.isSafeInteger(shard) || shard < 0 || shard >= count) {
|
|
10118
|
+
throw new Error("WORKER_SHARD_INVALID");
|
|
10119
|
+
}
|
|
10120
|
+
if (count === 1)
|
|
10121
|
+
return group.queue;
|
|
10122
|
+
const queue = `${group.queue}_${shardSuffix(shard)}`;
|
|
10123
|
+
if (!/^scw_[a-z0-9_]{1,40}$/.test(queue))
|
|
10124
|
+
throw new Error("WORKER_QUEUE_INVALID");
|
|
10125
|
+
return queue;
|
|
10126
|
+
}
|
|
10106
10127
|
function parseWorkerExecutionGroup(value) {
|
|
10107
10128
|
if (!Check2(WorkerExecutionGroupSchema, value) || value.replicas > value.maxReplicas || !value.queue.endsWith(`_v${value.definitionVersion}`) || value.lifecycle.visibilityTimeoutSeconds < value.lifecycle.executionTimeoutSeconds + value.lifecycle.shutdownGraceSeconds + 30) {
|
|
10108
10129
|
throw new Error("WORKER_EXECUTION_INVALID");
|
|
10109
10130
|
}
|
|
10110
|
-
|
|
10131
|
+
const group = structuredClone(value);
|
|
10132
|
+
for (let shard = 0;shard < workerShardCount(group); shard += 1)
|
|
10133
|
+
workerShardQueue(group, shard);
|
|
10134
|
+
return group;
|
|
10111
10135
|
}
|
|
10112
10136
|
function validateWorkerExecution(groups) {
|
|
10113
10137
|
const names = new Set, targets = new Set, queues = new Set, routes = new Set;
|
|
10114
10138
|
for (const candidate of groups) {
|
|
10115
10139
|
const group = parseWorkerExecutionGroup(candidate);
|
|
10116
10140
|
const route = `${group.taskKey}:${group.definitionVersion}`;
|
|
10117
|
-
|
|
10141
|
+
const physicalQueues = Array.from({ length: workerShardCount(group) }, (_, shard) => workerShardQueue(group, shard));
|
|
10142
|
+
if (names.has(group.name) || targets.has(group.target) || queues.has(group.queue) || physicalQueues.some((queue) => queues.has(queue)) || routes.has(route)) {
|
|
10118
10143
|
throw new Error("WORKER_EXECUTION_CONFLICT");
|
|
10119
10144
|
}
|
|
10120
10145
|
names.add(group.name);
|
|
10121
10146
|
targets.add(group.target);
|
|
10122
10147
|
queues.add(group.queue);
|
|
10148
|
+
physicalQueues.forEach((queue) => queues.add(queue));
|
|
10123
10149
|
routes.add(route);
|
|
10124
10150
|
}
|
|
10125
10151
|
}
|
|
@@ -10143,6 +10169,7 @@ var init_worker_execution = __esm(() => {
|
|
|
10143
10169
|
definitionVersion: String2({ pattern: "^[1-9][0-9]{0,8}$" }),
|
|
10144
10170
|
replicas: positive(16),
|
|
10145
10171
|
maxReplicas: positive(16),
|
|
10172
|
+
shardCount: Optional(positive(64)),
|
|
10146
10173
|
concurrencyPerReplica: positive(32),
|
|
10147
10174
|
resources: ComputeResourcesSchema,
|
|
10148
10175
|
database: _Object_({
|
|
@@ -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;
|