@supacloud/compiler 0.57.1 → 0.59.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/cli.js +290 -212
- package/dist/contract-manifest.d.ts +1 -0
- package/dist/delivery-bundle-protocol.d.ts +1 -0
- package/dist/delivery-bundle-worker.js +29 -2
- package/dist/index.js +290 -212
- package/dist/migration-app-version.d.ts +1 -1
- package/dist/migration-elysia-version.d.ts +1 -1
- package/dist/types.d.ts +5 -0
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -382,6 +382,7 @@ var init_di_resolution = __esm(() => {
|
|
|
382
382
|
"conflicting-injection-flags",
|
|
383
383
|
"cross-module-scoped-dependency",
|
|
384
384
|
"mixed-multi-provider-scopes",
|
|
385
|
+
"effect-contract-required",
|
|
385
386
|
"dynamic-aspect-reference",
|
|
386
387
|
"invalid-aspect-reference",
|
|
387
388
|
"aspect-operation-mismatch"
|
|
@@ -1089,7 +1090,13 @@ function validateGraph(graph, options = false) {
|
|
|
1089
1090
|
if (command.rpc && command.branches) {
|
|
1090
1091
|
error("command-rpc-branches-conflict", `Command ${command.name} cannot declare both rpc and branches.`, module.file, module.line);
|
|
1091
1092
|
}
|
|
1093
|
+
if (command.branchesFallback !== undefined && command.branchesFallback !== "reject" && command.branchesFallback !== "command") {
|
|
1094
|
+
error("command-branches-fallback-invalid", `Command ${command.name} has an invalid branches fallback.`, module.file, module.line);
|
|
1095
|
+
}
|
|
1092
1096
|
const branchAdapters = command.branches ?? [];
|
|
1097
|
+
if (command.branchesFallback === "command" && branchAdapters.length === 0) {
|
|
1098
|
+
error("command-branches-fallback-without-branches", `Command ${command.name} declares a fallback without branches.`, module.file, module.line);
|
|
1099
|
+
}
|
|
1093
1100
|
for (const branch of branchAdapters) {
|
|
1094
1101
|
if (typeof options !== "object" || !Object.hasOwn(options.commandCapabilities?.rpc ?? {}, branch.rpc)) {
|
|
1095
1102
|
error("command-rpc-unavailable", `Command ${command.name} requires configured RPC adapter '${branch.rpc}'.`, module.file, module.line);
|
|
@@ -1119,7 +1126,7 @@ function validateGraph(graph, options = false) {
|
|
|
1119
1126
|
error("command-transaction-unsupported", `Command ${command.name} branch '${branch.when}' declares transaction, but transaction support is unavailable.`, module.file, module.line);
|
|
1120
1127
|
}
|
|
1121
1128
|
}
|
|
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)) {
|
|
1129
|
+
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
1130
|
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
1131
|
}
|
|
1125
1132
|
if (rpcCaps?.boundary === "external" && (command.transaction === "required" || rpcCaps.transaction === true)) {
|
|
@@ -1672,6 +1679,7 @@ function buildContractManifest(graph, artifacts) {
|
|
|
1672
1679
|
module: module.name,
|
|
1673
1680
|
...command.permission === undefined ? {} : { permission: command.permission },
|
|
1674
1681
|
...command.rpc === undefined ? {} : { rpc: command.rpc },
|
|
1682
|
+
...command.branchesFallback === undefined ? {} : { branchesFallback: command.branchesFallback },
|
|
1675
1683
|
...command.branches === undefined ? {} : {
|
|
1676
1684
|
branches: command.branches.map((branch) => ({
|
|
1677
1685
|
when: branch.when,
|
|
@@ -2403,6 +2411,7 @@ function renderApplication(graph, options) {
|
|
|
2403
2411
|
permission: command.permission ?? null,
|
|
2404
2412
|
rpc: command.rpc ?? null,
|
|
2405
2413
|
branches: command.branches ?? [],
|
|
2414
|
+
branchesFallback: command.branchesFallback ?? "reject",
|
|
2406
2415
|
transaction: command.transaction ?? null,
|
|
2407
2416
|
audit: command.audit ?? null,
|
|
2408
2417
|
idempotency: command.idempotency ?? null
|
|
@@ -2453,6 +2462,7 @@ function renderApplication(graph, options) {
|
|
|
2453
2462
|
executor: route.command === undefined ? "route" : "command",
|
|
2454
2463
|
...command === undefined ? {} : { commandBoundary: route.command === undefined ? "handler" : "route" },
|
|
2455
2464
|
...route.effect === undefined ? {} : { effect: route.effect },
|
|
2465
|
+
...route.effectMode === undefined ? {} : { effectMode: route.effectMode },
|
|
2456
2466
|
invoker: isPipeline ? "pipeline" : "direct",
|
|
2457
2467
|
stages: operationPlans.find((plan) => plan.module === module.name && plan.kind === "route" && plan.name === `${route.method} ${joinRoutePaths(controller.path, route.path)}`).stages,
|
|
2458
2468
|
...commandRef === undefined ? {} : { command: command?.name ?? commandRef },
|
|
@@ -2480,6 +2490,7 @@ function renderApplication(graph, options) {
|
|
|
2480
2490
|
transaction: command.transaction,
|
|
2481
2491
|
idempotency: command.idempotency,
|
|
2482
2492
|
branches: command.branches ?? [],
|
|
2493
|
+
branchesFallback: command.branchesFallback ?? "reject",
|
|
2483
2494
|
...command.tenantScope === undefined ? {} : { tenantScope: command.tenantScope },
|
|
2484
2495
|
requiredCapabilities: {
|
|
2485
2496
|
permission: true,
|
|
@@ -2832,6 +2843,8 @@ class ModuleGenerator {
|
|
|
2832
2843
|
if (route.effect !== undefined) {
|
|
2833
2844
|
fields.push(`effect: ${JSON.stringify(route.effect)}`);
|
|
2834
2845
|
}
|
|
2846
|
+
if (route.effectMode !== undefined)
|
|
2847
|
+
fields.push(`effectMode: ${JSON.stringify(route.effectMode)}`);
|
|
2835
2848
|
if (idempotency !== undefined) {
|
|
2836
2849
|
fields.push(`idempotency: ${JSON.stringify(idempotency)}`);
|
|
2837
2850
|
}
|
|
@@ -2970,6 +2983,7 @@ ${indent(item, 2)}`).join(",")}
|
|
|
2970
2983
|
`idempotency: ${JSON.stringify(command.idempotency)}`,
|
|
2971
2984
|
...command.tenantScope ? [`tenantScope: ${JSON.stringify(command.tenantScope)}`] : [],
|
|
2972
2985
|
...command.rpc ? [`rpc: ${JSON.stringify(command.rpc)}`] : [],
|
|
2986
|
+
...command.branchesFallback ? [`branchesFallback: ${JSON.stringify(command.branchesFallback)}`] : [],
|
|
2973
2987
|
...command.branches && command.branches.length > 0 ? [`branches: ${JSON.stringify(command.branches)}`] : [],
|
|
2974
2988
|
...command.standalone ? ["standalone: true"] : [],
|
|
2975
2989
|
...command.aspects && command.aspects.length > 0 ? [`aspects: ${this.renderAspects(command.aspects)}`, `aspectPipeline: ${this.renderAspectPipeline(command.aspects)}`] : []
|
|
@@ -4681,6 +4695,8 @@ var HEADER = "// GENERATED BY @supacloud/compiler — do not edit", INTERFACES =
|
|
|
4681
4695
|
maxAttempts?: number;
|
|
4682
4696
|
timeoutMs?: number;
|
|
4683
4697
|
};
|
|
4698
|
+
/** Simple route profile; no Effect interpreter or error mapping is required. */
|
|
4699
|
+
effectMode?: "simple";
|
|
4684
4700
|
/** Compile-time ownership and transport classification for the route boundary. */
|
|
4685
4701
|
contract?: {
|
|
4686
4702
|
body?: "framework" | "domain";
|
|
@@ -4727,6 +4743,7 @@ var HEADER = "// GENERATED BY @supacloud/compiler — do not edit", INTERFACES =
|
|
|
4727
4743
|
export interface CompiledCommand {
|
|
4728
4744
|
rpc?: string;
|
|
4729
4745
|
branches?: readonly CompiledCommandBranch[];
|
|
4746
|
+
branchesFallback?: "reject" | "command";
|
|
4730
4747
|
className: string;
|
|
4731
4748
|
name: string;
|
|
4732
4749
|
permission: string;
|
|
@@ -4859,7 +4876,7 @@ async function compiledAspectStep(context: CompiledAspectContext, handler: () =>
|
|
|
4859
4876
|
}
|
|
4860
4877
|
called = true;
|
|
4861
4878
|
pending = Promise.resolve().then(handler).then(result => {
|
|
4862
|
-
if (strict) freezeCompiledAspectData(result);
|
|
4879
|
+
if (strict && !(result instanceof Response)) freezeCompiledAspectData(result);
|
|
4863
4880
|
value = result;
|
|
4864
4881
|
return result;
|
|
4865
4882
|
});
|
|
@@ -16488,23 +16505,49 @@ var init_application_development2 = __esm(() => {
|
|
|
16488
16505
|
});
|
|
16489
16506
|
|
|
16490
16507
|
// node_modules/@supacloud/delivery/src/worker-execution.ts
|
|
16508
|
+
function workerShardCount(group) {
|
|
16509
|
+
return group.shardCount ?? 1;
|
|
16510
|
+
}
|
|
16511
|
+
function shardSuffix(shard) {
|
|
16512
|
+
if (!Number.isSafeInteger(shard) || shard < 0 || shard >= 64) {
|
|
16513
|
+
throw new Error("WORKER_SHARD_INVALID");
|
|
16514
|
+
}
|
|
16515
|
+
return `s${shard.toString(36).padStart(2, "0")}`;
|
|
16516
|
+
}
|
|
16517
|
+
function workerShardQueue(group, shard) {
|
|
16518
|
+
const count = workerShardCount(group);
|
|
16519
|
+
if (!Number.isSafeInteger(shard) || shard < 0 || shard >= count) {
|
|
16520
|
+
throw new Error("WORKER_SHARD_INVALID");
|
|
16521
|
+
}
|
|
16522
|
+
if (count === 1)
|
|
16523
|
+
return group.queue;
|
|
16524
|
+
const queue = `${group.queue}_${shardSuffix(shard)}`;
|
|
16525
|
+
if (!/^scw_[a-z0-9_]{1,40}$/.test(queue))
|
|
16526
|
+
throw new Error("WORKER_QUEUE_INVALID");
|
|
16527
|
+
return queue;
|
|
16528
|
+
}
|
|
16491
16529
|
function parseWorkerExecutionGroup(value) {
|
|
16492
16530
|
if (!Check2(WorkerExecutionGroupSchema, value) || value.replicas > value.maxReplicas || !value.queue.endsWith(`_v${value.definitionVersion}`) || value.lifecycle.visibilityTimeoutSeconds < value.lifecycle.executionTimeoutSeconds + value.lifecycle.shutdownGraceSeconds + 30) {
|
|
16493
16531
|
throw new Error("WORKER_EXECUTION_INVALID");
|
|
16494
16532
|
}
|
|
16495
|
-
|
|
16533
|
+
const group = structuredClone(value);
|
|
16534
|
+
for (let shard = 0;shard < workerShardCount(group); shard += 1)
|
|
16535
|
+
workerShardQueue(group, shard);
|
|
16536
|
+
return group;
|
|
16496
16537
|
}
|
|
16497
16538
|
function validateWorkerExecution(groups) {
|
|
16498
16539
|
const names = new Set, targets = new Set, queues = new Set, routes = new Set;
|
|
16499
16540
|
for (const candidate of groups) {
|
|
16500
16541
|
const group = parseWorkerExecutionGroup(candidate);
|
|
16501
16542
|
const route = `${group.taskKey}:${group.definitionVersion}`;
|
|
16502
|
-
|
|
16543
|
+
const physicalQueues = Array.from({ length: workerShardCount(group) }, (_, shard) => workerShardQueue(group, shard));
|
|
16544
|
+
if (names.has(group.name) || targets.has(group.target) || queues.has(group.queue) || physicalQueues.some((queue) => queues.has(queue)) || routes.has(route)) {
|
|
16503
16545
|
throw new Error("WORKER_EXECUTION_CONFLICT");
|
|
16504
16546
|
}
|
|
16505
16547
|
names.add(group.name);
|
|
16506
16548
|
targets.add(group.target);
|
|
16507
16549
|
queues.add(group.queue);
|
|
16550
|
+
physicalQueues.forEach((queue) => queues.add(queue));
|
|
16508
16551
|
routes.add(route);
|
|
16509
16552
|
}
|
|
16510
16553
|
}
|
|
@@ -16528,6 +16571,7 @@ var init_worker_execution = __esm(() => {
|
|
|
16528
16571
|
definitionVersion: String2({ pattern: "^[1-9][0-9]{0,8}$" }),
|
|
16529
16572
|
replicas: positive(16),
|
|
16530
16573
|
maxReplicas: positive(16),
|
|
16574
|
+
shardCount: Optional(positive(64)),
|
|
16531
16575
|
concurrencyPerReplica: positive(32),
|
|
16532
16576
|
resources: ComputeResourcesSchema,
|
|
16533
16577
|
database: _Object_({
|
|
@@ -16937,7 +16981,7 @@ function parseDeliveryBuildManifest(value) {
|
|
|
16937
16981
|
if (!Check2(DeliveryBuildManifestSchema, value))
|
|
16938
16982
|
throw new Error("Invalid delivery build manifest.");
|
|
16939
16983
|
validateWorkerExecution(value.plan.targets.flatMap((target) => target.execution ? [target.execution] : []));
|
|
16940
|
-
if (value.plan.targets.reduce((count, target) => count + (target.execution
|
|
16984
|
+
if (value.plan.targets.reduce((count, target) => count + (target.execution ? (target.execution.replicas ?? 1) * workerShardCount(target.execution) : 1), 0) > 256) {
|
|
16941
16985
|
throw new Error("Invalid delivery replica count.");
|
|
16942
16986
|
}
|
|
16943
16987
|
for (const target of value.plan.targets) {
|
|
@@ -19873,6 +19917,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths, effectOptio
|
|
|
19873
19917
|
const permission = stringLiteralProp(meta, "permission");
|
|
19874
19918
|
const rpc = checkedRpc(meta, ctx);
|
|
19875
19919
|
const branches = checkedBranches(meta, ctx);
|
|
19920
|
+
const branchesFallback = checkedBranchesFallback(meta, ctx);
|
|
19876
19921
|
const audit = stringLiteralProp(meta, "audit");
|
|
19877
19922
|
const standaloneName = stringLiteralProp(meta, "name") ?? classInfo.decl.name?.text ?? name;
|
|
19878
19923
|
const standaloneUses = await parseInfraResourceUseRefs(infraResourceProp(meta, "uses", ctx), ctx, `command ${standaloneName}`, sourcePath(ctx.rootDir, classInfo.file), lineOf(classInfo.decl));
|
|
@@ -19883,6 +19928,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths, effectOptio
|
|
|
19883
19928
|
...permission === undefined ? {} : { permission },
|
|
19884
19929
|
...rpc === undefined ? {} : { rpc },
|
|
19885
19930
|
...branches === undefined ? {} : { branches },
|
|
19931
|
+
...branchesFallback === undefined ? {} : { branchesFallback },
|
|
19886
19932
|
transaction: checkedCommandMode(meta, "transaction", ctx, classInfo.decl.name?.text ?? name) ?? "none",
|
|
19887
19933
|
...audit === undefined ? {} : { audit },
|
|
19888
19934
|
idempotency: checkedCommandMode(meta, "idempotency", ctx, classInfo.decl.name?.text ?? name) ?? "none",
|
|
@@ -20257,6 +20303,7 @@ async function parseModule(candidate, nameByNode, ctx) {
|
|
|
20257
20303
|
const permission = stringLiteralProp(meta, "permission");
|
|
20258
20304
|
const rpc = checkedRpc(meta, ctx);
|
|
20259
20305
|
const branches = checkedBranches(meta, ctx);
|
|
20306
|
+
const branchesFallback = checkedBranchesFallback(meta, ctx);
|
|
20260
20307
|
const audit = stringLiteralProp(meta, "audit");
|
|
20261
20308
|
const commandName = stringLiteralProp(meta, "name") ?? cls.name?.text ?? "<anonymous>";
|
|
20262
20309
|
const uses = await parseInfraResourceUseRefs(infraResourceProp(meta, "uses", ctx), ctx, `command ${commandName}`, sourcePath(ctx.rootDir, cls.getSourceFile().fileName), lineOf(cls));
|
|
@@ -20268,6 +20315,7 @@ async function parseModule(candidate, nameByNode, ctx) {
|
|
|
20268
20315
|
...permission === undefined ? {} : { permission },
|
|
20269
20316
|
...rpc === undefined ? {} : { rpc },
|
|
20270
20317
|
...branches === undefined ? {} : { branches },
|
|
20318
|
+
...branchesFallback === undefined ? {} : { branchesFallback },
|
|
20271
20319
|
transaction: checkedCommandMode(meta, "transaction", ctx, cls.name?.text ?? "<anonymous>") ?? "none",
|
|
20272
20320
|
...audit === undefined ? {} : { audit },
|
|
20273
20321
|
idempotency: checkedCommandMode(meta, "idempotency", ctx, cls.name?.text ?? "<anonymous>") ?? "none",
|
|
@@ -21488,111 +21536,102 @@ async function parseController(input, ctx) {
|
|
|
21488
21536
|
}
|
|
21489
21537
|
const effectExpr = getProp(optionsObject, "effect");
|
|
21490
21538
|
if (effectExpr) {
|
|
21491
|
-
|
|
21492
|
-
|
|
21493
|
-
ctx.diagnostics.push({
|
|
21494
|
-
severity: "error",
|
|
21495
|
-
code: "invalid-effect-contract",
|
|
21496
|
-
errorCode: "SC3030",
|
|
21497
|
-
docsUrl: "https://supacloud.dev/errors/SC3030",
|
|
21498
|
-
file,
|
|
21499
|
-
line: lineOf(effectExpr),
|
|
21500
|
-
message: `Route ${route.handler} effect must be a statically resolvable object literal.`,
|
|
21501
|
-
suggestion: 'Use effect: { required: true, errors: [...], dependencies: [...], retry: "none" }.'
|
|
21502
|
-
});
|
|
21539
|
+
if (effectExpr.kind === ts8.SyntaxKind.FalseKeyword) {
|
|
21540
|
+
route.effectMode = "simple";
|
|
21503
21541
|
} else {
|
|
21504
|
-
const
|
|
21505
|
-
|
|
21506
|
-
if (requiredExpr !== undefined && booleanProp(effectObject, "required") !== true) {
|
|
21542
|
+
const effectObject = await resolveStaticObjectLiteral(effectExpr, ctx);
|
|
21543
|
+
if (!effectObject) {
|
|
21507
21544
|
ctx.diagnostics.push({
|
|
21508
21545
|
severity: "error",
|
|
21509
21546
|
code: "invalid-effect-contract",
|
|
21510
21547
|
errorCode: "SC3030",
|
|
21511
21548
|
docsUrl: "https://supacloud.dev/errors/SC3030",
|
|
21512
21549
|
file,
|
|
21513
|
-
line: lineOf(
|
|
21514
|
-
message: `Route ${route.handler} effect
|
|
21550
|
+
line: lineOf(effectExpr),
|
|
21551
|
+
message: `Route ${route.handler} effect must be a statically resolvable object literal.`,
|
|
21552
|
+
suggestion: 'Use effect: { required: true, errors: [...], dependencies: [...], retry: "none" }.'
|
|
21515
21553
|
});
|
|
21516
|
-
}
|
|
21517
|
-
|
|
21518
|
-
|
|
21519
|
-
if (
|
|
21554
|
+
} else {
|
|
21555
|
+
const effect = { required: true };
|
|
21556
|
+
const requiredExpr = getProp(effectObject, "required");
|
|
21557
|
+
if (requiredExpr !== undefined && booleanProp(effectObject, "required") !== true) {
|
|
21520
21558
|
ctx.diagnostics.push({
|
|
21521
21559
|
severity: "error",
|
|
21522
|
-
code: "invalid-effect-
|
|
21523
|
-
errorCode: "
|
|
21524
|
-
docsUrl: "https://supacloud.dev/errors/
|
|
21560
|
+
code: "invalid-effect-contract",
|
|
21561
|
+
errorCode: "SC3030",
|
|
21562
|
+
docsUrl: "https://supacloud.dev/errors/SC3030",
|
|
21525
21563
|
file,
|
|
21526
|
-
line: lineOf(
|
|
21527
|
-
message: `Route ${route.handler} effect.
|
|
21564
|
+
line: lineOf(requiredExpr),
|
|
21565
|
+
message: `Route ${route.handler} effect.required must be the literal true.`
|
|
21528
21566
|
});
|
|
21529
|
-
} else {
|
|
21530
|
-
effect.retry = retry;
|
|
21531
21567
|
}
|
|
21532
|
-
|
|
21533
|
-
|
|
21534
|
-
|
|
21535
|
-
|
|
21536
|
-
|
|
21537
|
-
|
|
21538
|
-
|
|
21568
|
+
const retry = stringLiteralProp(effectObject, "retry");
|
|
21569
|
+
if (retry !== undefined) {
|
|
21570
|
+
if (retry !== "none" && retry !== "explicit") {
|
|
21571
|
+
ctx.diagnostics.push({
|
|
21572
|
+
severity: "error",
|
|
21573
|
+
code: "invalid-effect-retry",
|
|
21574
|
+
errorCode: "SC3031",
|
|
21575
|
+
docsUrl: "https://supacloud.dev/errors/SC3031",
|
|
21576
|
+
file,
|
|
21577
|
+
line: lineOf(getProp(effectObject, "retry")),
|
|
21578
|
+
message: `Route ${route.handler} effect.retry must be "none" or "explicit".`
|
|
21579
|
+
});
|
|
21580
|
+
} else {
|
|
21581
|
+
effect.retry = retry;
|
|
21582
|
+
}
|
|
21583
|
+
} else if (ctx.effectOptions.requireRetryPolicy === false) {
|
|
21584
|
+
effect.retry = "none";
|
|
21585
|
+
}
|
|
21586
|
+
const maxAttemptsExpr = getProp(effectObject, "maxAttempts");
|
|
21587
|
+
if (maxAttemptsExpr !== undefined) {
|
|
21588
|
+
const maxAttempts = numberLiteralProp(effectObject, "maxAttempts");
|
|
21589
|
+
if (maxAttempts === undefined || maxAttempts < 1 || !Number.isInteger(maxAttempts)) {
|
|
21590
|
+
ctx.diagnostics.push({
|
|
21591
|
+
severity: "error",
|
|
21592
|
+
code: "invalid-effect-retry",
|
|
21593
|
+
errorCode: "SC3031",
|
|
21594
|
+
docsUrl: "https://supacloud.dev/errors/SC3031",
|
|
21595
|
+
file,
|
|
21596
|
+
line: lineOf(maxAttemptsExpr),
|
|
21597
|
+
message: `Route ${route.handler} effect.maxAttempts must be a positive integer.`
|
|
21598
|
+
});
|
|
21599
|
+
} else {
|
|
21600
|
+
effect.maxAttempts = maxAttempts;
|
|
21601
|
+
}
|
|
21602
|
+
}
|
|
21603
|
+
if (effect.retry === "explicit" && effect.maxAttempts === undefined) {
|
|
21539
21604
|
ctx.diagnostics.push({
|
|
21540
21605
|
severity: "error",
|
|
21541
21606
|
code: "invalid-effect-retry",
|
|
21542
21607
|
errorCode: "SC3031",
|
|
21543
21608
|
docsUrl: "https://supacloud.dev/errors/SC3031",
|
|
21544
21609
|
file,
|
|
21545
|
-
line: lineOf(
|
|
21546
|
-
message: `Route ${route.handler} effect.maxAttempts
|
|
21610
|
+
line: lineOf(effectExpr),
|
|
21611
|
+
message: `Route ${route.handler} effect.maxAttempts is required when retry is "explicit".`
|
|
21547
21612
|
});
|
|
21548
|
-
} else {
|
|
21549
|
-
effect.maxAttempts = maxAttempts;
|
|
21550
21613
|
}
|
|
21551
|
-
|
|
21552
|
-
|
|
21553
|
-
|
|
21554
|
-
|
|
21555
|
-
|
|
21556
|
-
|
|
21557
|
-
|
|
21558
|
-
|
|
21559
|
-
|
|
21560
|
-
|
|
21561
|
-
|
|
21562
|
-
|
|
21563
|
-
|
|
21564
|
-
|
|
21565
|
-
|
|
21566
|
-
|
|
21567
|
-
ctx.diagnostics.push({
|
|
21568
|
-
severity: "error",
|
|
21569
|
-
code: "invalid-effect-timeout",
|
|
21570
|
-
errorCode: "SC3032",
|
|
21571
|
-
docsUrl: "https://supacloud.dev/errors/SC3032",
|
|
21572
|
-
file,
|
|
21573
|
-
line: lineOf(timeoutExpr),
|
|
21574
|
-
message: `Route ${route.handler} effect.timeoutMs must be a positive integer.`
|
|
21575
|
-
});
|
|
21576
|
-
} else {
|
|
21577
|
-
effect.timeoutMs = timeoutMs;
|
|
21614
|
+
const timeoutExpr = getProp(effectObject, "timeoutMs");
|
|
21615
|
+
if (timeoutExpr !== undefined) {
|
|
21616
|
+
const timeoutMs = numberLiteralProp(effectObject, "timeoutMs");
|
|
21617
|
+
if (timeoutMs === undefined || timeoutMs <= 0 || !Number.isInteger(timeoutMs)) {
|
|
21618
|
+
ctx.diagnostics.push({
|
|
21619
|
+
severity: "error",
|
|
21620
|
+
code: "invalid-effect-timeout",
|
|
21621
|
+
errorCode: "SC3032",
|
|
21622
|
+
docsUrl: "https://supacloud.dev/errors/SC3032",
|
|
21623
|
+
file,
|
|
21624
|
+
line: lineOf(timeoutExpr),
|
|
21625
|
+
message: `Route ${route.handler} effect.timeoutMs must be a positive integer.`
|
|
21626
|
+
});
|
|
21627
|
+
} else {
|
|
21628
|
+
effect.timeoutMs = timeoutMs;
|
|
21629
|
+
}
|
|
21578
21630
|
}
|
|
21579
|
-
|
|
21580
|
-
|
|
21581
|
-
|
|
21582
|
-
|
|
21583
|
-
if (!dependenciesArray || dependenciesArray.elements.some((element) => !ts8.isStringLiteral(element) || element.text.trim().length === 0)) {
|
|
21584
|
-
ctx.diagnostics.push({
|
|
21585
|
-
severity: "error",
|
|
21586
|
-
code: "invalid-effect-dependencies",
|
|
21587
|
-
errorCode: "SC3033",
|
|
21588
|
-
docsUrl: "https://supacloud.dev/errors/SC3033",
|
|
21589
|
-
file,
|
|
21590
|
-
line: lineOf(dependenciesExpr),
|
|
21591
|
-
message: `Route ${route.handler} effect.dependencies must be a string array or a const initialized with one.`
|
|
21592
|
-
});
|
|
21593
|
-
} else {
|
|
21594
|
-
effect.dependencies = dependenciesArray.elements.map((element) => element.text);
|
|
21595
|
-
if (new Set(effect.dependencies).size !== effect.dependencies.length) {
|
|
21631
|
+
const dependenciesExpr = getProp(effectObject, "dependencies");
|
|
21632
|
+
if (dependenciesExpr !== undefined) {
|
|
21633
|
+
const dependenciesArray = await resolveStaticArrayLiteral(dependenciesExpr, ctx);
|
|
21634
|
+
if (!dependenciesArray || dependenciesArray.elements.some((element) => !ts8.isStringLiteral(element) || element.text.trim().length === 0)) {
|
|
21596
21635
|
ctx.diagnostics.push({
|
|
21597
21636
|
severity: "error",
|
|
21598
21637
|
code: "invalid-effect-dependencies",
|
|
@@ -21600,148 +21639,161 @@ async function parseController(input, ctx) {
|
|
|
21600
21639
|
docsUrl: "https://supacloud.dev/errors/SC3033",
|
|
21601
21640
|
file,
|
|
21602
21641
|
line: lineOf(dependenciesExpr),
|
|
21603
|
-
message: `Route ${route.handler} effect.dependencies must
|
|
21604
|
-
suggestion: "List each logical Effect dependency exactly once."
|
|
21642
|
+
message: `Route ${route.handler} effect.dependencies must be a string array or a const initialized with one.`
|
|
21605
21643
|
});
|
|
21606
|
-
}
|
|
21607
|
-
|
|
21608
|
-
|
|
21609
|
-
const errorsExpr = getProp(effectObject, "errors");
|
|
21610
|
-
if (errorsExpr !== undefined) {
|
|
21611
|
-
const errorsArray = await resolveStaticArrayLiteral(errorsExpr, ctx);
|
|
21612
|
-
if (!errorsArray) {
|
|
21613
|
-
ctx.diagnostics.push({
|
|
21614
|
-
severity: "error",
|
|
21615
|
-
code: "invalid-effect-errors",
|
|
21616
|
-
errorCode: "SC3034",
|
|
21617
|
-
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21618
|
-
file,
|
|
21619
|
-
line: lineOf(errorsExpr),
|
|
21620
|
-
message: `Route ${route.handler} effect.errors must be an object array or a const initialized with one.`
|
|
21621
|
-
});
|
|
21622
|
-
} else {
|
|
21623
|
-
const errors = [];
|
|
21624
|
-
for (const errorExpr of errorsArray.elements) {
|
|
21625
|
-
if (!ts8.isObjectLiteralExpression(errorExpr)) {
|
|
21644
|
+
} else {
|
|
21645
|
+
effect.dependencies = dependenciesArray.elements.map((element) => element.text);
|
|
21646
|
+
if (new Set(effect.dependencies).size !== effect.dependencies.length) {
|
|
21626
21647
|
ctx.diagnostics.push({
|
|
21627
21648
|
severity: "error",
|
|
21628
|
-
code: "invalid-effect-
|
|
21629
|
-
errorCode: "
|
|
21630
|
-
docsUrl: "https://supacloud.dev/errors/
|
|
21649
|
+
code: "invalid-effect-dependencies",
|
|
21650
|
+
errorCode: "SC3033",
|
|
21651
|
+
docsUrl: "https://supacloud.dev/errors/SC3033",
|
|
21631
21652
|
file,
|
|
21632
|
-
line: lineOf(
|
|
21633
|
-
message: `Route ${route.handler} effect.
|
|
21653
|
+
line: lineOf(dependenciesExpr),
|
|
21654
|
+
message: `Route ${route.handler} effect.dependencies must not contain duplicate service names.`,
|
|
21655
|
+
suggestion: "List each logical Effect dependency exactly once."
|
|
21634
21656
|
});
|
|
21635
|
-
continue;
|
|
21636
21657
|
}
|
|
21637
|
-
|
|
21638
|
-
|
|
21639
|
-
|
|
21640
|
-
|
|
21641
|
-
|
|
21642
|
-
|
|
21643
|
-
|
|
21644
|
-
|
|
21645
|
-
|
|
21646
|
-
|
|
21647
|
-
|
|
21648
|
-
|
|
21649
|
-
|
|
21650
|
-
|
|
21651
|
-
|
|
21652
|
-
|
|
21653
|
-
|
|
21654
|
-
|
|
21658
|
+
}
|
|
21659
|
+
}
|
|
21660
|
+
const errorsExpr = getProp(effectObject, "errors");
|
|
21661
|
+
if (errorsExpr !== undefined) {
|
|
21662
|
+
const errorsArray = await resolveStaticArrayLiteral(errorsExpr, ctx);
|
|
21663
|
+
if (!errorsArray) {
|
|
21664
|
+
ctx.diagnostics.push({
|
|
21665
|
+
severity: "error",
|
|
21666
|
+
code: "invalid-effect-errors",
|
|
21667
|
+
errorCode: "SC3034",
|
|
21668
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21669
|
+
file,
|
|
21670
|
+
line: lineOf(errorsExpr),
|
|
21671
|
+
message: `Route ${route.handler} effect.errors must be an object array or a const initialized with one.`
|
|
21672
|
+
});
|
|
21673
|
+
} else {
|
|
21674
|
+
const errors = [];
|
|
21675
|
+
for (const errorExpr of errorsArray.elements) {
|
|
21676
|
+
if (!ts8.isObjectLiteralExpression(errorExpr)) {
|
|
21677
|
+
ctx.diagnostics.push({
|
|
21678
|
+
severity: "error",
|
|
21679
|
+
code: "invalid-effect-errors",
|
|
21680
|
+
errorCode: "SC3034",
|
|
21681
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21682
|
+
file,
|
|
21683
|
+
line: lineOf(errorExpr),
|
|
21684
|
+
message: `Route ${route.handler} effect.errors entries must be object literals.`
|
|
21685
|
+
});
|
|
21686
|
+
continue;
|
|
21687
|
+
}
|
|
21688
|
+
const tag = stringLiteralProp(errorExpr, "tag");
|
|
21689
|
+
const code = stringLiteralProp(errorExpr, "code");
|
|
21690
|
+
const status = numberLiteralProp(errorExpr, "status");
|
|
21691
|
+
const message = stringLiteralProp(errorExpr, "message");
|
|
21692
|
+
const codeFromError = booleanLiteralProp(errorExpr, "codeFromError");
|
|
21693
|
+
const messageFromError = booleanLiteralProp(errorExpr, "messageFromError");
|
|
21694
|
+
const statusFromError = booleanLiteralProp(errorExpr, "statusFromError");
|
|
21695
|
+
const retryable = booleanLiteralProp(errorExpr, "retryable");
|
|
21696
|
+
const outcomeUnknown = booleanLiteralProp(errorExpr, "outcomeUnknown");
|
|
21697
|
+
if (invalidBooleanProp(errorExpr, "codeFromError") || invalidBooleanProp(errorExpr, "messageFromError") || invalidBooleanProp(errorExpr, "statusFromError") || invalidBooleanProp(errorExpr, "retryable") || invalidBooleanProp(errorExpr, "outcomeUnknown")) {
|
|
21698
|
+
ctx.diagnostics.push({
|
|
21699
|
+
severity: "error",
|
|
21700
|
+
code: "invalid-effect-errors",
|
|
21701
|
+
errorCode: "SC3034",
|
|
21702
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21703
|
+
file,
|
|
21704
|
+
line: lineOf(errorExpr),
|
|
21705
|
+
message: `Route ${route.handler} effect.errors instance flags must be boolean literals.`
|
|
21706
|
+
});
|
|
21707
|
+
continue;
|
|
21708
|
+
}
|
|
21709
|
+
if (!tag || !code || status === undefined || !Number.isInteger(status) || status < 400 || status > 599) {
|
|
21710
|
+
ctx.diagnostics.push({
|
|
21711
|
+
severity: "error",
|
|
21712
|
+
code: "invalid-effect-errors",
|
|
21713
|
+
errorCode: "SC3034",
|
|
21714
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21715
|
+
file,
|
|
21716
|
+
line: lineOf(errorExpr),
|
|
21717
|
+
message: `Route ${route.handler} effect.errors entries require tag, code and an HTTP status from 400 to 599.`
|
|
21718
|
+
});
|
|
21719
|
+
continue;
|
|
21720
|
+
}
|
|
21721
|
+
errors.push({
|
|
21722
|
+
tag,
|
|
21723
|
+
code,
|
|
21724
|
+
status,
|
|
21725
|
+
...message === undefined ? {} : { message },
|
|
21726
|
+
...codeFromError === undefined ? {} : { codeFromError },
|
|
21727
|
+
...messageFromError === undefined ? {} : { messageFromError },
|
|
21728
|
+
...statusFromError === undefined ? {} : { statusFromError },
|
|
21729
|
+
...retryable === undefined ? {} : { retryable },
|
|
21730
|
+
...outcomeUnknown === undefined ? {} : { outcomeUnknown }
|
|
21655
21731
|
});
|
|
21656
|
-
continue;
|
|
21657
21732
|
}
|
|
21658
|
-
|
|
21733
|
+
effect.errors = errors;
|
|
21734
|
+
if (new Set(errors.map((entry) => entry.tag)).size !== errors.length) {
|
|
21659
21735
|
ctx.diagnostics.push({
|
|
21660
21736
|
severity: "error",
|
|
21661
21737
|
code: "invalid-effect-errors",
|
|
21662
21738
|
errorCode: "SC3034",
|
|
21663
21739
|
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21664
21740
|
file,
|
|
21665
|
-
line: lineOf(
|
|
21666
|
-
message: `Route ${route.handler} effect.errors
|
|
21741
|
+
line: lineOf(errorsExpr),
|
|
21742
|
+
message: `Route ${route.handler} effect.errors must not contain duplicate tags.`,
|
|
21743
|
+
suggestion: "Map each tagged failure exactly once."
|
|
21667
21744
|
});
|
|
21668
|
-
continue;
|
|
21669
21745
|
}
|
|
21670
|
-
errors.push({
|
|
21671
|
-
tag,
|
|
21672
|
-
code,
|
|
21673
|
-
status,
|
|
21674
|
-
...message === undefined ? {} : { message },
|
|
21675
|
-
...codeFromError === undefined ? {} : { codeFromError },
|
|
21676
|
-
...messageFromError === undefined ? {} : { messageFromError },
|
|
21677
|
-
...statusFromError === undefined ? {} : { statusFromError },
|
|
21678
|
-
...retryable === undefined ? {} : { retryable },
|
|
21679
|
-
...outcomeUnknown === undefined ? {} : { outcomeUnknown }
|
|
21680
|
-
});
|
|
21681
|
-
}
|
|
21682
|
-
effect.errors = errors;
|
|
21683
|
-
if (new Set(errors.map((entry) => entry.tag)).size !== errors.length) {
|
|
21684
|
-
ctx.diagnostics.push({
|
|
21685
|
-
severity: "error",
|
|
21686
|
-
code: "invalid-effect-errors",
|
|
21687
|
-
errorCode: "SC3034",
|
|
21688
|
-
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21689
|
-
file,
|
|
21690
|
-
line: lineOf(errorsExpr),
|
|
21691
|
-
message: `Route ${route.handler} effect.errors must not contain duplicate tags.`,
|
|
21692
|
-
suggestion: "Map each tagged failure exactly once."
|
|
21693
|
-
});
|
|
21694
21746
|
}
|
|
21695
21747
|
}
|
|
21696
|
-
|
|
21697
|
-
|
|
21698
|
-
|
|
21699
|
-
|
|
21700
|
-
|
|
21701
|
-
|
|
21702
|
-
|
|
21703
|
-
|
|
21704
|
-
|
|
21705
|
-
|
|
21706
|
-
|
|
21707
|
-
|
|
21708
|
-
|
|
21709
|
-
|
|
21710
|
-
|
|
21711
|
-
|
|
21712
|
-
});
|
|
21713
|
-
}
|
|
21714
|
-
if (effectArguments) {
|
|
21715
|
-
const [, failureType, environmentType] = effectArguments;
|
|
21716
|
-
const failure = await effectFailureTags(failureType, method, ctx);
|
|
21717
|
-
if (ctx.effectOptions.requireTaggedErrorTypes && failure.invalid) {
|
|
21718
|
-
pushEffectDiagnostic(ctx, "effect-error-type", file, lineOf(method), `Route ${route.handler} must use never or a tagged failure union in Effect's error channel, not ${await effectTypeName(failureType, method, ctx)}.`, "Replace unknown/any with never or a union of objects carrying a literal _tag.");
|
|
21719
|
-
} else if (ctx.effectOptions.requireTaggedErrorTypes && effect.errors !== undefined && !failure.invalid) {
|
|
21720
|
-
const declaredTags = sortedUnique(effect.errors.map((entry) => entry.tag));
|
|
21721
|
-
const typedTags = sortedUnique(failure.tags);
|
|
21722
|
-
if (declaredTags.join("\x00") !== typedTags.join("\x00")) {
|
|
21723
|
-
pushEffectDiagnostic(ctx, "effect-error-mapping-type", file, lineOf(method), `Route ${route.handler} maps [${declaredTags.join(", ")}], but its Effect failure type declares [${typedTags.join(", ")}].`, "Make effect.errors and the Effect error type the same tagged union; do not hide unmapped failures.");
|
|
21724
|
-
}
|
|
21748
|
+
route.effect = effect;
|
|
21749
|
+
const effectArguments = await effectTypeArgumentsOf(returnType, ctx);
|
|
21750
|
+
const promiseReturn = Boolean(resultType && /\bPromise(?:<|\s)/.test(resultType));
|
|
21751
|
+
if (promiseReturn) {
|
|
21752
|
+
pushEffectDiagnostic(ctx, "effect-promise-return", file, lineOf(method), `Route ${route.handler} returns Promise instead of an Effect program.`, "Return Effect.Effect<Success, Failure, Requirements>; the Elysia boundary owns Promise interpretation.");
|
|
21753
|
+
} else if (!effectArguments) {
|
|
21754
|
+
ctx.diagnostics.push({
|
|
21755
|
+
severity: "error",
|
|
21756
|
+
code: "effect-return-type",
|
|
21757
|
+
errorCode: "SC3035",
|
|
21758
|
+
docsUrl: "https://supacloud.dev/errors/SC3035",
|
|
21759
|
+
file,
|
|
21760
|
+
line: lineOf(method),
|
|
21761
|
+
message: `Route ${route.handler} declares an Effect contract but returns ${resultType ?? "an unresolved type"}.`,
|
|
21762
|
+
suggestion: "Return Effect.Effect<Success, Failure, Requirements> from the handler."
|
|
21763
|
+
});
|
|
21725
21764
|
}
|
|
21726
|
-
|
|
21727
|
-
|
|
21728
|
-
|
|
21729
|
-
|
|
21730
|
-
|
|
21731
|
-
|
|
21732
|
-
const
|
|
21733
|
-
|
|
21734
|
-
|
|
21765
|
+
if (effectArguments) {
|
|
21766
|
+
const [, failureType, environmentType] = effectArguments;
|
|
21767
|
+
const failure = await effectFailureTags(failureType, method, ctx);
|
|
21768
|
+
if (ctx.effectOptions.requireTaggedErrorTypes && failure.invalid) {
|
|
21769
|
+
pushEffectDiagnostic(ctx, "effect-error-type", file, lineOf(method), `Route ${route.handler} must use never or a tagged failure union in Effect's error channel, not ${await effectTypeName(failureType, method, ctx)}.`, "Replace unknown/any with never or a union of objects carrying a literal _tag.");
|
|
21770
|
+
} else if (ctx.effectOptions.requireTaggedErrorTypes && effect.errors !== undefined && !failure.invalid) {
|
|
21771
|
+
const declaredTags = sortedUnique(effect.errors.map((entry) => entry.tag));
|
|
21772
|
+
const typedTags = sortedUnique(failure.tags);
|
|
21773
|
+
if (declaredTags.join("\x00") !== typedTags.join("\x00")) {
|
|
21774
|
+
pushEffectDiagnostic(ctx, "effect-error-mapping-type", file, lineOf(method), `Route ${route.handler} maps [${declaredTags.join(", ")}], but its Effect failure type declares [${typedTags.join(", ")}].`, "Make effect.errors and the Effect error type the same tagged union; do not hide unmapped failures.");
|
|
21735
21775
|
}
|
|
21736
21776
|
}
|
|
21777
|
+
const environment = await effectEnvironmentNames(environmentType, method, ctx);
|
|
21778
|
+
if (ctx.effectOptions.requireExactDependencyTypes && effect.dependencies !== undefined) {
|
|
21779
|
+
if (environment.invalid) {
|
|
21780
|
+
pushEffectDiagnostic(ctx, "effect-dependency-mismatch", file, lineOf(method), `Route ${route.handler} uses an Effect environment that cannot be represented by its explicit dependency list.`, "Use named service types or never in the third Effect type parameter.");
|
|
21781
|
+
} else {
|
|
21782
|
+
const declaredDependencies = sortedUnique(effect.dependencies);
|
|
21783
|
+
const typedDependencies = sortedUnique(environment.names);
|
|
21784
|
+
if (declaredDependencies.join("\x00") !== typedDependencies.join("\x00")) {
|
|
21785
|
+
pushEffectDiagnostic(ctx, "effect-dependency-mismatch", file, lineOf(method), `Route ${route.handler} declares dependencies [${declaredDependencies.join(", ")}], but its Effect environment is [${typedDependencies.join(", ")}].`, "Make effect.dependencies exactly match the third Effect type parameter.");
|
|
21786
|
+
}
|
|
21787
|
+
}
|
|
21788
|
+
}
|
|
21789
|
+
if (ctx.effectOptions.requireTimeoutForDependencies && (effect.dependencies?.length ?? 0) > 0 && effect.timeoutMs === undefined) {
|
|
21790
|
+
pushEffectDiagnostic(ctx, "effect-timeout-required", file, lineOf(method), `Route ${route.handler} declares external Effect dependencies without a timeout.`, "Add a positive effect.timeoutMs so external I/O cannot occupy a request indefinitely.");
|
|
21791
|
+
}
|
|
21737
21792
|
}
|
|
21738
|
-
if (ctx.effectOptions.
|
|
21739
|
-
|
|
21740
|
-
|
|
21741
|
-
|
|
21742
|
-
if (ctx.effectOptions.forbidDirectThrows && method.body) {
|
|
21743
|
-
for (const throwStatement of descendantsOfKind(method.body, ts8.isThrowStatement)) {
|
|
21744
|
-
pushEffectDiagnostic(ctx, "effect-direct-throw", file, lineOf(throwStatement), `Route ${route.handler} throws directly instead of representing failure in Effect.`, "Use Effect.fail with a tagged error value and map it in effect.errors.");
|
|
21793
|
+
if (ctx.effectOptions.forbidDirectThrows && method.body) {
|
|
21794
|
+
for (const throwStatement of descendantsOfKind(method.body, ts8.isThrowStatement)) {
|
|
21795
|
+
pushEffectDiagnostic(ctx, "effect-direct-throw", file, lineOf(throwStatement), `Route ${route.handler} throws directly instead of representing failure in Effect.`, "Use Effect.fail with a tagged error value and map it in effect.errors.");
|
|
21796
|
+
}
|
|
21745
21797
|
}
|
|
21746
21798
|
}
|
|
21747
21799
|
}
|
|
@@ -22102,6 +22154,23 @@ function checkedBranches(meta, ctx) {
|
|
|
22102
22154
|
}
|
|
22103
22155
|
return branches;
|
|
22104
22156
|
}
|
|
22157
|
+
function checkedBranchesFallback(meta, ctx) {
|
|
22158
|
+
const expression = getProp(meta, "branchesFallback");
|
|
22159
|
+
if (!expression)
|
|
22160
|
+
return;
|
|
22161
|
+
const value = stringLiteralProp(meta, "branchesFallback");
|
|
22162
|
+
if (value === "reject" || value === "command")
|
|
22163
|
+
return value;
|
|
22164
|
+
ctx.diagnostics.push({
|
|
22165
|
+
severity: "error",
|
|
22166
|
+
code: "invalid-command-branches-fallback",
|
|
22167
|
+
file: sourcePath(ctx.rootDir, expression.getSourceFile().fileName),
|
|
22168
|
+
line: lineOf(expression),
|
|
22169
|
+
message: "Command branchesFallback must be 'reject' or 'command'.",
|
|
22170
|
+
suggestion: "Use branchesFallback: 'command' only when the command handler owns the unmatched-input boundary."
|
|
22171
|
+
});
|
|
22172
|
+
return;
|
|
22173
|
+
}
|
|
22105
22174
|
async function classDeps(cls, ctx) {
|
|
22106
22175
|
const injectable = await parseInjectableOptions(cls, ctx);
|
|
22107
22176
|
const ctor = cls.members.find(ts8.isConstructorDeclaration);
|
|
@@ -23375,6 +23444,14 @@ function validateEffectPolicies(graph, options = {}) {
|
|
|
23375
23444
|
for (const controller of module.controllers) {
|
|
23376
23445
|
for (const route of controller.routes) {
|
|
23377
23446
|
const label = routeLabel(route, controller.path);
|
|
23447
|
+
if (route.effectMode === "simple") {
|
|
23448
|
+
const featureTransition = featureTransitionForRoute(module, controller, route);
|
|
23449
|
+
const commandRef = route.command ?? featureTransition?.command;
|
|
23450
|
+
if (commandRef !== undefined || !["GET", "HEAD", "OPTIONS"].includes(route.method)) {
|
|
23451
|
+
diagnostics.push(diagnostic("effect-contract-required", commandRef === undefined ? `Route ${label} cannot use effect: false for a write operation.` : `Route ${label} cannot use effect: false when it enters command ${commandRef}.`, controller.file, "Keep Effect enabled for writes and durable commands; effect: false is only for simple read routes."));
|
|
23452
|
+
}
|
|
23453
|
+
continue;
|
|
23454
|
+
}
|
|
23378
23455
|
if (options.requireRouteEffects && route.effect === undefined) {
|
|
23379
23456
|
diagnostics.push(diagnostic("effect-contract-required", `Route ${label} must declare an Effect contract.`, controller.file, 'Add effect: { dependencies: [...], errors: [...], retry: "none" } to the route options.'));
|
|
23380
23457
|
continue;
|
|
@@ -25274,8 +25351,8 @@ function createDeliveryPlan(graph, input, validation) {
|
|
|
25274
25351
|
target.execution = group;
|
|
25275
25352
|
}
|
|
25276
25353
|
}
|
|
25277
|
-
if ([...targets.values()].reduce((count, target) => count + (target.execution
|
|
25278
|
-
fail("delivery-execution-replica-limit", "A release supports at most
|
|
25354
|
+
if ([...targets.values()].reduce((count, target) => count + (target.execution ? (target.execution.replicas ?? 1) * (target.execution.shardCount ?? 1) : 1), 0) > 256) {
|
|
25355
|
+
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.");
|
|
25279
25356
|
}
|
|
25280
25357
|
const canonicalDiagnostics = [...new Map(diagnostics.map((item) => [JSON.stringify(item), item])).values()].sort((a, b) => compare2(JSON.stringify(a), JSON.stringify(b)));
|
|
25281
25358
|
if (canonicalDiagnostics.some((item) => item.severity === "error")) {
|
|
@@ -25366,6 +25443,7 @@ function validateExecutionManifest(plan, capabilities = {}) {
|
|
|
25366
25443
|
...command.adapter === null ? {} : { rpc: command.adapter },
|
|
25367
25444
|
...command.audit === null ? {} : { audit: command.audit },
|
|
25368
25445
|
...command.branches.length === 0 ? {} : { branches: command.branches },
|
|
25446
|
+
...command.branchesFallback === undefined ? {} : { branchesFallback: command.branchesFallback },
|
|
25369
25447
|
transaction: command.transaction,
|
|
25370
25448
|
idempotency: command.idempotency
|
|
25371
25449
|
}))
|
|
@@ -26432,10 +26510,10 @@ import { readFileSync as readFileSync4 } from "node:fs";
|
|
|
26432
26510
|
import { resolve as resolve19 } from "node:path";
|
|
26433
26511
|
|
|
26434
26512
|
// src/migration-app-version.ts
|
|
26435
|
-
var MIGRATION_APP_VERSION = "0.
|
|
26513
|
+
var MIGRATION_APP_VERSION = "0.39.1";
|
|
26436
26514
|
|
|
26437
26515
|
// src/migration-elysia-version.ts
|
|
26438
|
-
var MIGRATION_ELYSIA_VERSION = "0.
|
|
26516
|
+
var MIGRATION_ELYSIA_VERSION = "0.42.0";
|
|
26439
26517
|
|
|
26440
26518
|
// src/migration-policy.ts
|
|
26441
26519
|
var cachedCompilerVersion;
|