@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/dist/index.js CHANGED
@@ -1089,7 +1089,13 @@ function validateGraph(graph, options = false) {
1089
1089
  if (command.rpc && command.branches) {
1090
1090
  error("command-rpc-branches-conflict", `Command ${command.name} cannot declare both rpc and branches.`, module.file, module.line);
1091
1091
  }
1092
+ if (command.branchesFallback !== undefined && command.branchesFallback !== "reject" && command.branchesFallback !== "command") {
1093
+ error("command-branches-fallback-invalid", `Command ${command.name} has an invalid branches fallback.`, module.file, module.line);
1094
+ }
1092
1095
  const branchAdapters = command.branches ?? [];
1096
+ if (command.branchesFallback === "command" && branchAdapters.length === 0) {
1097
+ error("command-branches-fallback-without-branches", `Command ${command.name} declares a fallback without branches.`, module.file, module.line);
1098
+ }
1093
1099
  for (const branch of branchAdapters) {
1094
1100
  if (typeof options !== "object" || !Object.hasOwn(options.commandCapabilities?.rpc ?? {}, branch.rpc)) {
1095
1101
  error("command-rpc-unavailable", `Command ${command.name} requires configured RPC adapter '${branch.rpc}'.`, module.file, module.line);
@@ -1119,7 +1125,7 @@ function validateGraph(graph, options = false) {
1119
1125
  error("command-transaction-unsupported", `Command ${command.name} branch '${branch.when}' declares transaction, but transaction support is unavailable.`, module.file, module.line);
1120
1126
  }
1121
1127
  }
1122
- if (hostCaps.requirePersistentAdapters && (command.rpc !== undefined && (!rpcCaps?.boundary || rpcCaps.audit !== true || rpcCaps.idempotency !== true) || branchCaps.length > 0 && branchCaps.some(({ branch, capabilities }) => !capabilities?.boundary || capabilities.audit !== true || capabilities.idempotency !== true || capabilities.boundary === "database" && (capabilities.transaction !== true || branch.transaction !== "required")) || hostCaps.permission !== true || !command.permission || branchCaps.length === 0 && (!command.audit || command.idempotency !== "required") || branchCaps.length > 0 && branchCaps.some(({ branch }) => !branch.audit || branch.idempotency !== "required") || branchCaps.length > 0 && branchCaps.some(({ capabilities }) => capabilities?.boundary === "external" && capabilities.reconcile !== true) || command.rpc !== undefined && rpcCaps?.boundary === "database" && (rpcCaps.transaction !== true || command.transaction !== "required") || command.rpc === undefined && branchCaps.length === 0 && (hostCaps.audit !== true || hostCaps.idempotency !== true || hostCaps.transaction !== true || command.transaction !== "required") || command.rpc !== undefined && rpcCaps?.boundary === "external" && rpcCaps.reconcile !== true)) {
1128
+ if (hostCaps.requirePersistentAdapters && (command.rpc !== undefined && (!rpcCaps?.boundary || rpcCaps.audit !== true || rpcCaps.idempotency !== true) || branchCaps.length > 0 && branchCaps.some(({ branch, capabilities }) => !capabilities?.boundary || capabilities.audit !== true || capabilities.idempotency !== true || capabilities.boundary === "database" && (capabilities.transaction !== true || branch.transaction !== "required")) || hostCaps.permission !== true || !command.permission || branchCaps.length === 0 && (!command.audit || command.idempotency !== "required") || branchCaps.length > 0 && branchCaps.some(({ branch }) => !branch.audit || branch.idempotency !== "required") || branchCaps.length > 0 && branchCaps.some(({ capabilities }) => capabilities?.boundary === "external" && capabilities.reconcile !== true) || command.branchesFallback === "command" && (!command.audit || command.idempotency !== "required" || command.transaction !== "required" || hostCaps.audit !== true || hostCaps.idempotency !== true || hostCaps.transaction !== true) || command.rpc !== undefined && rpcCaps?.boundary === "database" && (rpcCaps.transaction !== true || command.transaction !== "required") || command.rpc === undefined && branchCaps.length === 0 && (hostCaps.audit !== true || hostCaps.idempotency !== true || hostCaps.transaction !== true || command.transaction !== "required") || command.rpc !== undefined && rpcCaps?.boundary === "external" && rpcCaps.reconcile !== true)) {
1123
1129
  error("command-persistence-required", `Command ${command.name} requires an explicit persistent adapter, permission, audit and idempotency policy.`, module.file, module.line, "Register a named database/external adapter, enable permission checks and declare permission, audit and required idempotency on the command.");
1124
1130
  }
1125
1131
  if (rpcCaps?.boundary === "external" && (command.transaction === "required" || rpcCaps.transaction === true)) {
@@ -1672,6 +1678,7 @@ function buildContractManifest(graph, artifacts) {
1672
1678
  module: module.name,
1673
1679
  ...command.permission === undefined ? {} : { permission: command.permission },
1674
1680
  ...command.rpc === undefined ? {} : { rpc: command.rpc },
1681
+ ...command.branchesFallback === undefined ? {} : { branchesFallback: command.branchesFallback },
1675
1682
  ...command.branches === undefined ? {} : {
1676
1683
  branches: command.branches.map((branch) => ({
1677
1684
  when: branch.when,
@@ -2089,6 +2096,47 @@ function allAspects(module) {
2089
2096
  ...(module.jobs ?? []).flatMap((job) => job.aspects ?? [])
2090
2097
  ];
2091
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
+ }
2092
2140
  function createExecutionPlans(graph) {
2093
2141
  const aspects = (boundary, refs = []) => refs.map((ref, index) => `${boundary}.aspect[${index}]:${ref.name}`);
2094
2142
  return graph.modules.flatMap((module) => [
@@ -2098,46 +2146,34 @@ function createExecutionPlans(graph) {
2098
2146
  const command = module.commands.find((item) => item.className === commandRef || item.name === commandRef);
2099
2147
  const boundCommand = route.command === undefined ? undefined : command;
2100
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));
2101
2156
  return {
2102
2157
  module: module.name,
2103
2158
  kind: "route",
2104
2159
  name: `${route.method} ${path.length > 1 ? path.replace(/\/+$/, "") : path}`,
2105
2160
  ...command ? { command: command.name } : {},
2106
- stages: [
2107
- ...boundCommand ? [
2108
- "commandExecutor",
2109
- "authorize",
2110
- ...boundCommand.branches?.length ? ["rpc:selected-branch"] : boundCommand.rpc ? [`rpc:${boundCommand.rpc}`] : [
2111
- ...boundCommand.idempotency === "required" ? ["idempotency"] : [],
2112
- ...boundCommand.transaction === "required" ? ["transaction"] : []
2113
- ]
2114
- ] : [],
2115
- ...aspects(`module:${module.name}`, module.aspects),
2116
- ...aspects("route", route.aspects),
2117
- ...aspects("command", boundCommand?.aspects),
2118
- "handler",
2119
- ...boundCommand?.audit && !boundCommand.rpc && !boundCommand.branches?.length ? ["audit"] : []
2120
- ]
2161
+ ...plannedStages
2121
2162
  };
2122
2163
  })),
2123
- ...module.commands.map((command) => ({
2124
- module: module.name,
2125
- kind: "command",
2126
- name: command.name,
2127
- command: command.name,
2128
- stages: [
2129
- "commandExecutor",
2130
- "authorize",
2131
- ...command.branches?.length ? ["rpc:selected-branch"] : command.rpc ? [`rpc:${command.rpc}`] : [
2132
- ...command.idempotency === "required" ? ["idempotency"] : [],
2133
- ...command.transaction === "required" ? ["transaction"] : []
2134
- ],
2164
+ ...module.commands.map((command) => {
2165
+ const stagePaths = commandStagePaths(command, [
2135
2166
  ...aspects(`module:${module.name}`, module.aspects),
2136
- ...aspects("command", command.aspects),
2137
- "handler",
2138
- ...command.audit && !command.rpc && !command.branches?.length ? ["audit"] : []
2139
- ]
2140
- })),
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
+ }),
2141
2177
  ...(module.jobs ?? []).map((job) => ({
2142
2178
  module: module.name,
2143
2179
  kind: "job",
@@ -2403,6 +2439,7 @@ function renderApplication(graph, options) {
2403
2439
  permission: command.permission ?? null,
2404
2440
  rpc: command.rpc ?? null,
2405
2441
  branches: command.branches ?? [],
2442
+ branchesFallback: command.branchesFallback ?? "reject",
2406
2443
  transaction: command.transaction ?? null,
2407
2444
  audit: command.audit ?? null,
2408
2445
  idempotency: command.idempotency ?? null
@@ -2471,25 +2508,29 @@ function renderApplication(graph, options) {
2471
2508
  }
2472
2509
  };
2473
2510
  }));
2474
- const commands = module.commands.map((command) => ({
2475
- name: command.name,
2476
- className: command.className,
2477
- executor: "commandExecutor",
2478
- adapter: command.rpc ?? null,
2479
- permission: command.permission ?? null,
2480
- audit: command.audit ?? null,
2481
- transaction: command.transaction,
2482
- idempotency: command.idempotency,
2483
- branches: command.branches ?? [],
2484
- ...command.tenantScope === undefined ? {} : { tenantScope: command.tenantScope },
2485
- requiredCapabilities: {
2486
- permission: true,
2487
- audit: command.branches?.length ? command.branches.some((branch) => Boolean(branch.audit)) : Boolean(command.audit),
2488
- idempotency: command.branches?.length ? command.branches.some((branch) => branch.idempotency === "required") : command.idempotency === "required",
2489
- transaction: command.branches?.length ? command.branches.some((branch) => branch.transaction === "required") : command.transaction === "required"
2490
- },
2491
- aspects: (command.aspects ?? []).map((aspect) => aspect.name)
2492
- })).sort((left, right) => left.name.localeCompare(right.name));
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));
2493
2534
  const jobs = (module.jobs ?? []).map((job) => ({
2494
2535
  name: job.name,
2495
2536
  className: job.className,
@@ -2973,6 +3014,7 @@ ${indent(item, 2)}`).join(",")}
2973
3014
  `idempotency: ${JSON.stringify(command.idempotency)}`,
2974
3015
  ...command.tenantScope ? [`tenantScope: ${JSON.stringify(command.tenantScope)}`] : [],
2975
3016
  ...command.rpc ? [`rpc: ${JSON.stringify(command.rpc)}`] : [],
3017
+ ...command.branchesFallback ? [`branchesFallback: ${JSON.stringify(command.branchesFallback)}`] : [],
2976
3018
  ...command.branches && command.branches.length > 0 ? [`branches: ${JSON.stringify(command.branches)}`] : [],
2977
3019
  ...command.standalone ? ["standalone: true"] : [],
2978
3020
  ...command.aspects && command.aspects.length > 0 ? [`aspects: ${this.renderAspects(command.aspects)}`, `aspectPipeline: ${this.renderAspectPipeline(command.aspects)}`] : []
@@ -4732,6 +4774,7 @@ var HEADER = "// GENERATED BY @supacloud/compiler — do not edit", INTERFACES =
4732
4774
  export interface CompiledCommand {
4733
4775
  rpc?: string;
4734
4776
  branches?: readonly CompiledCommandBranch[];
4777
+ branchesFallback?: "reject" | "command";
4735
4778
  className: string;
4736
4779
  name: string;
4737
4780
  permission: string;
@@ -4864,7 +4907,7 @@ async function compiledAspectStep(context: CompiledAspectContext, handler: () =>
4864
4907
  }
4865
4908
  called = true;
4866
4909
  pending = Promise.resolve().then(handler).then(result => {
4867
- if (strict) freezeCompiledAspectData(result);
4910
+ if (strict && !(result instanceof Response)) freezeCompiledAspectData(result);
4868
4911
  value = result;
4869
4912
  return result;
4870
4913
  });
@@ -15630,23 +15673,49 @@ var init_value2 = __esm(() => {
15630
15673
  });
15631
15674
 
15632
15675
  // node_modules/@supacloud/delivery/src/worker-execution.ts
15676
+ function workerShardCount(group) {
15677
+ return group.shardCount ?? 1;
15678
+ }
15679
+ function shardSuffix(shard) {
15680
+ if (!Number.isSafeInteger(shard) || shard < 0 || shard >= 64) {
15681
+ throw new Error("WORKER_SHARD_INVALID");
15682
+ }
15683
+ return `s${shard.toString(36).padStart(2, "0")}`;
15684
+ }
15685
+ function workerShardQueue(group, shard) {
15686
+ const count = workerShardCount(group);
15687
+ if (!Number.isSafeInteger(shard) || shard < 0 || shard >= count) {
15688
+ throw new Error("WORKER_SHARD_INVALID");
15689
+ }
15690
+ if (count === 1)
15691
+ return group.queue;
15692
+ const queue = `${group.queue}_${shardSuffix(shard)}`;
15693
+ if (!/^scw_[a-z0-9_]{1,40}$/.test(queue))
15694
+ throw new Error("WORKER_QUEUE_INVALID");
15695
+ return queue;
15696
+ }
15633
15697
  function parseWorkerExecutionGroup(value) {
15634
15698
  if (!Check2(WorkerExecutionGroupSchema, value) || value.replicas > value.maxReplicas || !value.queue.endsWith(`_v${value.definitionVersion}`) || value.lifecycle.visibilityTimeoutSeconds < value.lifecycle.executionTimeoutSeconds + value.lifecycle.shutdownGraceSeconds + 30) {
15635
15699
  throw new Error("WORKER_EXECUTION_INVALID");
15636
15700
  }
15637
- return structuredClone(value);
15701
+ const group = structuredClone(value);
15702
+ for (let shard = 0;shard < workerShardCount(group); shard += 1)
15703
+ workerShardQueue(group, shard);
15704
+ return group;
15638
15705
  }
15639
15706
  function validateWorkerExecution(groups) {
15640
15707
  const names = new Set, targets = new Set, queues = new Set, routes = new Set;
15641
15708
  for (const candidate of groups) {
15642
15709
  const group = parseWorkerExecutionGroup(candidate);
15643
15710
  const route = `${group.taskKey}:${group.definitionVersion}`;
15644
- if (names.has(group.name) || targets.has(group.target) || queues.has(group.queue) || routes.has(route)) {
15711
+ const physicalQueues = Array.from({ length: workerShardCount(group) }, (_, shard) => workerShardQueue(group, shard));
15712
+ if (names.has(group.name) || targets.has(group.target) || queues.has(group.queue) || physicalQueues.some((queue) => queues.has(queue)) || routes.has(route)) {
15645
15713
  throw new Error("WORKER_EXECUTION_CONFLICT");
15646
15714
  }
15647
15715
  names.add(group.name);
15648
15716
  targets.add(group.target);
15649
15717
  queues.add(group.queue);
15718
+ physicalQueues.forEach((queue) => queues.add(queue));
15650
15719
  routes.add(route);
15651
15720
  }
15652
15721
  }
@@ -15670,6 +15739,7 @@ var init_worker_execution = __esm(() => {
15670
15739
  definitionVersion: String2({ pattern: "^[1-9][0-9]{0,8}$" }),
15671
15740
  replicas: positive(16),
15672
15741
  maxReplicas: positive(16),
15742
+ shardCount: Optional(positive(64)),
15673
15743
  concurrencyPerReplica: positive(32),
15674
15744
  resources: ComputeResourcesSchema,
15675
15745
  database: _Object_({
@@ -16079,7 +16149,7 @@ function parseDeliveryBuildManifest(value) {
16079
16149
  if (!Check2(DeliveryBuildManifestSchema, value))
16080
16150
  throw new Error("Invalid delivery build manifest.");
16081
16151
  validateWorkerExecution(value.plan.targets.flatMap((target) => target.execution ? [target.execution] : []));
16082
- if (value.plan.targets.reduce((count, target) => count + (target.execution?.replicas ?? 1), 0) > 32) {
16152
+ if (value.plan.targets.reduce((count, target) => count + (target.execution ? (target.execution.replicas ?? 1) * workerShardCount(target.execution) : 1), 0) > 256) {
16083
16153
  throw new Error("Invalid delivery replica count.");
16084
16154
  }
16085
16155
  for (const target of value.plan.targets) {
@@ -16372,7 +16442,8 @@ var init_execution_snapshot = __esm(() => {
16372
16442
  kind: kind2,
16373
16443
  name: text,
16374
16444
  command: Optional(text),
16375
- stages: _Array_(text)
16445
+ stages: _Array_(text),
16446
+ stagePaths: Optional(_Array_(_Array_(text)))
16376
16447
  }, closed3)),
16377
16448
  commandAliases: _Array_(_Object_({ module: text, name: text, alias: text }, closed3)),
16378
16449
  contexts: _Array_(_Object_({
@@ -18091,7 +18162,8 @@ function buildTimeline(events, matchedPlans) {
18091
18162
  attempts: [],
18092
18163
  omittedAttempts: 0
18093
18164
  },
18094
- attempts: new Map
18165
+ attempts: new Map,
18166
+ ...plan === undefined ? {} : { plan }
18095
18167
  };
18096
18168
  groups.set(key, group);
18097
18169
  }
@@ -18119,13 +18191,16 @@ function buildTimeline(events, matchedPlans) {
18119
18191
  const declared = group.entry.declaredStages;
18120
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) => {
18121
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);
18122
- const terminal = declared[declared.length - 1];
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");
18123
18198
  return {
18124
18199
  attempt: attempt.attempt,
18125
18200
  stages,
18126
18201
  failed: hasFailure(attempt),
18127
- complete: terminal !== undefined && attempt.stages.some((stage) => stage.stage === terminal && stage.phase === "succeeded"),
18128
- missingStages: declared.filter((stage) => !attempt.observed.has(stage)),
18202
+ complete,
18203
+ missingStages: bestPath.filter((stage) => !attempt.observed.has(stage)),
18129
18204
  unexpectedStages: [],
18130
18205
  traceIds: [...attempt.traceIds].sort(),
18131
18206
  omittedStages: attempt.stages.length - stages.length
@@ -18547,7 +18622,7 @@ function importPath(out, path) {
18547
18622
  function parseDatabaseContractsOptions(value, directory) {
18548
18623
  if (!value || typeof value !== "object" || Array.isArray(value))
18549
18624
  throw new TypeError("Expected database contracts configuration");
18550
- const allowed = ["rootDir", "outDir", "postgrestTypes", "modelSchema", "role", "graphql", "migrations"];
18625
+ const allowed = ["rootDir", "outDir", "postgrestTypes", "modelSchema", "role", "graphql", "rpcRegistry", "rpcCatalog", "migrations"];
18551
18626
  if (Object.keys(value).some((name) => !allowed.includes(name)))
18552
18627
  throw new TypeError("Unknown database contracts option");
18553
18628
  const field = (name) => {
@@ -18563,6 +18638,8 @@ function parseDatabaseContractsOptions(value, directory) {
18563
18638
  };
18564
18639
  const postgrestTypes = optionalPath("postgrestTypes");
18565
18640
  const modelSchema = optionalPath("modelSchema");
18641
+ const rpcRegistry = optionalPath("rpcRegistry");
18642
+ const rpcCatalog = optionalPath("rpcCatalog");
18566
18643
  const graphqlPresent = Object.hasOwn(value, "graphql");
18567
18644
  if (!postgrestTypes && !modelSchema && !graphqlPresent) {
18568
18645
  throw new TypeError("Database contracts require postgrestTypes, modelSchema, or graphql");
@@ -18584,6 +18661,8 @@ function parseDatabaseContractsOptions(value, directory) {
18584
18661
  ...modelSchema ? { modelSchema: resolve24(directory, modelSchema) } : {},
18585
18662
  role: field("role"),
18586
18663
  ...graphql ? { graphql } : {},
18664
+ ...rpcRegistry ? { rpcRegistry: resolve24(directory, rpcRegistry) } : {},
18665
+ ...rpcCatalog ? { rpcCatalog: resolve24(directory, rpcCatalog) } : {},
18587
18666
  migrations: migrations.map((file) => resolve24(directory, file))
18588
18667
  };
18589
18668
  }
@@ -18659,6 +18738,10 @@ async function generateDatabaseContracts(options, check = false) {
18659
18738
  }
18660
18739
  if (options.graphql)
18661
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);
18662
18745
  if (new Set(options.migrations.map((path) => resolve24(rootDir, path))).size !== options.migrations.length) {
18663
18746
  throw new Error("Duplicate migration in database contracts configuration");
18664
18747
  }
@@ -19838,6 +19921,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths, effectOptio
19838
19921
  const permission = stringLiteralProp(meta, "permission");
19839
19922
  const rpc = checkedRpc(meta, ctx);
19840
19923
  const branches = checkedBranches(meta, ctx);
19924
+ const branchesFallback = checkedBranchesFallback(meta, ctx);
19841
19925
  const audit = stringLiteralProp(meta, "audit");
19842
19926
  const standaloneName = stringLiteralProp(meta, "name") ?? classInfo.decl.name?.text ?? name;
19843
19927
  const standaloneUses = await parseInfraResourceUseRefs(infraResourceProp(meta, "uses", ctx), ctx, `command ${standaloneName}`, sourcePath(ctx.rootDir, classInfo.file), lineOf(classInfo.decl));
@@ -19848,6 +19932,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths, effectOptio
19848
19932
  ...permission === undefined ? {} : { permission },
19849
19933
  ...rpc === undefined ? {} : { rpc },
19850
19934
  ...branches === undefined ? {} : { branches },
19935
+ ...branchesFallback === undefined ? {} : { branchesFallback },
19851
19936
  transaction: checkedCommandMode(meta, "transaction", ctx, classInfo.decl.name?.text ?? name) ?? "none",
19852
19937
  ...audit === undefined ? {} : { audit },
19853
19938
  idempotency: checkedCommandMode(meta, "idempotency", ctx, classInfo.decl.name?.text ?? name) ?? "none",
@@ -20222,6 +20307,7 @@ async function parseModule(candidate, nameByNode, ctx) {
20222
20307
  const permission = stringLiteralProp(meta, "permission");
20223
20308
  const rpc = checkedRpc(meta, ctx);
20224
20309
  const branches = checkedBranches(meta, ctx);
20310
+ const branchesFallback = checkedBranchesFallback(meta, ctx);
20225
20311
  const audit = stringLiteralProp(meta, "audit");
20226
20312
  const commandName = stringLiteralProp(meta, "name") ?? cls.name?.text ?? "<anonymous>";
20227
20313
  const uses = await parseInfraResourceUseRefs(infraResourceProp(meta, "uses", ctx), ctx, `command ${commandName}`, sourcePath(ctx.rootDir, cls.getSourceFile().fileName), lineOf(cls));
@@ -20233,6 +20319,7 @@ async function parseModule(candidate, nameByNode, ctx) {
20233
20319
  ...permission === undefined ? {} : { permission },
20234
20320
  ...rpc === undefined ? {} : { rpc },
20235
20321
  ...branches === undefined ? {} : { branches },
20322
+ ...branchesFallback === undefined ? {} : { branchesFallback },
20236
20323
  transaction: checkedCommandMode(meta, "transaction", ctx, cls.name?.text ?? "<anonymous>") ?? "none",
20237
20324
  ...audit === undefined ? {} : { audit },
20238
20325
  idempotency: checkedCommandMode(meta, "idempotency", ctx, cls.name?.text ?? "<anonymous>") ?? "none",
@@ -22071,6 +22158,23 @@ function checkedBranches(meta, ctx) {
22071
22158
  }
22072
22159
  return branches;
22073
22160
  }
22161
+ function checkedBranchesFallback(meta, ctx) {
22162
+ const expression = getProp(meta, "branchesFallback");
22163
+ if (!expression)
22164
+ return;
22165
+ const value = stringLiteralProp(meta, "branchesFallback");
22166
+ if (value === "reject" || value === "command")
22167
+ return value;
22168
+ ctx.diagnostics.push({
22169
+ severity: "error",
22170
+ code: "invalid-command-branches-fallback",
22171
+ file: sourcePath(ctx.rootDir, expression.getSourceFile().fileName),
22172
+ line: lineOf(expression),
22173
+ message: "Command branchesFallback must be 'reject' or 'command'.",
22174
+ suggestion: "Use branchesFallback: 'command' only when the command handler owns the unmatched-input boundary."
22175
+ });
22176
+ return;
22177
+ }
22074
22178
  async function classDeps(cls, ctx) {
22075
22179
  const injectable = await parseInjectableOptions(cls, ctx);
22076
22180
  const ctor = cls.members.find(ts8.isConstructorDeclaration);
@@ -23418,11 +23522,13 @@ function assertDatabaseGovernance(value) {
23418
23522
  if (!value || typeof value !== "object" || Array.isArray(value))
23419
23523
  throw new Error("Invalid databaseGovernance configuration.");
23420
23524
  const entry = value;
23421
- 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()))) {
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) {
23422
23528
  throw new Error("Invalid databaseGovernance mode, paths, connection or schemas.");
23423
23529
  }
23424
23530
  }
23425
- async function checkDatabaseGovernance(rootDir, value) {
23531
+ async function checkDatabaseGovernance(rootDir, value, rpcCapabilities = []) {
23426
23532
  if (value === undefined)
23427
23533
  return [];
23428
23534
  const failure = () => [{
@@ -23439,7 +23545,7 @@ async function checkDatabaseGovernance(rootDir, value) {
23439
23545
  const output = await new Promise((accept, reject) => {
23440
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));
23441
23547
  child.stdin?.on("error", reject);
23442
- child.stdin?.end(JSON.stringify(options));
23548
+ child.stdin?.end(JSON.stringify({ options, rpcCapabilities }));
23443
23549
  });
23444
23550
  const report = JSON.parse(output);
23445
23551
  if (!report || typeof report !== "object" || !("diagnostics" in report) || !Array.isArray(report.diagnostics))
@@ -23485,7 +23591,7 @@ async function compileProject(options) {
23485
23591
  ...validateEffectPolicies(graph, options.effect),
23486
23592
  ...validateJobPolicies(graph)
23487
23593
  ];
23488
- const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance);
23594
+ const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance, Object.keys(options.commandCapabilities?.rpc ?? {}));
23489
23595
  diagnostics.push(...databaseDiagnostics);
23490
23596
  if (databaseDiagnostics.length)
23491
23597
  return { diagnostics, graph, written: [] };
@@ -23555,7 +23661,7 @@ async function checkProject(options) {
23555
23661
  ...validateEffectPolicies(graph, options.effect),
23556
23662
  ...validateJobPolicies(graph)
23557
23663
  ];
23558
- const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance);
23664
+ const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance, Object.keys(options.commandCapabilities?.rpc ?? {}));
23559
23665
  diagnostics.push(...databaseDiagnostics);
23560
23666
  if (databaseDiagnostics.length) {
23561
23667
  return { diagnostics, graph, upToDate: false, mismatches: ["Database governance verification failed."] };
@@ -23860,8 +23966,8 @@ function createDeliveryPlan(graph, input, validation) {
23860
23966
  target.execution = group;
23861
23967
  }
23862
23968
  }
23863
- if ([...targets.values()].reduce((count, target) => count + (target.execution?.replicas ?? 1), 0) > 32) {
23864
- fail("delivery-execution-replica-limit", "A release supports at most 32 runtime targets including replicas.", "Reduce the replica count or split independent applications.");
23969
+ if ([...targets.values()].reduce((count, target) => count + (target.execution ? (target.execution.replicas ?? 1) * (target.execution.shardCount ?? 1) : 1), 0) > 256) {
23970
+ fail("delivery-execution-replica-limit", "A release supports at most 256 runtime targets including replicas and queue shards.", "Reduce the replica or shard count, or split independent applications.");
23865
23971
  }
23866
23972
  const canonicalDiagnostics = [...new Map(diagnostics.map((item) => [JSON.stringify(item), item])).values()].sort((a, b) => compare2(JSON.stringify(a), JSON.stringify(b)));
23867
23973
  if (canonicalDiagnostics.some((item) => item.severity === "error")) {
@@ -23951,6 +24057,7 @@ function validateExecutionManifest(plan, capabilities = {}) {
23951
24057
  ...command.adapter === null ? {} : { rpc: command.adapter },
23952
24058
  ...command.audit === null ? {} : { audit: command.audit },
23953
24059
  ...command.branches.length === 0 ? {} : { branches: command.branches },
24060
+ ...command.branchesFallback === undefined ? {} : { branchesFallback: command.branchesFallback },
23954
24061
  transaction: command.transaction,
23955
24062
  idempotency: command.idempotency
23956
24063
  }))
@@ -25702,10 +25809,10 @@ import { readFileSync as readFileSync4 } from "node:fs";
25702
25809
  import { resolve as resolve17 } from "node:path";
25703
25810
 
25704
25811
  // src/migration-app-version.ts
25705
- var MIGRATION_APP_VERSION = "0.39.0";
25812
+ var MIGRATION_APP_VERSION = "0.39.1";
25706
25813
 
25707
25814
  // src/migration-elysia-version.ts
25708
- var MIGRATION_ELYSIA_VERSION = "0.41.0";
25815
+ var MIGRATION_ELYSIA_VERSION = "0.42.1";
25709
25816
 
25710
25817
  // src/migration-policy.ts
25711
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_APP_VERSION = "0.39.0";
1
+ export declare const MIGRATION_APP_VERSION = "0.39.1";
@@ -1 +1 @@
1
- export declare const MIGRATION_ELYSIA_VERSION = "0.41.0";
1
+ export declare const MIGRATION_ELYSIA_VERSION = "0.42.1";
package/dist/types.d.ts CHANGED
@@ -324,6 +324,7 @@ export interface ControllerNode {
324
324
  export interface CommandNode {
325
325
  rpc?: string;
326
326
  branches?: CommandBranchNode[];
327
+ branchesFallback?: "reject" | "command";
327
328
  className: string;
328
329
  name: string;
329
330
  permission?: string;
@@ -432,6 +433,7 @@ export interface ExecutionPlanCommand {
432
433
  transaction: CommandNode["transaction"];
433
434
  idempotency: CommandNode["idempotency"];
434
435
  branches: CommandBranchNode[];
436
+ branchesFallback: "reject" | "command";
435
437
  tenantScope?: CommandNode["tenantScope"];
436
438
  requiredCapabilities: {
437
439
  permission: boolean;
@@ -773,6 +775,15 @@ export interface DatabaseGovernanceOptions {
773
775
  databaseUrlEnv?: string;
774
776
  /** Schemas included in the live Catalog query. */
775
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
+ };
776
787
  }
777
788
  export interface ModuleBoundaryRule {
778
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.58.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,8 +57,8 @@
57
57
  "directory": "packages/compiler"
58
58
  },
59
59
  "dependencies": {
60
- "@supacloud/db": "0.22.0",
61
- "@supacloud/delivery": "0.25.0",
60
+ "@supacloud/db": "0.24.0",
61
+ "@supacloud/delivery": "0.26.0",
62
62
  "typebox": "1.4.3",
63
63
  "@graphql-codegen/core": "^6.2.2",
64
64
  "@graphql-codegen/typed-document-node": "^7.2.0",
@@ -68,7 +68,7 @@
68
68
  "typescript": "7.1.0-beta"
69
69
  },
70
70
  "overrides": {
71
- "@supacloud/contracts": "0.16.0",
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",