@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.
@@ -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
- if (entry["mode"] !== "off" && entry["mode"] !== "check" && entry["mode"] !== "enforce" || Object.keys(entry).some((key) => !["mode", "projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv", "schemas"].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()))) {
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 options = JSON.parse(await Bun.stdin.text());
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
- return structuredClone(value);
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
- if (names.has(group.name) || targets.has(group.target) || queues.has(group.queue) || routes.has(route)) {
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;