@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/index.js
CHANGED
|
@@ -381,6 +381,7 @@ var init_di_resolution = __esm(() => {
|
|
|
381
381
|
"conflicting-injection-flags",
|
|
382
382
|
"cross-module-scoped-dependency",
|
|
383
383
|
"mixed-multi-provider-scopes",
|
|
384
|
+
"effect-contract-required",
|
|
384
385
|
"dynamic-aspect-reference",
|
|
385
386
|
"invalid-aspect-reference",
|
|
386
387
|
"aspect-operation-mismatch"
|
|
@@ -1088,7 +1089,13 @@ function validateGraph(graph, options = false) {
|
|
|
1088
1089
|
if (command.rpc && command.branches) {
|
|
1089
1090
|
error("command-rpc-branches-conflict", `Command ${command.name} cannot declare both rpc and branches.`, module.file, module.line);
|
|
1090
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
|
+
}
|
|
1091
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
|
+
}
|
|
1092
1099
|
for (const branch of branchAdapters) {
|
|
1093
1100
|
if (typeof options !== "object" || !Object.hasOwn(options.commandCapabilities?.rpc ?? {}, branch.rpc)) {
|
|
1094
1101
|
error("command-rpc-unavailable", `Command ${command.name} requires configured RPC adapter '${branch.rpc}'.`, module.file, module.line);
|
|
@@ -1118,7 +1125,7 @@ function validateGraph(graph, options = false) {
|
|
|
1118
1125
|
error("command-transaction-unsupported", `Command ${command.name} branch '${branch.when}' declares transaction, but transaction support is unavailable.`, module.file, module.line);
|
|
1119
1126
|
}
|
|
1120
1127
|
}
|
|
1121
|
-
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)) {
|
|
1122
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.");
|
|
1123
1130
|
}
|
|
1124
1131
|
if (rpcCaps?.boundary === "external" && (command.transaction === "required" || rpcCaps.transaction === true)) {
|
|
@@ -1671,6 +1678,7 @@ function buildContractManifest(graph, artifacts) {
|
|
|
1671
1678
|
module: module.name,
|
|
1672
1679
|
...command.permission === undefined ? {} : { permission: command.permission },
|
|
1673
1680
|
...command.rpc === undefined ? {} : { rpc: command.rpc },
|
|
1681
|
+
...command.branchesFallback === undefined ? {} : { branchesFallback: command.branchesFallback },
|
|
1674
1682
|
...command.branches === undefined ? {} : {
|
|
1675
1683
|
branches: command.branches.map((branch) => ({
|
|
1676
1684
|
when: branch.when,
|
|
@@ -2402,6 +2410,7 @@ function renderApplication(graph, options) {
|
|
|
2402
2410
|
permission: command.permission ?? null,
|
|
2403
2411
|
rpc: command.rpc ?? null,
|
|
2404
2412
|
branches: command.branches ?? [],
|
|
2413
|
+
branchesFallback: command.branchesFallback ?? "reject",
|
|
2405
2414
|
transaction: command.transaction ?? null,
|
|
2406
2415
|
audit: command.audit ?? null,
|
|
2407
2416
|
idempotency: command.idempotency ?? null
|
|
@@ -2452,6 +2461,7 @@ function renderApplication(graph, options) {
|
|
|
2452
2461
|
executor: route.command === undefined ? "route" : "command",
|
|
2453
2462
|
...command === undefined ? {} : { commandBoundary: route.command === undefined ? "handler" : "route" },
|
|
2454
2463
|
...route.effect === undefined ? {} : { effect: route.effect },
|
|
2464
|
+
...route.effectMode === undefined ? {} : { effectMode: route.effectMode },
|
|
2455
2465
|
invoker: isPipeline ? "pipeline" : "direct",
|
|
2456
2466
|
stages: operationPlans.find((plan) => plan.module === module.name && plan.kind === "route" && plan.name === `${route.method} ${joinRoutePaths(controller.path, route.path)}`).stages,
|
|
2457
2467
|
...commandRef === undefined ? {} : { command: command?.name ?? commandRef },
|
|
@@ -2479,6 +2489,7 @@ function renderApplication(graph, options) {
|
|
|
2479
2489
|
transaction: command.transaction,
|
|
2480
2490
|
idempotency: command.idempotency,
|
|
2481
2491
|
branches: command.branches ?? [],
|
|
2492
|
+
branchesFallback: command.branchesFallback ?? "reject",
|
|
2482
2493
|
...command.tenantScope === undefined ? {} : { tenantScope: command.tenantScope },
|
|
2483
2494
|
requiredCapabilities: {
|
|
2484
2495
|
permission: true,
|
|
@@ -2831,6 +2842,8 @@ class ModuleGenerator {
|
|
|
2831
2842
|
if (route.effect !== undefined) {
|
|
2832
2843
|
fields.push(`effect: ${JSON.stringify(route.effect)}`);
|
|
2833
2844
|
}
|
|
2845
|
+
if (route.effectMode !== undefined)
|
|
2846
|
+
fields.push(`effectMode: ${JSON.stringify(route.effectMode)}`);
|
|
2834
2847
|
if (idempotency !== undefined) {
|
|
2835
2848
|
fields.push(`idempotency: ${JSON.stringify(idempotency)}`);
|
|
2836
2849
|
}
|
|
@@ -2969,6 +2982,7 @@ ${indent(item, 2)}`).join(",")}
|
|
|
2969
2982
|
`idempotency: ${JSON.stringify(command.idempotency)}`,
|
|
2970
2983
|
...command.tenantScope ? [`tenantScope: ${JSON.stringify(command.tenantScope)}`] : [],
|
|
2971
2984
|
...command.rpc ? [`rpc: ${JSON.stringify(command.rpc)}`] : [],
|
|
2985
|
+
...command.branchesFallback ? [`branchesFallback: ${JSON.stringify(command.branchesFallback)}`] : [],
|
|
2972
2986
|
...command.branches && command.branches.length > 0 ? [`branches: ${JSON.stringify(command.branches)}`] : [],
|
|
2973
2987
|
...command.standalone ? ["standalone: true"] : [],
|
|
2974
2988
|
...command.aspects && command.aspects.length > 0 ? [`aspects: ${this.renderAspects(command.aspects)}`, `aspectPipeline: ${this.renderAspectPipeline(command.aspects)}`] : []
|
|
@@ -4680,6 +4694,8 @@ var HEADER = "// GENERATED BY @supacloud/compiler — do not edit", INTERFACES =
|
|
|
4680
4694
|
maxAttempts?: number;
|
|
4681
4695
|
timeoutMs?: number;
|
|
4682
4696
|
};
|
|
4697
|
+
/** Simple route profile; no Effect interpreter or error mapping is required. */
|
|
4698
|
+
effectMode?: "simple";
|
|
4683
4699
|
/** Compile-time ownership and transport classification for the route boundary. */
|
|
4684
4700
|
contract?: {
|
|
4685
4701
|
body?: "framework" | "domain";
|
|
@@ -4726,6 +4742,7 @@ var HEADER = "// GENERATED BY @supacloud/compiler — do not edit", INTERFACES =
|
|
|
4726
4742
|
export interface CompiledCommand {
|
|
4727
4743
|
rpc?: string;
|
|
4728
4744
|
branches?: readonly CompiledCommandBranch[];
|
|
4745
|
+
branchesFallback?: "reject" | "command";
|
|
4729
4746
|
className: string;
|
|
4730
4747
|
name: string;
|
|
4731
4748
|
permission: string;
|
|
@@ -4858,7 +4875,7 @@ async function compiledAspectStep(context: CompiledAspectContext, handler: () =>
|
|
|
4858
4875
|
}
|
|
4859
4876
|
called = true;
|
|
4860
4877
|
pending = Promise.resolve().then(handler).then(result => {
|
|
4861
|
-
if (strict) freezeCompiledAspectData(result);
|
|
4878
|
+
if (strict && !(result instanceof Response)) freezeCompiledAspectData(result);
|
|
4862
4879
|
value = result;
|
|
4863
4880
|
return result;
|
|
4864
4881
|
});
|
|
@@ -15624,23 +15641,49 @@ var init_value2 = __esm(() => {
|
|
|
15624
15641
|
});
|
|
15625
15642
|
|
|
15626
15643
|
// node_modules/@supacloud/delivery/src/worker-execution.ts
|
|
15644
|
+
function workerShardCount(group) {
|
|
15645
|
+
return group.shardCount ?? 1;
|
|
15646
|
+
}
|
|
15647
|
+
function shardSuffix(shard) {
|
|
15648
|
+
if (!Number.isSafeInteger(shard) || shard < 0 || shard >= 64) {
|
|
15649
|
+
throw new Error("WORKER_SHARD_INVALID");
|
|
15650
|
+
}
|
|
15651
|
+
return `s${shard.toString(36).padStart(2, "0")}`;
|
|
15652
|
+
}
|
|
15653
|
+
function workerShardQueue(group, shard) {
|
|
15654
|
+
const count = workerShardCount(group);
|
|
15655
|
+
if (!Number.isSafeInteger(shard) || shard < 0 || shard >= count) {
|
|
15656
|
+
throw new Error("WORKER_SHARD_INVALID");
|
|
15657
|
+
}
|
|
15658
|
+
if (count === 1)
|
|
15659
|
+
return group.queue;
|
|
15660
|
+
const queue = `${group.queue}_${shardSuffix(shard)}`;
|
|
15661
|
+
if (!/^scw_[a-z0-9_]{1,40}$/.test(queue))
|
|
15662
|
+
throw new Error("WORKER_QUEUE_INVALID");
|
|
15663
|
+
return queue;
|
|
15664
|
+
}
|
|
15627
15665
|
function parseWorkerExecutionGroup(value) {
|
|
15628
15666
|
if (!Check2(WorkerExecutionGroupSchema, value) || value.replicas > value.maxReplicas || !value.queue.endsWith(`_v${value.definitionVersion}`) || value.lifecycle.visibilityTimeoutSeconds < value.lifecycle.executionTimeoutSeconds + value.lifecycle.shutdownGraceSeconds + 30) {
|
|
15629
15667
|
throw new Error("WORKER_EXECUTION_INVALID");
|
|
15630
15668
|
}
|
|
15631
|
-
|
|
15669
|
+
const group = structuredClone(value);
|
|
15670
|
+
for (let shard = 0;shard < workerShardCount(group); shard += 1)
|
|
15671
|
+
workerShardQueue(group, shard);
|
|
15672
|
+
return group;
|
|
15632
15673
|
}
|
|
15633
15674
|
function validateWorkerExecution(groups) {
|
|
15634
15675
|
const names = new Set, targets = new Set, queues = new Set, routes = new Set;
|
|
15635
15676
|
for (const candidate of groups) {
|
|
15636
15677
|
const group = parseWorkerExecutionGroup(candidate);
|
|
15637
15678
|
const route = `${group.taskKey}:${group.definitionVersion}`;
|
|
15638
|
-
|
|
15679
|
+
const physicalQueues = Array.from({ length: workerShardCount(group) }, (_, shard) => workerShardQueue(group, shard));
|
|
15680
|
+
if (names.has(group.name) || targets.has(group.target) || queues.has(group.queue) || physicalQueues.some((queue) => queues.has(queue)) || routes.has(route)) {
|
|
15639
15681
|
throw new Error("WORKER_EXECUTION_CONFLICT");
|
|
15640
15682
|
}
|
|
15641
15683
|
names.add(group.name);
|
|
15642
15684
|
targets.add(group.target);
|
|
15643
15685
|
queues.add(group.queue);
|
|
15686
|
+
physicalQueues.forEach((queue) => queues.add(queue));
|
|
15644
15687
|
routes.add(route);
|
|
15645
15688
|
}
|
|
15646
15689
|
}
|
|
@@ -15664,6 +15707,7 @@ var init_worker_execution = __esm(() => {
|
|
|
15664
15707
|
definitionVersion: String2({ pattern: "^[1-9][0-9]{0,8}$" }),
|
|
15665
15708
|
replicas: positive(16),
|
|
15666
15709
|
maxReplicas: positive(16),
|
|
15710
|
+
shardCount: Optional(positive(64)),
|
|
15667
15711
|
concurrencyPerReplica: positive(32),
|
|
15668
15712
|
resources: ComputeResourcesSchema,
|
|
15669
15713
|
database: _Object_({
|
|
@@ -16073,7 +16117,7 @@ function parseDeliveryBuildManifest(value) {
|
|
|
16073
16117
|
if (!Check2(DeliveryBuildManifestSchema, value))
|
|
16074
16118
|
throw new Error("Invalid delivery build manifest.");
|
|
16075
16119
|
validateWorkerExecution(value.plan.targets.flatMap((target) => target.execution ? [target.execution] : []));
|
|
16076
|
-
if (value.plan.targets.reduce((count, target) => count + (target.execution
|
|
16120
|
+
if (value.plan.targets.reduce((count, target) => count + (target.execution ? (target.execution.replicas ?? 1) * workerShardCount(target.execution) : 1), 0) > 256) {
|
|
16077
16121
|
throw new Error("Invalid delivery replica count.");
|
|
16078
16122
|
}
|
|
16079
16123
|
for (const target of value.plan.targets) {
|
|
@@ -19832,6 +19876,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths, effectOptio
|
|
|
19832
19876
|
const permission = stringLiteralProp(meta, "permission");
|
|
19833
19877
|
const rpc = checkedRpc(meta, ctx);
|
|
19834
19878
|
const branches = checkedBranches(meta, ctx);
|
|
19879
|
+
const branchesFallback = checkedBranchesFallback(meta, ctx);
|
|
19835
19880
|
const audit = stringLiteralProp(meta, "audit");
|
|
19836
19881
|
const standaloneName = stringLiteralProp(meta, "name") ?? classInfo.decl.name?.text ?? name;
|
|
19837
19882
|
const standaloneUses = await parseInfraResourceUseRefs(infraResourceProp(meta, "uses", ctx), ctx, `command ${standaloneName}`, sourcePath(ctx.rootDir, classInfo.file), lineOf(classInfo.decl));
|
|
@@ -19842,6 +19887,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths, effectOptio
|
|
|
19842
19887
|
...permission === undefined ? {} : { permission },
|
|
19843
19888
|
...rpc === undefined ? {} : { rpc },
|
|
19844
19889
|
...branches === undefined ? {} : { branches },
|
|
19890
|
+
...branchesFallback === undefined ? {} : { branchesFallback },
|
|
19845
19891
|
transaction: checkedCommandMode(meta, "transaction", ctx, classInfo.decl.name?.text ?? name) ?? "none",
|
|
19846
19892
|
...audit === undefined ? {} : { audit },
|
|
19847
19893
|
idempotency: checkedCommandMode(meta, "idempotency", ctx, classInfo.decl.name?.text ?? name) ?? "none",
|
|
@@ -20216,6 +20262,7 @@ async function parseModule(candidate, nameByNode, ctx) {
|
|
|
20216
20262
|
const permission = stringLiteralProp(meta, "permission");
|
|
20217
20263
|
const rpc = checkedRpc(meta, ctx);
|
|
20218
20264
|
const branches = checkedBranches(meta, ctx);
|
|
20265
|
+
const branchesFallback = checkedBranchesFallback(meta, ctx);
|
|
20219
20266
|
const audit = stringLiteralProp(meta, "audit");
|
|
20220
20267
|
const commandName = stringLiteralProp(meta, "name") ?? cls.name?.text ?? "<anonymous>";
|
|
20221
20268
|
const uses = await parseInfraResourceUseRefs(infraResourceProp(meta, "uses", ctx), ctx, `command ${commandName}`, sourcePath(ctx.rootDir, cls.getSourceFile().fileName), lineOf(cls));
|
|
@@ -20227,6 +20274,7 @@ async function parseModule(candidate, nameByNode, ctx) {
|
|
|
20227
20274
|
...permission === undefined ? {} : { permission },
|
|
20228
20275
|
...rpc === undefined ? {} : { rpc },
|
|
20229
20276
|
...branches === undefined ? {} : { branches },
|
|
20277
|
+
...branchesFallback === undefined ? {} : { branchesFallback },
|
|
20230
20278
|
transaction: checkedCommandMode(meta, "transaction", ctx, cls.name?.text ?? "<anonymous>") ?? "none",
|
|
20231
20279
|
...audit === undefined ? {} : { audit },
|
|
20232
20280
|
idempotency: checkedCommandMode(meta, "idempotency", ctx, cls.name?.text ?? "<anonymous>") ?? "none",
|
|
@@ -21447,111 +21495,102 @@ async function parseController(input, ctx) {
|
|
|
21447
21495
|
}
|
|
21448
21496
|
const effectExpr = getProp(optionsObject, "effect");
|
|
21449
21497
|
if (effectExpr) {
|
|
21450
|
-
|
|
21451
|
-
|
|
21452
|
-
ctx.diagnostics.push({
|
|
21453
|
-
severity: "error",
|
|
21454
|
-
code: "invalid-effect-contract",
|
|
21455
|
-
errorCode: "SC3030",
|
|
21456
|
-
docsUrl: "https://supacloud.dev/errors/SC3030",
|
|
21457
|
-
file,
|
|
21458
|
-
line: lineOf(effectExpr),
|
|
21459
|
-
message: `Route ${route.handler} effect must be a statically resolvable object literal.`,
|
|
21460
|
-
suggestion: 'Use effect: { required: true, errors: [...], dependencies: [...], retry: "none" }.'
|
|
21461
|
-
});
|
|
21498
|
+
if (effectExpr.kind === ts8.SyntaxKind.FalseKeyword) {
|
|
21499
|
+
route.effectMode = "simple";
|
|
21462
21500
|
} else {
|
|
21463
|
-
const
|
|
21464
|
-
|
|
21465
|
-
if (requiredExpr !== undefined && booleanProp(effectObject, "required") !== true) {
|
|
21501
|
+
const effectObject = await resolveStaticObjectLiteral(effectExpr, ctx);
|
|
21502
|
+
if (!effectObject) {
|
|
21466
21503
|
ctx.diagnostics.push({
|
|
21467
21504
|
severity: "error",
|
|
21468
21505
|
code: "invalid-effect-contract",
|
|
21469
21506
|
errorCode: "SC3030",
|
|
21470
21507
|
docsUrl: "https://supacloud.dev/errors/SC3030",
|
|
21471
21508
|
file,
|
|
21472
|
-
line: lineOf(
|
|
21473
|
-
message: `Route ${route.handler} effect
|
|
21509
|
+
line: lineOf(effectExpr),
|
|
21510
|
+
message: `Route ${route.handler} effect must be a statically resolvable object literal.`,
|
|
21511
|
+
suggestion: 'Use effect: { required: true, errors: [...], dependencies: [...], retry: "none" }.'
|
|
21474
21512
|
});
|
|
21475
|
-
}
|
|
21476
|
-
|
|
21477
|
-
|
|
21478
|
-
if (
|
|
21513
|
+
} else {
|
|
21514
|
+
const effect = { required: true };
|
|
21515
|
+
const requiredExpr = getProp(effectObject, "required");
|
|
21516
|
+
if (requiredExpr !== undefined && booleanProp(effectObject, "required") !== true) {
|
|
21479
21517
|
ctx.diagnostics.push({
|
|
21480
21518
|
severity: "error",
|
|
21481
|
-
code: "invalid-effect-
|
|
21482
|
-
errorCode: "
|
|
21483
|
-
docsUrl: "https://supacloud.dev/errors/
|
|
21519
|
+
code: "invalid-effect-contract",
|
|
21520
|
+
errorCode: "SC3030",
|
|
21521
|
+
docsUrl: "https://supacloud.dev/errors/SC3030",
|
|
21484
21522
|
file,
|
|
21485
|
-
line: lineOf(
|
|
21486
|
-
message: `Route ${route.handler} effect.
|
|
21523
|
+
line: lineOf(requiredExpr),
|
|
21524
|
+
message: `Route ${route.handler} effect.required must be the literal true.`
|
|
21487
21525
|
});
|
|
21488
|
-
} else {
|
|
21489
|
-
effect.retry = retry;
|
|
21490
21526
|
}
|
|
21491
|
-
|
|
21492
|
-
|
|
21493
|
-
|
|
21494
|
-
|
|
21495
|
-
|
|
21496
|
-
|
|
21497
|
-
|
|
21527
|
+
const retry = stringLiteralProp(effectObject, "retry");
|
|
21528
|
+
if (retry !== undefined) {
|
|
21529
|
+
if (retry !== "none" && retry !== "explicit") {
|
|
21530
|
+
ctx.diagnostics.push({
|
|
21531
|
+
severity: "error",
|
|
21532
|
+
code: "invalid-effect-retry",
|
|
21533
|
+
errorCode: "SC3031",
|
|
21534
|
+
docsUrl: "https://supacloud.dev/errors/SC3031",
|
|
21535
|
+
file,
|
|
21536
|
+
line: lineOf(getProp(effectObject, "retry")),
|
|
21537
|
+
message: `Route ${route.handler} effect.retry must be "none" or "explicit".`
|
|
21538
|
+
});
|
|
21539
|
+
} else {
|
|
21540
|
+
effect.retry = retry;
|
|
21541
|
+
}
|
|
21542
|
+
} else if (ctx.effectOptions.requireRetryPolicy === false) {
|
|
21543
|
+
effect.retry = "none";
|
|
21544
|
+
}
|
|
21545
|
+
const maxAttemptsExpr = getProp(effectObject, "maxAttempts");
|
|
21546
|
+
if (maxAttemptsExpr !== undefined) {
|
|
21547
|
+
const maxAttempts = numberLiteralProp(effectObject, "maxAttempts");
|
|
21548
|
+
if (maxAttempts === undefined || maxAttempts < 1 || !Number.isInteger(maxAttempts)) {
|
|
21549
|
+
ctx.diagnostics.push({
|
|
21550
|
+
severity: "error",
|
|
21551
|
+
code: "invalid-effect-retry",
|
|
21552
|
+
errorCode: "SC3031",
|
|
21553
|
+
docsUrl: "https://supacloud.dev/errors/SC3031",
|
|
21554
|
+
file,
|
|
21555
|
+
line: lineOf(maxAttemptsExpr),
|
|
21556
|
+
message: `Route ${route.handler} effect.maxAttempts must be a positive integer.`
|
|
21557
|
+
});
|
|
21558
|
+
} else {
|
|
21559
|
+
effect.maxAttempts = maxAttempts;
|
|
21560
|
+
}
|
|
21561
|
+
}
|
|
21562
|
+
if (effect.retry === "explicit" && effect.maxAttempts === undefined) {
|
|
21498
21563
|
ctx.diagnostics.push({
|
|
21499
21564
|
severity: "error",
|
|
21500
21565
|
code: "invalid-effect-retry",
|
|
21501
21566
|
errorCode: "SC3031",
|
|
21502
21567
|
docsUrl: "https://supacloud.dev/errors/SC3031",
|
|
21503
21568
|
file,
|
|
21504
|
-
line: lineOf(
|
|
21505
|
-
message: `Route ${route.handler} effect.maxAttempts
|
|
21569
|
+
line: lineOf(effectExpr),
|
|
21570
|
+
message: `Route ${route.handler} effect.maxAttempts is required when retry is "explicit".`
|
|
21506
21571
|
});
|
|
21507
|
-
} else {
|
|
21508
|
-
effect.maxAttempts = maxAttempts;
|
|
21509
21572
|
}
|
|
21510
|
-
|
|
21511
|
-
|
|
21512
|
-
|
|
21513
|
-
|
|
21514
|
-
|
|
21515
|
-
|
|
21516
|
-
|
|
21517
|
-
|
|
21518
|
-
|
|
21519
|
-
|
|
21520
|
-
|
|
21521
|
-
|
|
21522
|
-
|
|
21523
|
-
|
|
21524
|
-
|
|
21525
|
-
|
|
21526
|
-
ctx.diagnostics.push({
|
|
21527
|
-
severity: "error",
|
|
21528
|
-
code: "invalid-effect-timeout",
|
|
21529
|
-
errorCode: "SC3032",
|
|
21530
|
-
docsUrl: "https://supacloud.dev/errors/SC3032",
|
|
21531
|
-
file,
|
|
21532
|
-
line: lineOf(timeoutExpr),
|
|
21533
|
-
message: `Route ${route.handler} effect.timeoutMs must be a positive integer.`
|
|
21534
|
-
});
|
|
21535
|
-
} else {
|
|
21536
|
-
effect.timeoutMs = timeoutMs;
|
|
21573
|
+
const timeoutExpr = getProp(effectObject, "timeoutMs");
|
|
21574
|
+
if (timeoutExpr !== undefined) {
|
|
21575
|
+
const timeoutMs = numberLiteralProp(effectObject, "timeoutMs");
|
|
21576
|
+
if (timeoutMs === undefined || timeoutMs <= 0 || !Number.isInteger(timeoutMs)) {
|
|
21577
|
+
ctx.diagnostics.push({
|
|
21578
|
+
severity: "error",
|
|
21579
|
+
code: "invalid-effect-timeout",
|
|
21580
|
+
errorCode: "SC3032",
|
|
21581
|
+
docsUrl: "https://supacloud.dev/errors/SC3032",
|
|
21582
|
+
file,
|
|
21583
|
+
line: lineOf(timeoutExpr),
|
|
21584
|
+
message: `Route ${route.handler} effect.timeoutMs must be a positive integer.`
|
|
21585
|
+
});
|
|
21586
|
+
} else {
|
|
21587
|
+
effect.timeoutMs = timeoutMs;
|
|
21588
|
+
}
|
|
21537
21589
|
}
|
|
21538
|
-
|
|
21539
|
-
|
|
21540
|
-
|
|
21541
|
-
|
|
21542
|
-
if (!dependenciesArray || dependenciesArray.elements.some((element) => !ts8.isStringLiteral(element) || element.text.trim().length === 0)) {
|
|
21543
|
-
ctx.diagnostics.push({
|
|
21544
|
-
severity: "error",
|
|
21545
|
-
code: "invalid-effect-dependencies",
|
|
21546
|
-
errorCode: "SC3033",
|
|
21547
|
-
docsUrl: "https://supacloud.dev/errors/SC3033",
|
|
21548
|
-
file,
|
|
21549
|
-
line: lineOf(dependenciesExpr),
|
|
21550
|
-
message: `Route ${route.handler} effect.dependencies must be a string array or a const initialized with one.`
|
|
21551
|
-
});
|
|
21552
|
-
} else {
|
|
21553
|
-
effect.dependencies = dependenciesArray.elements.map((element) => element.text);
|
|
21554
|
-
if (new Set(effect.dependencies).size !== effect.dependencies.length) {
|
|
21590
|
+
const dependenciesExpr = getProp(effectObject, "dependencies");
|
|
21591
|
+
if (dependenciesExpr !== undefined) {
|
|
21592
|
+
const dependenciesArray = await resolveStaticArrayLiteral(dependenciesExpr, ctx);
|
|
21593
|
+
if (!dependenciesArray || dependenciesArray.elements.some((element) => !ts8.isStringLiteral(element) || element.text.trim().length === 0)) {
|
|
21555
21594
|
ctx.diagnostics.push({
|
|
21556
21595
|
severity: "error",
|
|
21557
21596
|
code: "invalid-effect-dependencies",
|
|
@@ -21559,148 +21598,161 @@ async function parseController(input, ctx) {
|
|
|
21559
21598
|
docsUrl: "https://supacloud.dev/errors/SC3033",
|
|
21560
21599
|
file,
|
|
21561
21600
|
line: lineOf(dependenciesExpr),
|
|
21562
|
-
message: `Route ${route.handler} effect.dependencies must
|
|
21563
|
-
suggestion: "List each logical Effect dependency exactly once."
|
|
21601
|
+
message: `Route ${route.handler} effect.dependencies must be a string array or a const initialized with one.`
|
|
21564
21602
|
});
|
|
21565
|
-
}
|
|
21566
|
-
|
|
21567
|
-
|
|
21568
|
-
const errorsExpr = getProp(effectObject, "errors");
|
|
21569
|
-
if (errorsExpr !== undefined) {
|
|
21570
|
-
const errorsArray = await resolveStaticArrayLiteral(errorsExpr, ctx);
|
|
21571
|
-
if (!errorsArray) {
|
|
21572
|
-
ctx.diagnostics.push({
|
|
21573
|
-
severity: "error",
|
|
21574
|
-
code: "invalid-effect-errors",
|
|
21575
|
-
errorCode: "SC3034",
|
|
21576
|
-
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21577
|
-
file,
|
|
21578
|
-
line: lineOf(errorsExpr),
|
|
21579
|
-
message: `Route ${route.handler} effect.errors must be an object array or a const initialized with one.`
|
|
21580
|
-
});
|
|
21581
|
-
} else {
|
|
21582
|
-
const errors = [];
|
|
21583
|
-
for (const errorExpr of errorsArray.elements) {
|
|
21584
|
-
if (!ts8.isObjectLiteralExpression(errorExpr)) {
|
|
21603
|
+
} else {
|
|
21604
|
+
effect.dependencies = dependenciesArray.elements.map((element) => element.text);
|
|
21605
|
+
if (new Set(effect.dependencies).size !== effect.dependencies.length) {
|
|
21585
21606
|
ctx.diagnostics.push({
|
|
21586
21607
|
severity: "error",
|
|
21587
|
-
code: "invalid-effect-
|
|
21588
|
-
errorCode: "
|
|
21589
|
-
docsUrl: "https://supacloud.dev/errors/
|
|
21608
|
+
code: "invalid-effect-dependencies",
|
|
21609
|
+
errorCode: "SC3033",
|
|
21610
|
+
docsUrl: "https://supacloud.dev/errors/SC3033",
|
|
21590
21611
|
file,
|
|
21591
|
-
line: lineOf(
|
|
21592
|
-
message: `Route ${route.handler} effect.
|
|
21612
|
+
line: lineOf(dependenciesExpr),
|
|
21613
|
+
message: `Route ${route.handler} effect.dependencies must not contain duplicate service names.`,
|
|
21614
|
+
suggestion: "List each logical Effect dependency exactly once."
|
|
21593
21615
|
});
|
|
21594
|
-
continue;
|
|
21595
21616
|
}
|
|
21596
|
-
|
|
21597
|
-
|
|
21598
|
-
|
|
21599
|
-
|
|
21600
|
-
|
|
21601
|
-
|
|
21602
|
-
|
|
21603
|
-
|
|
21604
|
-
|
|
21605
|
-
|
|
21606
|
-
|
|
21607
|
-
|
|
21608
|
-
|
|
21609
|
-
|
|
21610
|
-
|
|
21611
|
-
|
|
21612
|
-
|
|
21613
|
-
|
|
21617
|
+
}
|
|
21618
|
+
}
|
|
21619
|
+
const errorsExpr = getProp(effectObject, "errors");
|
|
21620
|
+
if (errorsExpr !== undefined) {
|
|
21621
|
+
const errorsArray = await resolveStaticArrayLiteral(errorsExpr, ctx);
|
|
21622
|
+
if (!errorsArray) {
|
|
21623
|
+
ctx.diagnostics.push({
|
|
21624
|
+
severity: "error",
|
|
21625
|
+
code: "invalid-effect-errors",
|
|
21626
|
+
errorCode: "SC3034",
|
|
21627
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21628
|
+
file,
|
|
21629
|
+
line: lineOf(errorsExpr),
|
|
21630
|
+
message: `Route ${route.handler} effect.errors must be an object array or a const initialized with one.`
|
|
21631
|
+
});
|
|
21632
|
+
} else {
|
|
21633
|
+
const errors = [];
|
|
21634
|
+
for (const errorExpr of errorsArray.elements) {
|
|
21635
|
+
if (!ts8.isObjectLiteralExpression(errorExpr)) {
|
|
21636
|
+
ctx.diagnostics.push({
|
|
21637
|
+
severity: "error",
|
|
21638
|
+
code: "invalid-effect-errors",
|
|
21639
|
+
errorCode: "SC3034",
|
|
21640
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21641
|
+
file,
|
|
21642
|
+
line: lineOf(errorExpr),
|
|
21643
|
+
message: `Route ${route.handler} effect.errors entries must be object literals.`
|
|
21644
|
+
});
|
|
21645
|
+
continue;
|
|
21646
|
+
}
|
|
21647
|
+
const tag = stringLiteralProp(errorExpr, "tag");
|
|
21648
|
+
const code = stringLiteralProp(errorExpr, "code");
|
|
21649
|
+
const status = numberLiteralProp(errorExpr, "status");
|
|
21650
|
+
const message = stringLiteralProp(errorExpr, "message");
|
|
21651
|
+
const codeFromError = booleanLiteralProp(errorExpr, "codeFromError");
|
|
21652
|
+
const messageFromError = booleanLiteralProp(errorExpr, "messageFromError");
|
|
21653
|
+
const statusFromError = booleanLiteralProp(errorExpr, "statusFromError");
|
|
21654
|
+
const retryable = booleanLiteralProp(errorExpr, "retryable");
|
|
21655
|
+
const outcomeUnknown = booleanLiteralProp(errorExpr, "outcomeUnknown");
|
|
21656
|
+
if (invalidBooleanProp(errorExpr, "codeFromError") || invalidBooleanProp(errorExpr, "messageFromError") || invalidBooleanProp(errorExpr, "statusFromError") || invalidBooleanProp(errorExpr, "retryable") || invalidBooleanProp(errorExpr, "outcomeUnknown")) {
|
|
21657
|
+
ctx.diagnostics.push({
|
|
21658
|
+
severity: "error",
|
|
21659
|
+
code: "invalid-effect-errors",
|
|
21660
|
+
errorCode: "SC3034",
|
|
21661
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21662
|
+
file,
|
|
21663
|
+
line: lineOf(errorExpr),
|
|
21664
|
+
message: `Route ${route.handler} effect.errors instance flags must be boolean literals.`
|
|
21665
|
+
});
|
|
21666
|
+
continue;
|
|
21667
|
+
}
|
|
21668
|
+
if (!tag || !code || status === undefined || !Number.isInteger(status) || status < 400 || status > 599) {
|
|
21669
|
+
ctx.diagnostics.push({
|
|
21670
|
+
severity: "error",
|
|
21671
|
+
code: "invalid-effect-errors",
|
|
21672
|
+
errorCode: "SC3034",
|
|
21673
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21674
|
+
file,
|
|
21675
|
+
line: lineOf(errorExpr),
|
|
21676
|
+
message: `Route ${route.handler} effect.errors entries require tag, code and an HTTP status from 400 to 599.`
|
|
21677
|
+
});
|
|
21678
|
+
continue;
|
|
21679
|
+
}
|
|
21680
|
+
errors.push({
|
|
21681
|
+
tag,
|
|
21682
|
+
code,
|
|
21683
|
+
status,
|
|
21684
|
+
...message === undefined ? {} : { message },
|
|
21685
|
+
...codeFromError === undefined ? {} : { codeFromError },
|
|
21686
|
+
...messageFromError === undefined ? {} : { messageFromError },
|
|
21687
|
+
...statusFromError === undefined ? {} : { statusFromError },
|
|
21688
|
+
...retryable === undefined ? {} : { retryable },
|
|
21689
|
+
...outcomeUnknown === undefined ? {} : { outcomeUnknown }
|
|
21614
21690
|
});
|
|
21615
|
-
continue;
|
|
21616
21691
|
}
|
|
21617
|
-
|
|
21692
|
+
effect.errors = errors;
|
|
21693
|
+
if (new Set(errors.map((entry) => entry.tag)).size !== errors.length) {
|
|
21618
21694
|
ctx.diagnostics.push({
|
|
21619
21695
|
severity: "error",
|
|
21620
21696
|
code: "invalid-effect-errors",
|
|
21621
21697
|
errorCode: "SC3034",
|
|
21622
21698
|
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21623
21699
|
file,
|
|
21624
|
-
line: lineOf(
|
|
21625
|
-
message: `Route ${route.handler} effect.errors
|
|
21700
|
+
line: lineOf(errorsExpr),
|
|
21701
|
+
message: `Route ${route.handler} effect.errors must not contain duplicate tags.`,
|
|
21702
|
+
suggestion: "Map each tagged failure exactly once."
|
|
21626
21703
|
});
|
|
21627
|
-
continue;
|
|
21628
21704
|
}
|
|
21629
|
-
errors.push({
|
|
21630
|
-
tag,
|
|
21631
|
-
code,
|
|
21632
|
-
status,
|
|
21633
|
-
...message === undefined ? {} : { message },
|
|
21634
|
-
...codeFromError === undefined ? {} : { codeFromError },
|
|
21635
|
-
...messageFromError === undefined ? {} : { messageFromError },
|
|
21636
|
-
...statusFromError === undefined ? {} : { statusFromError },
|
|
21637
|
-
...retryable === undefined ? {} : { retryable },
|
|
21638
|
-
...outcomeUnknown === undefined ? {} : { outcomeUnknown }
|
|
21639
|
-
});
|
|
21640
|
-
}
|
|
21641
|
-
effect.errors = errors;
|
|
21642
|
-
if (new Set(errors.map((entry) => entry.tag)).size !== errors.length) {
|
|
21643
|
-
ctx.diagnostics.push({
|
|
21644
|
-
severity: "error",
|
|
21645
|
-
code: "invalid-effect-errors",
|
|
21646
|
-
errorCode: "SC3034",
|
|
21647
|
-
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21648
|
-
file,
|
|
21649
|
-
line: lineOf(errorsExpr),
|
|
21650
|
-
message: `Route ${route.handler} effect.errors must not contain duplicate tags.`,
|
|
21651
|
-
suggestion: "Map each tagged failure exactly once."
|
|
21652
|
-
});
|
|
21653
21705
|
}
|
|
21654
21706
|
}
|
|
21655
|
-
|
|
21656
|
-
|
|
21657
|
-
|
|
21658
|
-
|
|
21659
|
-
|
|
21660
|
-
|
|
21661
|
-
|
|
21662
|
-
|
|
21663
|
-
|
|
21664
|
-
|
|
21665
|
-
|
|
21666
|
-
|
|
21667
|
-
|
|
21668
|
-
|
|
21669
|
-
|
|
21670
|
-
|
|
21671
|
-
});
|
|
21672
|
-
}
|
|
21673
|
-
if (effectArguments) {
|
|
21674
|
-
const [, failureType, environmentType] = effectArguments;
|
|
21675
|
-
const failure = await effectFailureTags(failureType, method, ctx);
|
|
21676
|
-
if (ctx.effectOptions.requireTaggedErrorTypes && failure.invalid) {
|
|
21677
|
-
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.");
|
|
21678
|
-
} else if (ctx.effectOptions.requireTaggedErrorTypes && effect.errors !== undefined && !failure.invalid) {
|
|
21679
|
-
const declaredTags = sortedUnique(effect.errors.map((entry) => entry.tag));
|
|
21680
|
-
const typedTags = sortedUnique(failure.tags);
|
|
21681
|
-
if (declaredTags.join("\x00") !== typedTags.join("\x00")) {
|
|
21682
|
-
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.");
|
|
21683
|
-
}
|
|
21707
|
+
route.effect = effect;
|
|
21708
|
+
const effectArguments = await effectTypeArgumentsOf(returnType, ctx);
|
|
21709
|
+
const promiseReturn = Boolean(resultType && /\bPromise(?:<|\s)/.test(resultType));
|
|
21710
|
+
if (promiseReturn) {
|
|
21711
|
+
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.");
|
|
21712
|
+
} else if (!effectArguments) {
|
|
21713
|
+
ctx.diagnostics.push({
|
|
21714
|
+
severity: "error",
|
|
21715
|
+
code: "effect-return-type",
|
|
21716
|
+
errorCode: "SC3035",
|
|
21717
|
+
docsUrl: "https://supacloud.dev/errors/SC3035",
|
|
21718
|
+
file,
|
|
21719
|
+
line: lineOf(method),
|
|
21720
|
+
message: `Route ${route.handler} declares an Effect contract but returns ${resultType ?? "an unresolved type"}.`,
|
|
21721
|
+
suggestion: "Return Effect.Effect<Success, Failure, Requirements> from the handler."
|
|
21722
|
+
});
|
|
21684
21723
|
}
|
|
21685
|
-
|
|
21686
|
-
|
|
21687
|
-
|
|
21688
|
-
|
|
21689
|
-
|
|
21690
|
-
|
|
21691
|
-
const
|
|
21692
|
-
|
|
21693
|
-
|
|
21724
|
+
if (effectArguments) {
|
|
21725
|
+
const [, failureType, environmentType] = effectArguments;
|
|
21726
|
+
const failure = await effectFailureTags(failureType, method, ctx);
|
|
21727
|
+
if (ctx.effectOptions.requireTaggedErrorTypes && failure.invalid) {
|
|
21728
|
+
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.");
|
|
21729
|
+
} else if (ctx.effectOptions.requireTaggedErrorTypes && effect.errors !== undefined && !failure.invalid) {
|
|
21730
|
+
const declaredTags = sortedUnique(effect.errors.map((entry) => entry.tag));
|
|
21731
|
+
const typedTags = sortedUnique(failure.tags);
|
|
21732
|
+
if (declaredTags.join("\x00") !== typedTags.join("\x00")) {
|
|
21733
|
+
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.");
|
|
21694
21734
|
}
|
|
21695
21735
|
}
|
|
21736
|
+
const environment = await effectEnvironmentNames(environmentType, method, ctx);
|
|
21737
|
+
if (ctx.effectOptions.requireExactDependencyTypes && effect.dependencies !== undefined) {
|
|
21738
|
+
if (environment.invalid) {
|
|
21739
|
+
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.");
|
|
21740
|
+
} else {
|
|
21741
|
+
const declaredDependencies = sortedUnique(effect.dependencies);
|
|
21742
|
+
const typedDependencies = sortedUnique(environment.names);
|
|
21743
|
+
if (declaredDependencies.join("\x00") !== typedDependencies.join("\x00")) {
|
|
21744
|
+
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.");
|
|
21745
|
+
}
|
|
21746
|
+
}
|
|
21747
|
+
}
|
|
21748
|
+
if (ctx.effectOptions.requireTimeoutForDependencies && (effect.dependencies?.length ?? 0) > 0 && effect.timeoutMs === undefined) {
|
|
21749
|
+
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.");
|
|
21750
|
+
}
|
|
21696
21751
|
}
|
|
21697
|
-
if (ctx.effectOptions.
|
|
21698
|
-
|
|
21699
|
-
|
|
21700
|
-
|
|
21701
|
-
if (ctx.effectOptions.forbidDirectThrows && method.body) {
|
|
21702
|
-
for (const throwStatement of descendantsOfKind(method.body, ts8.isThrowStatement)) {
|
|
21703
|
-
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.");
|
|
21752
|
+
if (ctx.effectOptions.forbidDirectThrows && method.body) {
|
|
21753
|
+
for (const throwStatement of descendantsOfKind(method.body, ts8.isThrowStatement)) {
|
|
21754
|
+
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.");
|
|
21755
|
+
}
|
|
21704
21756
|
}
|
|
21705
21757
|
}
|
|
21706
21758
|
}
|
|
@@ -22061,6 +22113,23 @@ function checkedBranches(meta, ctx) {
|
|
|
22061
22113
|
}
|
|
22062
22114
|
return branches;
|
|
22063
22115
|
}
|
|
22116
|
+
function checkedBranchesFallback(meta, ctx) {
|
|
22117
|
+
const expression = getProp(meta, "branchesFallback");
|
|
22118
|
+
if (!expression)
|
|
22119
|
+
return;
|
|
22120
|
+
const value = stringLiteralProp(meta, "branchesFallback");
|
|
22121
|
+
if (value === "reject" || value === "command")
|
|
22122
|
+
return value;
|
|
22123
|
+
ctx.diagnostics.push({
|
|
22124
|
+
severity: "error",
|
|
22125
|
+
code: "invalid-command-branches-fallback",
|
|
22126
|
+
file: sourcePath(ctx.rootDir, expression.getSourceFile().fileName),
|
|
22127
|
+
line: lineOf(expression),
|
|
22128
|
+
message: "Command branchesFallback must be 'reject' or 'command'.",
|
|
22129
|
+
suggestion: "Use branchesFallback: 'command' only when the command handler owns the unmatched-input boundary."
|
|
22130
|
+
});
|
|
22131
|
+
return;
|
|
22132
|
+
}
|
|
22064
22133
|
async function classDeps(cls, ctx) {
|
|
22065
22134
|
const injectable = await parseInjectableOptions(cls, ctx);
|
|
22066
22135
|
const ctor = cls.members.find(ts8.isConstructorDeclaration);
|
|
@@ -23336,6 +23405,14 @@ function validateEffectPolicies(graph, options = {}) {
|
|
|
23336
23405
|
for (const controller of module.controllers) {
|
|
23337
23406
|
for (const route of controller.routes) {
|
|
23338
23407
|
const label = routeLabel(route, controller.path);
|
|
23408
|
+
if (route.effectMode === "simple") {
|
|
23409
|
+
const featureTransition = featureTransitionForRoute(module, controller, route);
|
|
23410
|
+
const commandRef = route.command ?? featureTransition?.command;
|
|
23411
|
+
if (commandRef !== undefined || !["GET", "HEAD", "OPTIONS"].includes(route.method)) {
|
|
23412
|
+
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."));
|
|
23413
|
+
}
|
|
23414
|
+
continue;
|
|
23415
|
+
}
|
|
23339
23416
|
if (options.requireRouteEffects && route.effect === undefined) {
|
|
23340
23417
|
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.'));
|
|
23341
23418
|
continue;
|
|
@@ -23842,8 +23919,8 @@ function createDeliveryPlan(graph, input, validation) {
|
|
|
23842
23919
|
target.execution = group;
|
|
23843
23920
|
}
|
|
23844
23921
|
}
|
|
23845
|
-
if ([...targets.values()].reduce((count, target) => count + (target.execution
|
|
23846
|
-
fail("delivery-execution-replica-limit", "A release supports at most
|
|
23922
|
+
if ([...targets.values()].reduce((count, target) => count + (target.execution ? (target.execution.replicas ?? 1) * (target.execution.shardCount ?? 1) : 1), 0) > 256) {
|
|
23923
|
+
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.");
|
|
23847
23924
|
}
|
|
23848
23925
|
const canonicalDiagnostics = [...new Map(diagnostics.map((item) => [JSON.stringify(item), item])).values()].sort((a, b) => compare2(JSON.stringify(a), JSON.stringify(b)));
|
|
23849
23926
|
if (canonicalDiagnostics.some((item) => item.severity === "error")) {
|
|
@@ -23933,6 +24010,7 @@ function validateExecutionManifest(plan, capabilities = {}) {
|
|
|
23933
24010
|
...command.adapter === null ? {} : { rpc: command.adapter },
|
|
23934
24011
|
...command.audit === null ? {} : { audit: command.audit },
|
|
23935
24012
|
...command.branches.length === 0 ? {} : { branches: command.branches },
|
|
24013
|
+
...command.branchesFallback === undefined ? {} : { branchesFallback: command.branchesFallback },
|
|
23936
24014
|
transaction: command.transaction,
|
|
23937
24015
|
idempotency: command.idempotency
|
|
23938
24016
|
}))
|
|
@@ -25684,10 +25762,10 @@ import { readFileSync as readFileSync4 } from "node:fs";
|
|
|
25684
25762
|
import { resolve as resolve17 } from "node:path";
|
|
25685
25763
|
|
|
25686
25764
|
// src/migration-app-version.ts
|
|
25687
|
-
var MIGRATION_APP_VERSION = "0.
|
|
25765
|
+
var MIGRATION_APP_VERSION = "0.39.1";
|
|
25688
25766
|
|
|
25689
25767
|
// src/migration-elysia-version.ts
|
|
25690
|
-
var MIGRATION_ELYSIA_VERSION = "0.
|
|
25768
|
+
var MIGRATION_ELYSIA_VERSION = "0.42.0";
|
|
25691
25769
|
|
|
25692
25770
|
// src/migration-policy.ts
|
|
25693
25771
|
var cachedCompilerVersion;
|