@supacloud/compiler 0.57.0 → 0.58.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 +226 -206
- package/dist/command-boundary.d.ts +2 -0
- package/dist/index.js +226 -206
- package/dist/migration-app-version.d.ts +1 -1
- package/dist/migration-elysia-version.d.ts +1 -1
- package/dist/types.d.ts +3 -0
- package/package.json +2 -2
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"
|
|
@@ -2453,6 +2454,7 @@ function renderApplication(graph, options) {
|
|
|
2453
2454
|
executor: route.command === undefined ? "route" : "command",
|
|
2454
2455
|
...command === undefined ? {} : { commandBoundary: route.command === undefined ? "handler" : "route" },
|
|
2455
2456
|
...route.effect === undefined ? {} : { effect: route.effect },
|
|
2457
|
+
...route.effectMode === undefined ? {} : { effectMode: route.effectMode },
|
|
2456
2458
|
invoker: isPipeline ? "pipeline" : "direct",
|
|
2457
2459
|
stages: operationPlans.find((plan) => plan.module === module.name && plan.kind === "route" && plan.name === `${route.method} ${joinRoutePaths(controller.path, route.path)}`).stages,
|
|
2458
2460
|
...commandRef === undefined ? {} : { command: command?.name ?? commandRef },
|
|
@@ -2832,6 +2834,8 @@ class ModuleGenerator {
|
|
|
2832
2834
|
if (route.effect !== undefined) {
|
|
2833
2835
|
fields.push(`effect: ${JSON.stringify(route.effect)}`);
|
|
2834
2836
|
}
|
|
2837
|
+
if (route.effectMode !== undefined)
|
|
2838
|
+
fields.push(`effectMode: ${JSON.stringify(route.effectMode)}`);
|
|
2835
2839
|
if (idempotency !== undefined) {
|
|
2836
2840
|
fields.push(`idempotency: ${JSON.stringify(idempotency)}`);
|
|
2837
2841
|
}
|
|
@@ -4681,6 +4685,8 @@ var HEADER = "// GENERATED BY @supacloud/compiler — do not edit", INTERFACES =
|
|
|
4681
4685
|
maxAttempts?: number;
|
|
4682
4686
|
timeoutMs?: number;
|
|
4683
4687
|
};
|
|
4688
|
+
/** Simple route profile; no Effect interpreter or error mapping is required. */
|
|
4689
|
+
effectMode?: "simple";
|
|
4684
4690
|
/** Compile-time ownership and transport classification for the route boundary. */
|
|
4685
4691
|
contract?: {
|
|
4686
4692
|
body?: "framework" | "domain";
|
|
@@ -19186,6 +19192,8 @@ async function scanCommandBoundaries(source, file, checker, commands, boundRoute
|
|
|
19186
19192
|
return false;
|
|
19187
19193
|
if (!ts4.isCallExpression(node.parent) || node.parent.expression !== node)
|
|
19188
19194
|
return false;
|
|
19195
|
+
if (command.rpc !== undefined && propertyName(method.name) === "execute")
|
|
19196
|
+
return true;
|
|
19189
19197
|
for (let owner = node.parent.parent;owner; owner = owner.parent) {
|
|
19190
19198
|
if (ts4.isMethodDeclaration(owner)) {
|
|
19191
19199
|
if (owner.parent === method.parent && (ts4.isPropertyAccessExpression(node) || ts4.isElementAccessExpression(node)) && node.expression.kind === ts4.SyntaxKind.ThisKeyword)
|
|
@@ -19951,7 +19959,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths, effectOptio
|
|
|
19951
19959
|
}
|
|
19952
19960
|
const governedClasses = modules.flatMap((module) => module.commands.flatMap((command) => {
|
|
19953
19961
|
const declaration = ctx.classesByName.get(command.className)?.decl;
|
|
19954
|
-
return declaration ? [{ declaration, name: command.name }] : [];
|
|
19962
|
+
return declaration ? [{ declaration, name: command.name, ...command.rpc === undefined ? {} : { rpc: command.rpc } }] : [];
|
|
19955
19963
|
}));
|
|
19956
19964
|
const boundRoutes = new Map;
|
|
19957
19965
|
for (const module of modules)
|
|
@@ -21486,111 +21494,102 @@ async function parseController(input, ctx) {
|
|
|
21486
21494
|
}
|
|
21487
21495
|
const effectExpr = getProp(optionsObject, "effect");
|
|
21488
21496
|
if (effectExpr) {
|
|
21489
|
-
|
|
21490
|
-
|
|
21491
|
-
ctx.diagnostics.push({
|
|
21492
|
-
severity: "error",
|
|
21493
|
-
code: "invalid-effect-contract",
|
|
21494
|
-
errorCode: "SC3030",
|
|
21495
|
-
docsUrl: "https://supacloud.dev/errors/SC3030",
|
|
21496
|
-
file,
|
|
21497
|
-
line: lineOf(effectExpr),
|
|
21498
|
-
message: `Route ${route.handler} effect must be a statically resolvable object literal.`,
|
|
21499
|
-
suggestion: 'Use effect: { required: true, errors: [...], dependencies: [...], retry: "none" }.'
|
|
21500
|
-
});
|
|
21497
|
+
if (effectExpr.kind === ts8.SyntaxKind.FalseKeyword) {
|
|
21498
|
+
route.effectMode = "simple";
|
|
21501
21499
|
} else {
|
|
21502
|
-
const
|
|
21503
|
-
|
|
21504
|
-
if (requiredExpr !== undefined && booleanProp(effectObject, "required") !== true) {
|
|
21500
|
+
const effectObject = await resolveStaticObjectLiteral(effectExpr, ctx);
|
|
21501
|
+
if (!effectObject) {
|
|
21505
21502
|
ctx.diagnostics.push({
|
|
21506
21503
|
severity: "error",
|
|
21507
21504
|
code: "invalid-effect-contract",
|
|
21508
21505
|
errorCode: "SC3030",
|
|
21509
21506
|
docsUrl: "https://supacloud.dev/errors/SC3030",
|
|
21510
21507
|
file,
|
|
21511
|
-
line: lineOf(
|
|
21512
|
-
message: `Route ${route.handler} effect
|
|
21508
|
+
line: lineOf(effectExpr),
|
|
21509
|
+
message: `Route ${route.handler} effect must be a statically resolvable object literal.`,
|
|
21510
|
+
suggestion: 'Use effect: { required: true, errors: [...], dependencies: [...], retry: "none" }.'
|
|
21513
21511
|
});
|
|
21514
|
-
}
|
|
21515
|
-
|
|
21516
|
-
|
|
21517
|
-
if (
|
|
21512
|
+
} else {
|
|
21513
|
+
const effect = { required: true };
|
|
21514
|
+
const requiredExpr = getProp(effectObject, "required");
|
|
21515
|
+
if (requiredExpr !== undefined && booleanProp(effectObject, "required") !== true) {
|
|
21518
21516
|
ctx.diagnostics.push({
|
|
21519
21517
|
severity: "error",
|
|
21520
|
-
code: "invalid-effect-
|
|
21521
|
-
errorCode: "
|
|
21522
|
-
docsUrl: "https://supacloud.dev/errors/
|
|
21518
|
+
code: "invalid-effect-contract",
|
|
21519
|
+
errorCode: "SC3030",
|
|
21520
|
+
docsUrl: "https://supacloud.dev/errors/SC3030",
|
|
21523
21521
|
file,
|
|
21524
|
-
line: lineOf(
|
|
21525
|
-
message: `Route ${route.handler} effect.
|
|
21522
|
+
line: lineOf(requiredExpr),
|
|
21523
|
+
message: `Route ${route.handler} effect.required must be the literal true.`
|
|
21526
21524
|
});
|
|
21527
|
-
} else {
|
|
21528
|
-
effect.retry = retry;
|
|
21529
21525
|
}
|
|
21530
|
-
|
|
21531
|
-
|
|
21532
|
-
|
|
21533
|
-
|
|
21534
|
-
|
|
21535
|
-
|
|
21536
|
-
|
|
21526
|
+
const retry = stringLiteralProp(effectObject, "retry");
|
|
21527
|
+
if (retry !== undefined) {
|
|
21528
|
+
if (retry !== "none" && retry !== "explicit") {
|
|
21529
|
+
ctx.diagnostics.push({
|
|
21530
|
+
severity: "error",
|
|
21531
|
+
code: "invalid-effect-retry",
|
|
21532
|
+
errorCode: "SC3031",
|
|
21533
|
+
docsUrl: "https://supacloud.dev/errors/SC3031",
|
|
21534
|
+
file,
|
|
21535
|
+
line: lineOf(getProp(effectObject, "retry")),
|
|
21536
|
+
message: `Route ${route.handler} effect.retry must be "none" or "explicit".`
|
|
21537
|
+
});
|
|
21538
|
+
} else {
|
|
21539
|
+
effect.retry = retry;
|
|
21540
|
+
}
|
|
21541
|
+
} else if (ctx.effectOptions.requireRetryPolicy === false) {
|
|
21542
|
+
effect.retry = "none";
|
|
21543
|
+
}
|
|
21544
|
+
const maxAttemptsExpr = getProp(effectObject, "maxAttempts");
|
|
21545
|
+
if (maxAttemptsExpr !== undefined) {
|
|
21546
|
+
const maxAttempts = numberLiteralProp(effectObject, "maxAttempts");
|
|
21547
|
+
if (maxAttempts === undefined || maxAttempts < 1 || !Number.isInteger(maxAttempts)) {
|
|
21548
|
+
ctx.diagnostics.push({
|
|
21549
|
+
severity: "error",
|
|
21550
|
+
code: "invalid-effect-retry",
|
|
21551
|
+
errorCode: "SC3031",
|
|
21552
|
+
docsUrl: "https://supacloud.dev/errors/SC3031",
|
|
21553
|
+
file,
|
|
21554
|
+
line: lineOf(maxAttemptsExpr),
|
|
21555
|
+
message: `Route ${route.handler} effect.maxAttempts must be a positive integer.`
|
|
21556
|
+
});
|
|
21557
|
+
} else {
|
|
21558
|
+
effect.maxAttempts = maxAttempts;
|
|
21559
|
+
}
|
|
21560
|
+
}
|
|
21561
|
+
if (effect.retry === "explicit" && effect.maxAttempts === undefined) {
|
|
21537
21562
|
ctx.diagnostics.push({
|
|
21538
21563
|
severity: "error",
|
|
21539
21564
|
code: "invalid-effect-retry",
|
|
21540
21565
|
errorCode: "SC3031",
|
|
21541
21566
|
docsUrl: "https://supacloud.dev/errors/SC3031",
|
|
21542
21567
|
file,
|
|
21543
|
-
line: lineOf(
|
|
21544
|
-
message: `Route ${route.handler} effect.maxAttempts
|
|
21568
|
+
line: lineOf(effectExpr),
|
|
21569
|
+
message: `Route ${route.handler} effect.maxAttempts is required when retry is "explicit".`
|
|
21545
21570
|
});
|
|
21546
|
-
} else {
|
|
21547
|
-
effect.maxAttempts = maxAttempts;
|
|
21548
21571
|
}
|
|
21549
|
-
|
|
21550
|
-
|
|
21551
|
-
|
|
21552
|
-
|
|
21553
|
-
|
|
21554
|
-
|
|
21555
|
-
|
|
21556
|
-
|
|
21557
|
-
|
|
21558
|
-
|
|
21559
|
-
|
|
21560
|
-
|
|
21561
|
-
|
|
21562
|
-
|
|
21563
|
-
|
|
21564
|
-
|
|
21565
|
-
ctx.diagnostics.push({
|
|
21566
|
-
severity: "error",
|
|
21567
|
-
code: "invalid-effect-timeout",
|
|
21568
|
-
errorCode: "SC3032",
|
|
21569
|
-
docsUrl: "https://supacloud.dev/errors/SC3032",
|
|
21570
|
-
file,
|
|
21571
|
-
line: lineOf(timeoutExpr),
|
|
21572
|
-
message: `Route ${route.handler} effect.timeoutMs must be a positive integer.`
|
|
21573
|
-
});
|
|
21574
|
-
} else {
|
|
21575
|
-
effect.timeoutMs = timeoutMs;
|
|
21572
|
+
const timeoutExpr = getProp(effectObject, "timeoutMs");
|
|
21573
|
+
if (timeoutExpr !== undefined) {
|
|
21574
|
+
const timeoutMs = numberLiteralProp(effectObject, "timeoutMs");
|
|
21575
|
+
if (timeoutMs === undefined || timeoutMs <= 0 || !Number.isInteger(timeoutMs)) {
|
|
21576
|
+
ctx.diagnostics.push({
|
|
21577
|
+
severity: "error",
|
|
21578
|
+
code: "invalid-effect-timeout",
|
|
21579
|
+
errorCode: "SC3032",
|
|
21580
|
+
docsUrl: "https://supacloud.dev/errors/SC3032",
|
|
21581
|
+
file,
|
|
21582
|
+
line: lineOf(timeoutExpr),
|
|
21583
|
+
message: `Route ${route.handler} effect.timeoutMs must be a positive integer.`
|
|
21584
|
+
});
|
|
21585
|
+
} else {
|
|
21586
|
+
effect.timeoutMs = timeoutMs;
|
|
21587
|
+
}
|
|
21576
21588
|
}
|
|
21577
|
-
|
|
21578
|
-
|
|
21579
|
-
|
|
21580
|
-
|
|
21581
|
-
if (!dependenciesArray || dependenciesArray.elements.some((element) => !ts8.isStringLiteral(element) || element.text.trim().length === 0)) {
|
|
21582
|
-
ctx.diagnostics.push({
|
|
21583
|
-
severity: "error",
|
|
21584
|
-
code: "invalid-effect-dependencies",
|
|
21585
|
-
errorCode: "SC3033",
|
|
21586
|
-
docsUrl: "https://supacloud.dev/errors/SC3033",
|
|
21587
|
-
file,
|
|
21588
|
-
line: lineOf(dependenciesExpr),
|
|
21589
|
-
message: `Route ${route.handler} effect.dependencies must be a string array or a const initialized with one.`
|
|
21590
|
-
});
|
|
21591
|
-
} else {
|
|
21592
|
-
effect.dependencies = dependenciesArray.elements.map((element) => element.text);
|
|
21593
|
-
if (new Set(effect.dependencies).size !== effect.dependencies.length) {
|
|
21589
|
+
const dependenciesExpr = getProp(effectObject, "dependencies");
|
|
21590
|
+
if (dependenciesExpr !== undefined) {
|
|
21591
|
+
const dependenciesArray = await resolveStaticArrayLiteral(dependenciesExpr, ctx);
|
|
21592
|
+
if (!dependenciesArray || dependenciesArray.elements.some((element) => !ts8.isStringLiteral(element) || element.text.trim().length === 0)) {
|
|
21594
21593
|
ctx.diagnostics.push({
|
|
21595
21594
|
severity: "error",
|
|
21596
21595
|
code: "invalid-effect-dependencies",
|
|
@@ -21598,148 +21597,161 @@ async function parseController(input, ctx) {
|
|
|
21598
21597
|
docsUrl: "https://supacloud.dev/errors/SC3033",
|
|
21599
21598
|
file,
|
|
21600
21599
|
line: lineOf(dependenciesExpr),
|
|
21601
|
-
message: `Route ${route.handler} effect.dependencies must
|
|
21602
|
-
suggestion: "List each logical Effect dependency exactly once."
|
|
21600
|
+
message: `Route ${route.handler} effect.dependencies must be a string array or a const initialized with one.`
|
|
21603
21601
|
});
|
|
21604
|
-
}
|
|
21605
|
-
|
|
21606
|
-
|
|
21607
|
-
const errorsExpr = getProp(effectObject, "errors");
|
|
21608
|
-
if (errorsExpr !== undefined) {
|
|
21609
|
-
const errorsArray = await resolveStaticArrayLiteral(errorsExpr, ctx);
|
|
21610
|
-
if (!errorsArray) {
|
|
21611
|
-
ctx.diagnostics.push({
|
|
21612
|
-
severity: "error",
|
|
21613
|
-
code: "invalid-effect-errors",
|
|
21614
|
-
errorCode: "SC3034",
|
|
21615
|
-
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21616
|
-
file,
|
|
21617
|
-
line: lineOf(errorsExpr),
|
|
21618
|
-
message: `Route ${route.handler} effect.errors must be an object array or a const initialized with one.`
|
|
21619
|
-
});
|
|
21620
|
-
} else {
|
|
21621
|
-
const errors = [];
|
|
21622
|
-
for (const errorExpr of errorsArray.elements) {
|
|
21623
|
-
if (!ts8.isObjectLiteralExpression(errorExpr)) {
|
|
21602
|
+
} else {
|
|
21603
|
+
effect.dependencies = dependenciesArray.elements.map((element) => element.text);
|
|
21604
|
+
if (new Set(effect.dependencies).size !== effect.dependencies.length) {
|
|
21624
21605
|
ctx.diagnostics.push({
|
|
21625
21606
|
severity: "error",
|
|
21626
|
-
code: "invalid-effect-
|
|
21627
|
-
errorCode: "
|
|
21628
|
-
docsUrl: "https://supacloud.dev/errors/
|
|
21607
|
+
code: "invalid-effect-dependencies",
|
|
21608
|
+
errorCode: "SC3033",
|
|
21609
|
+
docsUrl: "https://supacloud.dev/errors/SC3033",
|
|
21629
21610
|
file,
|
|
21630
|
-
line: lineOf(
|
|
21631
|
-
message: `Route ${route.handler} effect.
|
|
21611
|
+
line: lineOf(dependenciesExpr),
|
|
21612
|
+
message: `Route ${route.handler} effect.dependencies must not contain duplicate service names.`,
|
|
21613
|
+
suggestion: "List each logical Effect dependency exactly once."
|
|
21632
21614
|
});
|
|
21633
|
-
continue;
|
|
21634
21615
|
}
|
|
21635
|
-
|
|
21636
|
-
|
|
21637
|
-
|
|
21638
|
-
|
|
21639
|
-
|
|
21640
|
-
|
|
21641
|
-
|
|
21642
|
-
|
|
21643
|
-
|
|
21644
|
-
|
|
21645
|
-
|
|
21646
|
-
|
|
21647
|
-
|
|
21648
|
-
|
|
21649
|
-
|
|
21650
|
-
|
|
21651
|
-
|
|
21652
|
-
|
|
21616
|
+
}
|
|
21617
|
+
}
|
|
21618
|
+
const errorsExpr = getProp(effectObject, "errors");
|
|
21619
|
+
if (errorsExpr !== undefined) {
|
|
21620
|
+
const errorsArray = await resolveStaticArrayLiteral(errorsExpr, ctx);
|
|
21621
|
+
if (!errorsArray) {
|
|
21622
|
+
ctx.diagnostics.push({
|
|
21623
|
+
severity: "error",
|
|
21624
|
+
code: "invalid-effect-errors",
|
|
21625
|
+
errorCode: "SC3034",
|
|
21626
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21627
|
+
file,
|
|
21628
|
+
line: lineOf(errorsExpr),
|
|
21629
|
+
message: `Route ${route.handler} effect.errors must be an object array or a const initialized with one.`
|
|
21630
|
+
});
|
|
21631
|
+
} else {
|
|
21632
|
+
const errors = [];
|
|
21633
|
+
for (const errorExpr of errorsArray.elements) {
|
|
21634
|
+
if (!ts8.isObjectLiteralExpression(errorExpr)) {
|
|
21635
|
+
ctx.diagnostics.push({
|
|
21636
|
+
severity: "error",
|
|
21637
|
+
code: "invalid-effect-errors",
|
|
21638
|
+
errorCode: "SC3034",
|
|
21639
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21640
|
+
file,
|
|
21641
|
+
line: lineOf(errorExpr),
|
|
21642
|
+
message: `Route ${route.handler} effect.errors entries must be object literals.`
|
|
21643
|
+
});
|
|
21644
|
+
continue;
|
|
21645
|
+
}
|
|
21646
|
+
const tag = stringLiteralProp(errorExpr, "tag");
|
|
21647
|
+
const code = stringLiteralProp(errorExpr, "code");
|
|
21648
|
+
const status = numberLiteralProp(errorExpr, "status");
|
|
21649
|
+
const message = stringLiteralProp(errorExpr, "message");
|
|
21650
|
+
const codeFromError = booleanLiteralProp(errorExpr, "codeFromError");
|
|
21651
|
+
const messageFromError = booleanLiteralProp(errorExpr, "messageFromError");
|
|
21652
|
+
const statusFromError = booleanLiteralProp(errorExpr, "statusFromError");
|
|
21653
|
+
const retryable = booleanLiteralProp(errorExpr, "retryable");
|
|
21654
|
+
const outcomeUnknown = booleanLiteralProp(errorExpr, "outcomeUnknown");
|
|
21655
|
+
if (invalidBooleanProp(errorExpr, "codeFromError") || invalidBooleanProp(errorExpr, "messageFromError") || invalidBooleanProp(errorExpr, "statusFromError") || invalidBooleanProp(errorExpr, "retryable") || invalidBooleanProp(errorExpr, "outcomeUnknown")) {
|
|
21656
|
+
ctx.diagnostics.push({
|
|
21657
|
+
severity: "error",
|
|
21658
|
+
code: "invalid-effect-errors",
|
|
21659
|
+
errorCode: "SC3034",
|
|
21660
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21661
|
+
file,
|
|
21662
|
+
line: lineOf(errorExpr),
|
|
21663
|
+
message: `Route ${route.handler} effect.errors instance flags must be boolean literals.`
|
|
21664
|
+
});
|
|
21665
|
+
continue;
|
|
21666
|
+
}
|
|
21667
|
+
if (!tag || !code || status === undefined || !Number.isInteger(status) || status < 400 || status > 599) {
|
|
21668
|
+
ctx.diagnostics.push({
|
|
21669
|
+
severity: "error",
|
|
21670
|
+
code: "invalid-effect-errors",
|
|
21671
|
+
errorCode: "SC3034",
|
|
21672
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21673
|
+
file,
|
|
21674
|
+
line: lineOf(errorExpr),
|
|
21675
|
+
message: `Route ${route.handler} effect.errors entries require tag, code and an HTTP status from 400 to 599.`
|
|
21676
|
+
});
|
|
21677
|
+
continue;
|
|
21678
|
+
}
|
|
21679
|
+
errors.push({
|
|
21680
|
+
tag,
|
|
21681
|
+
code,
|
|
21682
|
+
status,
|
|
21683
|
+
...message === undefined ? {} : { message },
|
|
21684
|
+
...codeFromError === undefined ? {} : { codeFromError },
|
|
21685
|
+
...messageFromError === undefined ? {} : { messageFromError },
|
|
21686
|
+
...statusFromError === undefined ? {} : { statusFromError },
|
|
21687
|
+
...retryable === undefined ? {} : { retryable },
|
|
21688
|
+
...outcomeUnknown === undefined ? {} : { outcomeUnknown }
|
|
21653
21689
|
});
|
|
21654
|
-
continue;
|
|
21655
21690
|
}
|
|
21656
|
-
|
|
21691
|
+
effect.errors = errors;
|
|
21692
|
+
if (new Set(errors.map((entry) => entry.tag)).size !== errors.length) {
|
|
21657
21693
|
ctx.diagnostics.push({
|
|
21658
21694
|
severity: "error",
|
|
21659
21695
|
code: "invalid-effect-errors",
|
|
21660
21696
|
errorCode: "SC3034",
|
|
21661
21697
|
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21662
21698
|
file,
|
|
21663
|
-
line: lineOf(
|
|
21664
|
-
message: `Route ${route.handler} effect.errors
|
|
21699
|
+
line: lineOf(errorsExpr),
|
|
21700
|
+
message: `Route ${route.handler} effect.errors must not contain duplicate tags.`,
|
|
21701
|
+
suggestion: "Map each tagged failure exactly once."
|
|
21665
21702
|
});
|
|
21666
|
-
continue;
|
|
21667
21703
|
}
|
|
21668
|
-
errors.push({
|
|
21669
|
-
tag,
|
|
21670
|
-
code,
|
|
21671
|
-
status,
|
|
21672
|
-
...message === undefined ? {} : { message },
|
|
21673
|
-
...codeFromError === undefined ? {} : { codeFromError },
|
|
21674
|
-
...messageFromError === undefined ? {} : { messageFromError },
|
|
21675
|
-
...statusFromError === undefined ? {} : { statusFromError },
|
|
21676
|
-
...retryable === undefined ? {} : { retryable },
|
|
21677
|
-
...outcomeUnknown === undefined ? {} : { outcomeUnknown }
|
|
21678
|
-
});
|
|
21679
|
-
}
|
|
21680
|
-
effect.errors = errors;
|
|
21681
|
-
if (new Set(errors.map((entry) => entry.tag)).size !== errors.length) {
|
|
21682
|
-
ctx.diagnostics.push({
|
|
21683
|
-
severity: "error",
|
|
21684
|
-
code: "invalid-effect-errors",
|
|
21685
|
-
errorCode: "SC3034",
|
|
21686
|
-
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21687
|
-
file,
|
|
21688
|
-
line: lineOf(errorsExpr),
|
|
21689
|
-
message: `Route ${route.handler} effect.errors must not contain duplicate tags.`,
|
|
21690
|
-
suggestion: "Map each tagged failure exactly once."
|
|
21691
|
-
});
|
|
21692
21704
|
}
|
|
21693
21705
|
}
|
|
21694
|
-
|
|
21695
|
-
|
|
21696
|
-
|
|
21697
|
-
|
|
21698
|
-
|
|
21699
|
-
|
|
21700
|
-
|
|
21701
|
-
|
|
21702
|
-
|
|
21703
|
-
|
|
21704
|
-
|
|
21705
|
-
|
|
21706
|
-
|
|
21707
|
-
|
|
21708
|
-
|
|
21709
|
-
|
|
21710
|
-
});
|
|
21711
|
-
}
|
|
21712
|
-
if (effectArguments) {
|
|
21713
|
-
const [, failureType, environmentType] = effectArguments;
|
|
21714
|
-
const failure = await effectFailureTags(failureType, method, ctx);
|
|
21715
|
-
if (ctx.effectOptions.requireTaggedErrorTypes && failure.invalid) {
|
|
21716
|
-
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.");
|
|
21717
|
-
} else if (ctx.effectOptions.requireTaggedErrorTypes && effect.errors !== undefined && !failure.invalid) {
|
|
21718
|
-
const declaredTags = sortedUnique(effect.errors.map((entry) => entry.tag));
|
|
21719
|
-
const typedTags = sortedUnique(failure.tags);
|
|
21720
|
-
if (declaredTags.join("\x00") !== typedTags.join("\x00")) {
|
|
21721
|
-
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.");
|
|
21722
|
-
}
|
|
21706
|
+
route.effect = effect;
|
|
21707
|
+
const effectArguments = await effectTypeArgumentsOf(returnType, ctx);
|
|
21708
|
+
const promiseReturn = Boolean(resultType && /\bPromise(?:<|\s)/.test(resultType));
|
|
21709
|
+
if (promiseReturn) {
|
|
21710
|
+
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.");
|
|
21711
|
+
} else if (!effectArguments) {
|
|
21712
|
+
ctx.diagnostics.push({
|
|
21713
|
+
severity: "error",
|
|
21714
|
+
code: "effect-return-type",
|
|
21715
|
+
errorCode: "SC3035",
|
|
21716
|
+
docsUrl: "https://supacloud.dev/errors/SC3035",
|
|
21717
|
+
file,
|
|
21718
|
+
line: lineOf(method),
|
|
21719
|
+
message: `Route ${route.handler} declares an Effect contract but returns ${resultType ?? "an unresolved type"}.`,
|
|
21720
|
+
suggestion: "Return Effect.Effect<Success, Failure, Requirements> from the handler."
|
|
21721
|
+
});
|
|
21723
21722
|
}
|
|
21724
|
-
|
|
21725
|
-
|
|
21726
|
-
|
|
21727
|
-
|
|
21728
|
-
|
|
21729
|
-
|
|
21730
|
-
const
|
|
21731
|
-
|
|
21732
|
-
|
|
21723
|
+
if (effectArguments) {
|
|
21724
|
+
const [, failureType, environmentType] = effectArguments;
|
|
21725
|
+
const failure = await effectFailureTags(failureType, method, ctx);
|
|
21726
|
+
if (ctx.effectOptions.requireTaggedErrorTypes && failure.invalid) {
|
|
21727
|
+
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.");
|
|
21728
|
+
} else if (ctx.effectOptions.requireTaggedErrorTypes && effect.errors !== undefined && !failure.invalid) {
|
|
21729
|
+
const declaredTags = sortedUnique(effect.errors.map((entry) => entry.tag));
|
|
21730
|
+
const typedTags = sortedUnique(failure.tags);
|
|
21731
|
+
if (declaredTags.join("\x00") !== typedTags.join("\x00")) {
|
|
21732
|
+
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.");
|
|
21733
21733
|
}
|
|
21734
21734
|
}
|
|
21735
|
+
const environment = await effectEnvironmentNames(environmentType, method, ctx);
|
|
21736
|
+
if (ctx.effectOptions.requireExactDependencyTypes && effect.dependencies !== undefined) {
|
|
21737
|
+
if (environment.invalid) {
|
|
21738
|
+
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.");
|
|
21739
|
+
} else {
|
|
21740
|
+
const declaredDependencies = sortedUnique(effect.dependencies);
|
|
21741
|
+
const typedDependencies = sortedUnique(environment.names);
|
|
21742
|
+
if (declaredDependencies.join("\x00") !== typedDependencies.join("\x00")) {
|
|
21743
|
+
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.");
|
|
21744
|
+
}
|
|
21745
|
+
}
|
|
21746
|
+
}
|
|
21747
|
+
if (ctx.effectOptions.requireTimeoutForDependencies && (effect.dependencies?.length ?? 0) > 0 && effect.timeoutMs === undefined) {
|
|
21748
|
+
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.");
|
|
21749
|
+
}
|
|
21735
21750
|
}
|
|
21736
|
-
if (ctx.effectOptions.
|
|
21737
|
-
|
|
21738
|
-
|
|
21739
|
-
|
|
21740
|
-
if (ctx.effectOptions.forbidDirectThrows && method.body) {
|
|
21741
|
-
for (const throwStatement of descendantsOfKind(method.body, ts8.isThrowStatement)) {
|
|
21742
|
-
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.");
|
|
21751
|
+
if (ctx.effectOptions.forbidDirectThrows && method.body) {
|
|
21752
|
+
for (const throwStatement of descendantsOfKind(method.body, ts8.isThrowStatement)) {
|
|
21753
|
+
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.");
|
|
21754
|
+
}
|
|
21743
21755
|
}
|
|
21744
21756
|
}
|
|
21745
21757
|
}
|
|
@@ -23373,6 +23385,14 @@ function validateEffectPolicies(graph, options = {}) {
|
|
|
23373
23385
|
for (const controller of module.controllers) {
|
|
23374
23386
|
for (const route of controller.routes) {
|
|
23375
23387
|
const label = routeLabel(route, controller.path);
|
|
23388
|
+
if (route.effectMode === "simple") {
|
|
23389
|
+
const featureTransition = featureTransitionForRoute(module, controller, route);
|
|
23390
|
+
const commandRef = route.command ?? featureTransition?.command;
|
|
23391
|
+
if (commandRef !== undefined || !["GET", "HEAD", "OPTIONS"].includes(route.method)) {
|
|
23392
|
+
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."));
|
|
23393
|
+
}
|
|
23394
|
+
continue;
|
|
23395
|
+
}
|
|
23376
23396
|
if (options.requireRouteEffects && route.effect === undefined) {
|
|
23377
23397
|
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.'));
|
|
23378
23398
|
continue;
|
|
@@ -26430,10 +26450,10 @@ import { readFileSync as readFileSync4 } from "node:fs";
|
|
|
26430
26450
|
import { resolve as resolve19 } from "node:path";
|
|
26431
26451
|
|
|
26432
26452
|
// src/migration-app-version.ts
|
|
26433
|
-
var MIGRATION_APP_VERSION = "0.
|
|
26453
|
+
var MIGRATION_APP_VERSION = "0.39.0";
|
|
26434
26454
|
|
|
26435
26455
|
// src/migration-elysia-version.ts
|
|
26436
|
-
var MIGRATION_ELYSIA_VERSION = "0.
|
|
26456
|
+
var MIGRATION_ELYSIA_VERSION = "0.41.0";
|
|
26437
26457
|
|
|
26438
26458
|
// src/migration-policy.ts
|
|
26439
26459
|
var cachedCompilerVersion;
|
|
@@ -3,6 +3,8 @@ import type { Diagnostic } from "./types";
|
|
|
3
3
|
interface GovernedClass {
|
|
4
4
|
declaration: ts.ClassDeclaration;
|
|
5
5
|
name: string;
|
|
6
|
+
/** Declared durable persistence boundary (the database RPC that owns receipt, audit and transaction). */
|
|
7
|
+
rpc?: string;
|
|
6
8
|
}
|
|
7
9
|
/** A conservative source check for typed command references, not a JavaScript sandbox. */
|
|
8
10
|
export declare function scanCommandBoundaries(source: ts.SourceFile, file: string, checker: ts.TypeChecker, commands: readonly GovernedClass[], boundRoutes: ReadonlyMap<ts.MethodDeclaration, string>): Promise<Diagnostic[]>;
|
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"
|
|
@@ -2452,6 +2453,7 @@ function renderApplication(graph, options) {
|
|
|
2452
2453
|
executor: route.command === undefined ? "route" : "command",
|
|
2453
2454
|
...command === undefined ? {} : { commandBoundary: route.command === undefined ? "handler" : "route" },
|
|
2454
2455
|
...route.effect === undefined ? {} : { effect: route.effect },
|
|
2456
|
+
...route.effectMode === undefined ? {} : { effectMode: route.effectMode },
|
|
2455
2457
|
invoker: isPipeline ? "pipeline" : "direct",
|
|
2456
2458
|
stages: operationPlans.find((plan) => plan.module === module.name && plan.kind === "route" && plan.name === `${route.method} ${joinRoutePaths(controller.path, route.path)}`).stages,
|
|
2457
2459
|
...commandRef === undefined ? {} : { command: command?.name ?? commandRef },
|
|
@@ -2831,6 +2833,8 @@ class ModuleGenerator {
|
|
|
2831
2833
|
if (route.effect !== undefined) {
|
|
2832
2834
|
fields.push(`effect: ${JSON.stringify(route.effect)}`);
|
|
2833
2835
|
}
|
|
2836
|
+
if (route.effectMode !== undefined)
|
|
2837
|
+
fields.push(`effectMode: ${JSON.stringify(route.effectMode)}`);
|
|
2834
2838
|
if (idempotency !== undefined) {
|
|
2835
2839
|
fields.push(`idempotency: ${JSON.stringify(idempotency)}`);
|
|
2836
2840
|
}
|
|
@@ -4680,6 +4684,8 @@ var HEADER = "// GENERATED BY @supacloud/compiler — do not edit", INTERFACES =
|
|
|
4680
4684
|
maxAttempts?: number;
|
|
4681
4685
|
timeoutMs?: number;
|
|
4682
4686
|
};
|
|
4687
|
+
/** Simple route profile; no Effect interpreter or error mapping is required. */
|
|
4688
|
+
effectMode?: "simple";
|
|
4683
4689
|
/** Compile-time ownership and transport classification for the route boundary. */
|
|
4684
4690
|
contract?: {
|
|
4685
4691
|
body?: "framework" | "domain";
|
|
@@ -19145,6 +19151,8 @@ async function scanCommandBoundaries(source, file, checker, commands, boundRoute
|
|
|
19145
19151
|
return false;
|
|
19146
19152
|
if (!ts4.isCallExpression(node.parent) || node.parent.expression !== node)
|
|
19147
19153
|
return false;
|
|
19154
|
+
if (command.rpc !== undefined && propertyName(method.name) === "execute")
|
|
19155
|
+
return true;
|
|
19148
19156
|
for (let owner = node.parent.parent;owner; owner = owner.parent) {
|
|
19149
19157
|
if (ts4.isMethodDeclaration(owner)) {
|
|
19150
19158
|
if (owner.parent === method.parent && (ts4.isPropertyAccessExpression(node) || ts4.isElementAccessExpression(node)) && node.expression.kind === ts4.SyntaxKind.ThisKeyword)
|
|
@@ -19910,7 +19918,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths, effectOptio
|
|
|
19910
19918
|
}
|
|
19911
19919
|
const governedClasses = modules.flatMap((module) => module.commands.flatMap((command) => {
|
|
19912
19920
|
const declaration = ctx.classesByName.get(command.className)?.decl;
|
|
19913
|
-
return declaration ? [{ declaration, name: command.name }] : [];
|
|
19921
|
+
return declaration ? [{ declaration, name: command.name, ...command.rpc === undefined ? {} : { rpc: command.rpc } }] : [];
|
|
19914
19922
|
}));
|
|
19915
19923
|
const boundRoutes = new Map;
|
|
19916
19924
|
for (const module of modules)
|
|
@@ -21445,111 +21453,102 @@ async function parseController(input, ctx) {
|
|
|
21445
21453
|
}
|
|
21446
21454
|
const effectExpr = getProp(optionsObject, "effect");
|
|
21447
21455
|
if (effectExpr) {
|
|
21448
|
-
|
|
21449
|
-
|
|
21450
|
-
ctx.diagnostics.push({
|
|
21451
|
-
severity: "error",
|
|
21452
|
-
code: "invalid-effect-contract",
|
|
21453
|
-
errorCode: "SC3030",
|
|
21454
|
-
docsUrl: "https://supacloud.dev/errors/SC3030",
|
|
21455
|
-
file,
|
|
21456
|
-
line: lineOf(effectExpr),
|
|
21457
|
-
message: `Route ${route.handler} effect must be a statically resolvable object literal.`,
|
|
21458
|
-
suggestion: 'Use effect: { required: true, errors: [...], dependencies: [...], retry: "none" }.'
|
|
21459
|
-
});
|
|
21456
|
+
if (effectExpr.kind === ts8.SyntaxKind.FalseKeyword) {
|
|
21457
|
+
route.effectMode = "simple";
|
|
21460
21458
|
} else {
|
|
21461
|
-
const
|
|
21462
|
-
|
|
21463
|
-
if (requiredExpr !== undefined && booleanProp(effectObject, "required") !== true) {
|
|
21459
|
+
const effectObject = await resolveStaticObjectLiteral(effectExpr, ctx);
|
|
21460
|
+
if (!effectObject) {
|
|
21464
21461
|
ctx.diagnostics.push({
|
|
21465
21462
|
severity: "error",
|
|
21466
21463
|
code: "invalid-effect-contract",
|
|
21467
21464
|
errorCode: "SC3030",
|
|
21468
21465
|
docsUrl: "https://supacloud.dev/errors/SC3030",
|
|
21469
21466
|
file,
|
|
21470
|
-
line: lineOf(
|
|
21471
|
-
message: `Route ${route.handler} effect
|
|
21467
|
+
line: lineOf(effectExpr),
|
|
21468
|
+
message: `Route ${route.handler} effect must be a statically resolvable object literal.`,
|
|
21469
|
+
suggestion: 'Use effect: { required: true, errors: [...], dependencies: [...], retry: "none" }.'
|
|
21472
21470
|
});
|
|
21473
|
-
}
|
|
21474
|
-
|
|
21475
|
-
|
|
21476
|
-
if (
|
|
21471
|
+
} else {
|
|
21472
|
+
const effect = { required: true };
|
|
21473
|
+
const requiredExpr = getProp(effectObject, "required");
|
|
21474
|
+
if (requiredExpr !== undefined && booleanProp(effectObject, "required") !== true) {
|
|
21477
21475
|
ctx.diagnostics.push({
|
|
21478
21476
|
severity: "error",
|
|
21479
|
-
code: "invalid-effect-
|
|
21480
|
-
errorCode: "
|
|
21481
|
-
docsUrl: "https://supacloud.dev/errors/
|
|
21477
|
+
code: "invalid-effect-contract",
|
|
21478
|
+
errorCode: "SC3030",
|
|
21479
|
+
docsUrl: "https://supacloud.dev/errors/SC3030",
|
|
21482
21480
|
file,
|
|
21483
|
-
line: lineOf(
|
|
21484
|
-
message: `Route ${route.handler} effect.
|
|
21481
|
+
line: lineOf(requiredExpr),
|
|
21482
|
+
message: `Route ${route.handler} effect.required must be the literal true.`
|
|
21485
21483
|
});
|
|
21486
|
-
} else {
|
|
21487
|
-
effect.retry = retry;
|
|
21488
21484
|
}
|
|
21489
|
-
|
|
21490
|
-
|
|
21491
|
-
|
|
21492
|
-
|
|
21493
|
-
|
|
21494
|
-
|
|
21495
|
-
|
|
21485
|
+
const retry = stringLiteralProp(effectObject, "retry");
|
|
21486
|
+
if (retry !== undefined) {
|
|
21487
|
+
if (retry !== "none" && retry !== "explicit") {
|
|
21488
|
+
ctx.diagnostics.push({
|
|
21489
|
+
severity: "error",
|
|
21490
|
+
code: "invalid-effect-retry",
|
|
21491
|
+
errorCode: "SC3031",
|
|
21492
|
+
docsUrl: "https://supacloud.dev/errors/SC3031",
|
|
21493
|
+
file,
|
|
21494
|
+
line: lineOf(getProp(effectObject, "retry")),
|
|
21495
|
+
message: `Route ${route.handler} effect.retry must be "none" or "explicit".`
|
|
21496
|
+
});
|
|
21497
|
+
} else {
|
|
21498
|
+
effect.retry = retry;
|
|
21499
|
+
}
|
|
21500
|
+
} else if (ctx.effectOptions.requireRetryPolicy === false) {
|
|
21501
|
+
effect.retry = "none";
|
|
21502
|
+
}
|
|
21503
|
+
const maxAttemptsExpr = getProp(effectObject, "maxAttempts");
|
|
21504
|
+
if (maxAttemptsExpr !== undefined) {
|
|
21505
|
+
const maxAttempts = numberLiteralProp(effectObject, "maxAttempts");
|
|
21506
|
+
if (maxAttempts === undefined || maxAttempts < 1 || !Number.isInteger(maxAttempts)) {
|
|
21507
|
+
ctx.diagnostics.push({
|
|
21508
|
+
severity: "error",
|
|
21509
|
+
code: "invalid-effect-retry",
|
|
21510
|
+
errorCode: "SC3031",
|
|
21511
|
+
docsUrl: "https://supacloud.dev/errors/SC3031",
|
|
21512
|
+
file,
|
|
21513
|
+
line: lineOf(maxAttemptsExpr),
|
|
21514
|
+
message: `Route ${route.handler} effect.maxAttempts must be a positive integer.`
|
|
21515
|
+
});
|
|
21516
|
+
} else {
|
|
21517
|
+
effect.maxAttempts = maxAttempts;
|
|
21518
|
+
}
|
|
21519
|
+
}
|
|
21520
|
+
if (effect.retry === "explicit" && effect.maxAttempts === undefined) {
|
|
21496
21521
|
ctx.diagnostics.push({
|
|
21497
21522
|
severity: "error",
|
|
21498
21523
|
code: "invalid-effect-retry",
|
|
21499
21524
|
errorCode: "SC3031",
|
|
21500
21525
|
docsUrl: "https://supacloud.dev/errors/SC3031",
|
|
21501
21526
|
file,
|
|
21502
|
-
line: lineOf(
|
|
21503
|
-
message: `Route ${route.handler} effect.maxAttempts
|
|
21527
|
+
line: lineOf(effectExpr),
|
|
21528
|
+
message: `Route ${route.handler} effect.maxAttempts is required when retry is "explicit".`
|
|
21504
21529
|
});
|
|
21505
|
-
} else {
|
|
21506
|
-
effect.maxAttempts = maxAttempts;
|
|
21507
21530
|
}
|
|
21508
|
-
|
|
21509
|
-
|
|
21510
|
-
|
|
21511
|
-
|
|
21512
|
-
|
|
21513
|
-
|
|
21514
|
-
|
|
21515
|
-
|
|
21516
|
-
|
|
21517
|
-
|
|
21518
|
-
|
|
21519
|
-
|
|
21520
|
-
|
|
21521
|
-
|
|
21522
|
-
|
|
21523
|
-
|
|
21524
|
-
ctx.diagnostics.push({
|
|
21525
|
-
severity: "error",
|
|
21526
|
-
code: "invalid-effect-timeout",
|
|
21527
|
-
errorCode: "SC3032",
|
|
21528
|
-
docsUrl: "https://supacloud.dev/errors/SC3032",
|
|
21529
|
-
file,
|
|
21530
|
-
line: lineOf(timeoutExpr),
|
|
21531
|
-
message: `Route ${route.handler} effect.timeoutMs must be a positive integer.`
|
|
21532
|
-
});
|
|
21533
|
-
} else {
|
|
21534
|
-
effect.timeoutMs = timeoutMs;
|
|
21531
|
+
const timeoutExpr = getProp(effectObject, "timeoutMs");
|
|
21532
|
+
if (timeoutExpr !== undefined) {
|
|
21533
|
+
const timeoutMs = numberLiteralProp(effectObject, "timeoutMs");
|
|
21534
|
+
if (timeoutMs === undefined || timeoutMs <= 0 || !Number.isInteger(timeoutMs)) {
|
|
21535
|
+
ctx.diagnostics.push({
|
|
21536
|
+
severity: "error",
|
|
21537
|
+
code: "invalid-effect-timeout",
|
|
21538
|
+
errorCode: "SC3032",
|
|
21539
|
+
docsUrl: "https://supacloud.dev/errors/SC3032",
|
|
21540
|
+
file,
|
|
21541
|
+
line: lineOf(timeoutExpr),
|
|
21542
|
+
message: `Route ${route.handler} effect.timeoutMs must be a positive integer.`
|
|
21543
|
+
});
|
|
21544
|
+
} else {
|
|
21545
|
+
effect.timeoutMs = timeoutMs;
|
|
21546
|
+
}
|
|
21535
21547
|
}
|
|
21536
|
-
|
|
21537
|
-
|
|
21538
|
-
|
|
21539
|
-
|
|
21540
|
-
if (!dependenciesArray || dependenciesArray.elements.some((element) => !ts8.isStringLiteral(element) || element.text.trim().length === 0)) {
|
|
21541
|
-
ctx.diagnostics.push({
|
|
21542
|
-
severity: "error",
|
|
21543
|
-
code: "invalid-effect-dependencies",
|
|
21544
|
-
errorCode: "SC3033",
|
|
21545
|
-
docsUrl: "https://supacloud.dev/errors/SC3033",
|
|
21546
|
-
file,
|
|
21547
|
-
line: lineOf(dependenciesExpr),
|
|
21548
|
-
message: `Route ${route.handler} effect.dependencies must be a string array or a const initialized with one.`
|
|
21549
|
-
});
|
|
21550
|
-
} else {
|
|
21551
|
-
effect.dependencies = dependenciesArray.elements.map((element) => element.text);
|
|
21552
|
-
if (new Set(effect.dependencies).size !== effect.dependencies.length) {
|
|
21548
|
+
const dependenciesExpr = getProp(effectObject, "dependencies");
|
|
21549
|
+
if (dependenciesExpr !== undefined) {
|
|
21550
|
+
const dependenciesArray = await resolveStaticArrayLiteral(dependenciesExpr, ctx);
|
|
21551
|
+
if (!dependenciesArray || dependenciesArray.elements.some((element) => !ts8.isStringLiteral(element) || element.text.trim().length === 0)) {
|
|
21553
21552
|
ctx.diagnostics.push({
|
|
21554
21553
|
severity: "error",
|
|
21555
21554
|
code: "invalid-effect-dependencies",
|
|
@@ -21557,148 +21556,161 @@ async function parseController(input, ctx) {
|
|
|
21557
21556
|
docsUrl: "https://supacloud.dev/errors/SC3033",
|
|
21558
21557
|
file,
|
|
21559
21558
|
line: lineOf(dependenciesExpr),
|
|
21560
|
-
message: `Route ${route.handler} effect.dependencies must
|
|
21561
|
-
suggestion: "List each logical Effect dependency exactly once."
|
|
21559
|
+
message: `Route ${route.handler} effect.dependencies must be a string array or a const initialized with one.`
|
|
21562
21560
|
});
|
|
21563
|
-
}
|
|
21564
|
-
|
|
21565
|
-
|
|
21566
|
-
const errorsExpr = getProp(effectObject, "errors");
|
|
21567
|
-
if (errorsExpr !== undefined) {
|
|
21568
|
-
const errorsArray = await resolveStaticArrayLiteral(errorsExpr, ctx);
|
|
21569
|
-
if (!errorsArray) {
|
|
21570
|
-
ctx.diagnostics.push({
|
|
21571
|
-
severity: "error",
|
|
21572
|
-
code: "invalid-effect-errors",
|
|
21573
|
-
errorCode: "SC3034",
|
|
21574
|
-
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21575
|
-
file,
|
|
21576
|
-
line: lineOf(errorsExpr),
|
|
21577
|
-
message: `Route ${route.handler} effect.errors must be an object array or a const initialized with one.`
|
|
21578
|
-
});
|
|
21579
|
-
} else {
|
|
21580
|
-
const errors = [];
|
|
21581
|
-
for (const errorExpr of errorsArray.elements) {
|
|
21582
|
-
if (!ts8.isObjectLiteralExpression(errorExpr)) {
|
|
21561
|
+
} else {
|
|
21562
|
+
effect.dependencies = dependenciesArray.elements.map((element) => element.text);
|
|
21563
|
+
if (new Set(effect.dependencies).size !== effect.dependencies.length) {
|
|
21583
21564
|
ctx.diagnostics.push({
|
|
21584
21565
|
severity: "error",
|
|
21585
|
-
code: "invalid-effect-
|
|
21586
|
-
errorCode: "
|
|
21587
|
-
docsUrl: "https://supacloud.dev/errors/
|
|
21566
|
+
code: "invalid-effect-dependencies",
|
|
21567
|
+
errorCode: "SC3033",
|
|
21568
|
+
docsUrl: "https://supacloud.dev/errors/SC3033",
|
|
21588
21569
|
file,
|
|
21589
|
-
line: lineOf(
|
|
21590
|
-
message: `Route ${route.handler} effect.
|
|
21570
|
+
line: lineOf(dependenciesExpr),
|
|
21571
|
+
message: `Route ${route.handler} effect.dependencies must not contain duplicate service names.`,
|
|
21572
|
+
suggestion: "List each logical Effect dependency exactly once."
|
|
21591
21573
|
});
|
|
21592
|
-
continue;
|
|
21593
21574
|
}
|
|
21594
|
-
|
|
21595
|
-
|
|
21596
|
-
|
|
21597
|
-
|
|
21598
|
-
|
|
21599
|
-
|
|
21600
|
-
|
|
21601
|
-
|
|
21602
|
-
|
|
21603
|
-
|
|
21604
|
-
|
|
21605
|
-
|
|
21606
|
-
|
|
21607
|
-
|
|
21608
|
-
|
|
21609
|
-
|
|
21610
|
-
|
|
21611
|
-
|
|
21575
|
+
}
|
|
21576
|
+
}
|
|
21577
|
+
const errorsExpr = getProp(effectObject, "errors");
|
|
21578
|
+
if (errorsExpr !== undefined) {
|
|
21579
|
+
const errorsArray = await resolveStaticArrayLiteral(errorsExpr, ctx);
|
|
21580
|
+
if (!errorsArray) {
|
|
21581
|
+
ctx.diagnostics.push({
|
|
21582
|
+
severity: "error",
|
|
21583
|
+
code: "invalid-effect-errors",
|
|
21584
|
+
errorCode: "SC3034",
|
|
21585
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21586
|
+
file,
|
|
21587
|
+
line: lineOf(errorsExpr),
|
|
21588
|
+
message: `Route ${route.handler} effect.errors must be an object array or a const initialized with one.`
|
|
21589
|
+
});
|
|
21590
|
+
} else {
|
|
21591
|
+
const errors = [];
|
|
21592
|
+
for (const errorExpr of errorsArray.elements) {
|
|
21593
|
+
if (!ts8.isObjectLiteralExpression(errorExpr)) {
|
|
21594
|
+
ctx.diagnostics.push({
|
|
21595
|
+
severity: "error",
|
|
21596
|
+
code: "invalid-effect-errors",
|
|
21597
|
+
errorCode: "SC3034",
|
|
21598
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21599
|
+
file,
|
|
21600
|
+
line: lineOf(errorExpr),
|
|
21601
|
+
message: `Route ${route.handler} effect.errors entries must be object literals.`
|
|
21602
|
+
});
|
|
21603
|
+
continue;
|
|
21604
|
+
}
|
|
21605
|
+
const tag = stringLiteralProp(errorExpr, "tag");
|
|
21606
|
+
const code = stringLiteralProp(errorExpr, "code");
|
|
21607
|
+
const status = numberLiteralProp(errorExpr, "status");
|
|
21608
|
+
const message = stringLiteralProp(errorExpr, "message");
|
|
21609
|
+
const codeFromError = booleanLiteralProp(errorExpr, "codeFromError");
|
|
21610
|
+
const messageFromError = booleanLiteralProp(errorExpr, "messageFromError");
|
|
21611
|
+
const statusFromError = booleanLiteralProp(errorExpr, "statusFromError");
|
|
21612
|
+
const retryable = booleanLiteralProp(errorExpr, "retryable");
|
|
21613
|
+
const outcomeUnknown = booleanLiteralProp(errorExpr, "outcomeUnknown");
|
|
21614
|
+
if (invalidBooleanProp(errorExpr, "codeFromError") || invalidBooleanProp(errorExpr, "messageFromError") || invalidBooleanProp(errorExpr, "statusFromError") || invalidBooleanProp(errorExpr, "retryable") || invalidBooleanProp(errorExpr, "outcomeUnknown")) {
|
|
21615
|
+
ctx.diagnostics.push({
|
|
21616
|
+
severity: "error",
|
|
21617
|
+
code: "invalid-effect-errors",
|
|
21618
|
+
errorCode: "SC3034",
|
|
21619
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21620
|
+
file,
|
|
21621
|
+
line: lineOf(errorExpr),
|
|
21622
|
+
message: `Route ${route.handler} effect.errors instance flags must be boolean literals.`
|
|
21623
|
+
});
|
|
21624
|
+
continue;
|
|
21625
|
+
}
|
|
21626
|
+
if (!tag || !code || status === undefined || !Number.isInteger(status) || status < 400 || status > 599) {
|
|
21627
|
+
ctx.diagnostics.push({
|
|
21628
|
+
severity: "error",
|
|
21629
|
+
code: "invalid-effect-errors",
|
|
21630
|
+
errorCode: "SC3034",
|
|
21631
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21632
|
+
file,
|
|
21633
|
+
line: lineOf(errorExpr),
|
|
21634
|
+
message: `Route ${route.handler} effect.errors entries require tag, code and an HTTP status from 400 to 599.`
|
|
21635
|
+
});
|
|
21636
|
+
continue;
|
|
21637
|
+
}
|
|
21638
|
+
errors.push({
|
|
21639
|
+
tag,
|
|
21640
|
+
code,
|
|
21641
|
+
status,
|
|
21642
|
+
...message === undefined ? {} : { message },
|
|
21643
|
+
...codeFromError === undefined ? {} : { codeFromError },
|
|
21644
|
+
...messageFromError === undefined ? {} : { messageFromError },
|
|
21645
|
+
...statusFromError === undefined ? {} : { statusFromError },
|
|
21646
|
+
...retryable === undefined ? {} : { retryable },
|
|
21647
|
+
...outcomeUnknown === undefined ? {} : { outcomeUnknown }
|
|
21612
21648
|
});
|
|
21613
|
-
continue;
|
|
21614
21649
|
}
|
|
21615
|
-
|
|
21650
|
+
effect.errors = errors;
|
|
21651
|
+
if (new Set(errors.map((entry) => entry.tag)).size !== errors.length) {
|
|
21616
21652
|
ctx.diagnostics.push({
|
|
21617
21653
|
severity: "error",
|
|
21618
21654
|
code: "invalid-effect-errors",
|
|
21619
21655
|
errorCode: "SC3034",
|
|
21620
21656
|
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21621
21657
|
file,
|
|
21622
|
-
line: lineOf(
|
|
21623
|
-
message: `Route ${route.handler} effect.errors
|
|
21658
|
+
line: lineOf(errorsExpr),
|
|
21659
|
+
message: `Route ${route.handler} effect.errors must not contain duplicate tags.`,
|
|
21660
|
+
suggestion: "Map each tagged failure exactly once."
|
|
21624
21661
|
});
|
|
21625
|
-
continue;
|
|
21626
21662
|
}
|
|
21627
|
-
errors.push({
|
|
21628
|
-
tag,
|
|
21629
|
-
code,
|
|
21630
|
-
status,
|
|
21631
|
-
...message === undefined ? {} : { message },
|
|
21632
|
-
...codeFromError === undefined ? {} : { codeFromError },
|
|
21633
|
-
...messageFromError === undefined ? {} : { messageFromError },
|
|
21634
|
-
...statusFromError === undefined ? {} : { statusFromError },
|
|
21635
|
-
...retryable === undefined ? {} : { retryable },
|
|
21636
|
-
...outcomeUnknown === undefined ? {} : { outcomeUnknown }
|
|
21637
|
-
});
|
|
21638
|
-
}
|
|
21639
|
-
effect.errors = errors;
|
|
21640
|
-
if (new Set(errors.map((entry) => entry.tag)).size !== errors.length) {
|
|
21641
|
-
ctx.diagnostics.push({
|
|
21642
|
-
severity: "error",
|
|
21643
|
-
code: "invalid-effect-errors",
|
|
21644
|
-
errorCode: "SC3034",
|
|
21645
|
-
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
21646
|
-
file,
|
|
21647
|
-
line: lineOf(errorsExpr),
|
|
21648
|
-
message: `Route ${route.handler} effect.errors must not contain duplicate tags.`,
|
|
21649
|
-
suggestion: "Map each tagged failure exactly once."
|
|
21650
|
-
});
|
|
21651
21663
|
}
|
|
21652
21664
|
}
|
|
21653
|
-
|
|
21654
|
-
|
|
21655
|
-
|
|
21656
|
-
|
|
21657
|
-
|
|
21658
|
-
|
|
21659
|
-
|
|
21660
|
-
|
|
21661
|
-
|
|
21662
|
-
|
|
21663
|
-
|
|
21664
|
-
|
|
21665
|
-
|
|
21666
|
-
|
|
21667
|
-
|
|
21668
|
-
|
|
21669
|
-
});
|
|
21670
|
-
}
|
|
21671
|
-
if (effectArguments) {
|
|
21672
|
-
const [, failureType, environmentType] = effectArguments;
|
|
21673
|
-
const failure = await effectFailureTags(failureType, method, ctx);
|
|
21674
|
-
if (ctx.effectOptions.requireTaggedErrorTypes && failure.invalid) {
|
|
21675
|
-
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.");
|
|
21676
|
-
} else if (ctx.effectOptions.requireTaggedErrorTypes && effect.errors !== undefined && !failure.invalid) {
|
|
21677
|
-
const declaredTags = sortedUnique(effect.errors.map((entry) => entry.tag));
|
|
21678
|
-
const typedTags = sortedUnique(failure.tags);
|
|
21679
|
-
if (declaredTags.join("\x00") !== typedTags.join("\x00")) {
|
|
21680
|
-
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.");
|
|
21681
|
-
}
|
|
21665
|
+
route.effect = effect;
|
|
21666
|
+
const effectArguments = await effectTypeArgumentsOf(returnType, ctx);
|
|
21667
|
+
const promiseReturn = Boolean(resultType && /\bPromise(?:<|\s)/.test(resultType));
|
|
21668
|
+
if (promiseReturn) {
|
|
21669
|
+
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.");
|
|
21670
|
+
} else if (!effectArguments) {
|
|
21671
|
+
ctx.diagnostics.push({
|
|
21672
|
+
severity: "error",
|
|
21673
|
+
code: "effect-return-type",
|
|
21674
|
+
errorCode: "SC3035",
|
|
21675
|
+
docsUrl: "https://supacloud.dev/errors/SC3035",
|
|
21676
|
+
file,
|
|
21677
|
+
line: lineOf(method),
|
|
21678
|
+
message: `Route ${route.handler} declares an Effect contract but returns ${resultType ?? "an unresolved type"}.`,
|
|
21679
|
+
suggestion: "Return Effect.Effect<Success, Failure, Requirements> from the handler."
|
|
21680
|
+
});
|
|
21682
21681
|
}
|
|
21683
|
-
|
|
21684
|
-
|
|
21685
|
-
|
|
21686
|
-
|
|
21687
|
-
|
|
21688
|
-
|
|
21689
|
-
const
|
|
21690
|
-
|
|
21691
|
-
|
|
21682
|
+
if (effectArguments) {
|
|
21683
|
+
const [, failureType, environmentType] = effectArguments;
|
|
21684
|
+
const failure = await effectFailureTags(failureType, method, ctx);
|
|
21685
|
+
if (ctx.effectOptions.requireTaggedErrorTypes && failure.invalid) {
|
|
21686
|
+
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.");
|
|
21687
|
+
} else if (ctx.effectOptions.requireTaggedErrorTypes && effect.errors !== undefined && !failure.invalid) {
|
|
21688
|
+
const declaredTags = sortedUnique(effect.errors.map((entry) => entry.tag));
|
|
21689
|
+
const typedTags = sortedUnique(failure.tags);
|
|
21690
|
+
if (declaredTags.join("\x00") !== typedTags.join("\x00")) {
|
|
21691
|
+
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.");
|
|
21692
21692
|
}
|
|
21693
21693
|
}
|
|
21694
|
+
const environment = await effectEnvironmentNames(environmentType, method, ctx);
|
|
21695
|
+
if (ctx.effectOptions.requireExactDependencyTypes && effect.dependencies !== undefined) {
|
|
21696
|
+
if (environment.invalid) {
|
|
21697
|
+
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.");
|
|
21698
|
+
} else {
|
|
21699
|
+
const declaredDependencies = sortedUnique(effect.dependencies);
|
|
21700
|
+
const typedDependencies = sortedUnique(environment.names);
|
|
21701
|
+
if (declaredDependencies.join("\x00") !== typedDependencies.join("\x00")) {
|
|
21702
|
+
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.");
|
|
21703
|
+
}
|
|
21704
|
+
}
|
|
21705
|
+
}
|
|
21706
|
+
if (ctx.effectOptions.requireTimeoutForDependencies && (effect.dependencies?.length ?? 0) > 0 && effect.timeoutMs === undefined) {
|
|
21707
|
+
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.");
|
|
21708
|
+
}
|
|
21694
21709
|
}
|
|
21695
|
-
if (ctx.effectOptions.
|
|
21696
|
-
|
|
21697
|
-
|
|
21698
|
-
|
|
21699
|
-
if (ctx.effectOptions.forbidDirectThrows && method.body) {
|
|
21700
|
-
for (const throwStatement of descendantsOfKind(method.body, ts8.isThrowStatement)) {
|
|
21701
|
-
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.");
|
|
21710
|
+
if (ctx.effectOptions.forbidDirectThrows && method.body) {
|
|
21711
|
+
for (const throwStatement of descendantsOfKind(method.body, ts8.isThrowStatement)) {
|
|
21712
|
+
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.");
|
|
21713
|
+
}
|
|
21702
21714
|
}
|
|
21703
21715
|
}
|
|
21704
21716
|
}
|
|
@@ -23334,6 +23346,14 @@ function validateEffectPolicies(graph, options = {}) {
|
|
|
23334
23346
|
for (const controller of module.controllers) {
|
|
23335
23347
|
for (const route of controller.routes) {
|
|
23336
23348
|
const label = routeLabel(route, controller.path);
|
|
23349
|
+
if (route.effectMode === "simple") {
|
|
23350
|
+
const featureTransition = featureTransitionForRoute(module, controller, route);
|
|
23351
|
+
const commandRef = route.command ?? featureTransition?.command;
|
|
23352
|
+
if (commandRef !== undefined || !["GET", "HEAD", "OPTIONS"].includes(route.method)) {
|
|
23353
|
+
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."));
|
|
23354
|
+
}
|
|
23355
|
+
continue;
|
|
23356
|
+
}
|
|
23337
23357
|
if (options.requireRouteEffects && route.effect === undefined) {
|
|
23338
23358
|
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.'));
|
|
23339
23359
|
continue;
|
|
@@ -25682,10 +25702,10 @@ import { readFileSync as readFileSync4 } from "node:fs";
|
|
|
25682
25702
|
import { resolve as resolve17 } from "node:path";
|
|
25683
25703
|
|
|
25684
25704
|
// src/migration-app-version.ts
|
|
25685
|
-
var MIGRATION_APP_VERSION = "0.
|
|
25705
|
+
var MIGRATION_APP_VERSION = "0.39.0";
|
|
25686
25706
|
|
|
25687
25707
|
// src/migration-elysia-version.ts
|
|
25688
|
-
var MIGRATION_ELYSIA_VERSION = "0.
|
|
25708
|
+
var MIGRATION_ELYSIA_VERSION = "0.41.0";
|
|
25689
25709
|
|
|
25690
25710
|
// src/migration-policy.ts
|
|
25691
25711
|
var cachedCompilerVersion;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MIGRATION_APP_VERSION = "0.
|
|
1
|
+
export declare const MIGRATION_APP_VERSION = "0.39.0";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MIGRATION_ELYSIA_VERSION = "0.
|
|
1
|
+
export declare const MIGRATION_ELYSIA_VERSION = "0.41.0";
|
package/dist/types.d.ts
CHANGED
|
@@ -253,6 +253,8 @@ export interface RouteNode {
|
|
|
253
253
|
responses?: Record<string, string>;
|
|
254
254
|
/** Static Effect execution contract for this route. */
|
|
255
255
|
effect?: EffectRouteNode;
|
|
256
|
+
/** Simple route profile: handler may return a Promise/value without Effect governance. */
|
|
257
|
+
effectMode?: "simple";
|
|
256
258
|
/** @Command-decorated class explicitly bound by the route. */
|
|
257
259
|
command?: string;
|
|
258
260
|
/** Route guards executed before handler (Angular CanActivateFn style). */
|
|
@@ -404,6 +406,7 @@ export interface ExecutionPlanRoute {
|
|
|
404
406
|
invoker: "direct" | "pipeline";
|
|
405
407
|
commandBoundary?: "route" | "handler";
|
|
406
408
|
effect?: EffectRouteNode;
|
|
409
|
+
effectMode?: "simple";
|
|
407
410
|
stages: string[];
|
|
408
411
|
command?: string;
|
|
409
412
|
guards: string[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.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,7 +57,7 @@
|
|
|
57
57
|
"directory": "packages/compiler"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@supacloud/db": "0.
|
|
60
|
+
"@supacloud/db": "0.22.0",
|
|
61
61
|
"@supacloud/delivery": "0.25.0",
|
|
62
62
|
"typebox": "1.4.3",
|
|
63
63
|
"@graphql-codegen/core": "^6.2.2",
|