@supacloud/compiler 0.40.1 → 0.42.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/README.md +8 -0
- package/dist/analyze.d.ts +2 -2
- package/dist/cli.js +534 -15
- package/dist/config.d.ts +4 -1
- package/dist/effect-governance.d.ts +3 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +535 -15
- package/dist/migration-app-version.d.ts +1 -1
- package/dist/migration-elysia-version.d.ts +1 -1
- package/dist/types.d.ts +38 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1236,6 +1236,24 @@ var init_validate = __esm(() => {
|
|
|
1236
1236
|
"route-contract-required": { code: "SC3021", docsUrl: "https://supacloud.dev/errors/SC3021" },
|
|
1237
1237
|
"route-contract-unverified": { code: "SC3022", docsUrl: "https://supacloud.dev/errors/SC3022" },
|
|
1238
1238
|
"route-contract-evidence-required": { code: "SC3023", docsUrl: "https://supacloud.dev/errors/SC3023" },
|
|
1239
|
+
"invalid-effect-contract": { code: "SC3030", docsUrl: "https://supacloud.dev/errors/SC3030" },
|
|
1240
|
+
"invalid-effect-retry": { code: "SC3031", docsUrl: "https://supacloud.dev/errors/SC3031" },
|
|
1241
|
+
"invalid-effect-timeout": { code: "SC3032", docsUrl: "https://supacloud.dev/errors/SC3032" },
|
|
1242
|
+
"invalid-effect-dependencies": { code: "SC3033", docsUrl: "https://supacloud.dev/errors/SC3033" },
|
|
1243
|
+
"invalid-effect-errors": { code: "SC3034", docsUrl: "https://supacloud.dev/errors/SC3034" },
|
|
1244
|
+
"effect-return-type": { code: "SC3035", docsUrl: "https://supacloud.dev/errors/SC3035" },
|
|
1245
|
+
"effect-contract-required": { code: "SC3036", docsUrl: "https://supacloud.dev/errors/SC3036" },
|
|
1246
|
+
"effect-error-mapping-required": { code: "SC3037", docsUrl: "https://supacloud.dev/errors/SC3037" },
|
|
1247
|
+
"effect-dependencies-required": { code: "SC3038", docsUrl: "https://supacloud.dev/errors/SC3038" },
|
|
1248
|
+
"effect-command-retry-forbidden": { code: "SC3039", docsUrl: "https://supacloud.dev/errors/SC3039" },
|
|
1249
|
+
"effect-dependency-type": { code: "SC3040", docsUrl: "https://supacloud.dev/errors/SC3040" },
|
|
1250
|
+
"effect-error-type": { code: "SC3041", docsUrl: "https://supacloud.dev/errors/SC3041" },
|
|
1251
|
+
"effect-error-mapping-type": { code: "SC3042", docsUrl: "https://supacloud.dev/errors/SC3042" },
|
|
1252
|
+
"effect-dependency-mismatch": { code: "SC3043", docsUrl: "https://supacloud.dev/errors/SC3043" },
|
|
1253
|
+
"effect-timeout-required": { code: "SC3044", docsUrl: "https://supacloud.dev/errors/SC3044" },
|
|
1254
|
+
"effect-direct-runtime-execution": { code: "SC3045", docsUrl: "https://supacloud.dev/errors/SC3045" },
|
|
1255
|
+
"effect-direct-throw": { code: "SC3046", docsUrl: "https://supacloud.dev/errors/SC3046" },
|
|
1256
|
+
"effect-promise-return": { code: "SC3047", docsUrl: "https://supacloud.dev/errors/SC3047" },
|
|
1239
1257
|
"invalid-route-response-map": { code: "SC3024", docsUrl: "https://supacloud.dev/errors/SC3024" },
|
|
1240
1258
|
"invalid-route-response-selector": { code: "SC3025", docsUrl: "https://supacloud.dev/errors/SC3025" },
|
|
1241
1259
|
"conflicting-route-response-schema": { code: "SC3026", docsUrl: "https://supacloud.dev/errors/SC3026" },
|
|
@@ -1774,6 +1792,9 @@ class ModuleGenerator {
|
|
|
1774
1792
|
});
|
|
1775
1793
|
fields.push(`responses: { ${responseFields.join(", ")} }`);
|
|
1776
1794
|
}
|
|
1795
|
+
if (route.effect !== undefined) {
|
|
1796
|
+
fields.push(`effect: ${JSON.stringify(route.effect)}`);
|
|
1797
|
+
}
|
|
1777
1798
|
if (route.command)
|
|
1778
1799
|
fields.push(`command: ${JSON.stringify(route.command)}`);
|
|
1779
1800
|
if (route.guards && route.guards.length > 0) {
|
|
@@ -3585,6 +3606,14 @@ var HEADER = "// GENERATED BY @supacloud/compiler — do not edit", INTERFACES =
|
|
|
3585
3606
|
cookie?: unknown;
|
|
3586
3607
|
response?: unknown;
|
|
3587
3608
|
responses?: Record<string | number, unknown>;
|
|
3609
|
+
effect?: {
|
|
3610
|
+
required: true;
|
|
3611
|
+
errors?: Array<{ tag: string; status: number; code: string; message?: string }>;
|
|
3612
|
+
dependencies?: string[];
|
|
3613
|
+
retry?: "none" | "explicit";
|
|
3614
|
+
maxAttempts?: number;
|
|
3615
|
+
timeoutMs?: number;
|
|
3616
|
+
};
|
|
3588
3617
|
/** Compile-time ownership and transport classification for the route boundary. */
|
|
3589
3618
|
contract?: {
|
|
3590
3619
|
body?: "framework" | "domain";
|
|
@@ -27752,6 +27781,13 @@ function withDiagnosticMetadata(diagnostic) {
|
|
|
27752
27781
|
const metadata = COMPILER_DIAGNOSTIC_CODES[diagnostic.code];
|
|
27753
27782
|
return metadata ? { ...diagnostic, errorCode: metadata.code, docsUrl: metadata.docsUrl } : diagnostic;
|
|
27754
27783
|
}
|
|
27784
|
+
var DEFAULT_EFFECT_ANALYSIS_OPTIONS = {
|
|
27785
|
+
requireTaggedErrorTypes: true,
|
|
27786
|
+
requireExactDependencyTypes: true,
|
|
27787
|
+
requireTimeoutForDependencies: true,
|
|
27788
|
+
forbidDirectRuntimeExecution: true,
|
|
27789
|
+
forbidDirectThrows: true
|
|
27790
|
+
};
|
|
27755
27791
|
function nodeText(node) {
|
|
27756
27792
|
return node.getText(node.getSourceFile());
|
|
27757
27793
|
}
|
|
@@ -27797,7 +27833,7 @@ function descendantsOfKind(root, predicate) {
|
|
|
27797
27833
|
visit(root);
|
|
27798
27834
|
return result;
|
|
27799
27835
|
}
|
|
27800
|
-
async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
27836
|
+
async function analyzeProject(rootDir, include, cache, changedPaths, effectOptions = {}) {
|
|
27801
27837
|
const session = cache?.programSession ?? createIncrementalProgramSession(rootDir);
|
|
27802
27838
|
if (cache)
|
|
27803
27839
|
cache.programSession = session;
|
|
@@ -27816,8 +27852,21 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
27816
27852
|
moduleHandlerFiles: new Map,
|
|
27817
27853
|
infraResources: new Map,
|
|
27818
27854
|
infraResourceClasses: new Map,
|
|
27819
|
-
diagnostics: []
|
|
27855
|
+
diagnostics: [],
|
|
27856
|
+
effectOptions: {
|
|
27857
|
+
...DEFAULT_EFFECT_ANALYSIS_OPTIONS,
|
|
27858
|
+
requireTaggedErrorTypes: effectOptions.requireTaggedErrorTypes ?? DEFAULT_EFFECT_ANALYSIS_OPTIONS.requireTaggedErrorTypes,
|
|
27859
|
+
requireExactDependencyTypes: effectOptions.requireExactDependencyTypes ?? DEFAULT_EFFECT_ANALYSIS_OPTIONS.requireExactDependencyTypes,
|
|
27860
|
+
requireTimeoutForDependencies: effectOptions.requireTimeoutForDependencies ?? DEFAULT_EFFECT_ANALYSIS_OPTIONS.requireTimeoutForDependencies,
|
|
27861
|
+
forbidDirectRuntimeExecution: effectOptions.forbidDirectRuntimeExecution ?? DEFAULT_EFFECT_ANALYSIS_OPTIONS.forbidDirectRuntimeExecution,
|
|
27862
|
+
forbidDirectThrows: effectOptions.forbidDirectThrows ?? DEFAULT_EFFECT_ANALYSIS_OPTIONS.forbidDirectThrows
|
|
27863
|
+
}
|
|
27820
27864
|
};
|
|
27865
|
+
if (ctx.effectOptions.forbidDirectRuntimeExecution) {
|
|
27866
|
+
for (const sourceFile of sourceFiles) {
|
|
27867
|
+
scanEffectRuntimeEscapes(sourceFile, ctx);
|
|
27868
|
+
}
|
|
27869
|
+
}
|
|
27821
27870
|
const nativeTraitFiles = new Map;
|
|
27822
27871
|
for (const diagnostic of session.getDiagnostics()) {
|
|
27823
27872
|
ctx.diagnostics.push(toCompilerDiagnostic(diagnostic, rootDir));
|
|
@@ -28993,11 +29042,150 @@ function declaredClassType(declaration, ctx) {
|
|
|
28993
29042
|
return symbol ? ctx.checker.getDeclaredTypeOfSymbol(symbol) : undefined;
|
|
28994
29043
|
}
|
|
28995
29044
|
function typeArgumentsOf(type, ctx) {
|
|
28996
|
-
|
|
29045
|
+
if (!isTypeReference(type))
|
|
29046
|
+
return [];
|
|
29047
|
+
const argumentsFromChecker = ctx.checker.getTypeArguments(type);
|
|
29048
|
+
if (argumentsFromChecker.length > 0)
|
|
29049
|
+
return argumentsFromChecker;
|
|
29050
|
+
const reference = type;
|
|
29051
|
+
if (reference.resolvedTypeArguments && reference.resolvedTypeArguments.length > 0) {
|
|
29052
|
+
return reference.resolvedTypeArguments;
|
|
29053
|
+
}
|
|
29054
|
+
return reference.aliasTypeArguments ?? [];
|
|
28997
29055
|
}
|
|
28998
29056
|
function isTypeReference(type) {
|
|
28999
29057
|
return "target" in type;
|
|
29000
29058
|
}
|
|
29059
|
+
function effectTypeArgumentsOf(type, ctx) {
|
|
29060
|
+
if (!type)
|
|
29061
|
+
return;
|
|
29062
|
+
const reference = isTypeReference(type) ? type : undefined;
|
|
29063
|
+
const target = reference?.target ?? type;
|
|
29064
|
+
const symbol = target.aliasSymbol ?? target.getSymbol();
|
|
29065
|
+
const text = ctx.checker.typeToString(type, undefined, ts7.TypeFormatFlags.NoTruncation);
|
|
29066
|
+
if (symbol?.getName() !== "Effect" && !/^(?:Effect\.)?Effect</.test(text))
|
|
29067
|
+
return;
|
|
29068
|
+
const args = typeArgumentsOf(type, ctx);
|
|
29069
|
+
return args.length >= 3 ? [args[0], args[1], args[2]] : undefined;
|
|
29070
|
+
}
|
|
29071
|
+
function effectTypeName(type, location, ctx) {
|
|
29072
|
+
return ctx.checker.typeToString(type, location, ts7.TypeFormatFlags.NoTruncation);
|
|
29073
|
+
}
|
|
29074
|
+
function stringLiteralTypeValue(type) {
|
|
29075
|
+
return (type.flags & ts7.TypeFlags.StringLiteral) !== 0 ? type.value : undefined;
|
|
29076
|
+
}
|
|
29077
|
+
function effectFailureTags(type, location, ctx) {
|
|
29078
|
+
if ((type.flags & ts7.TypeFlags.Never) !== 0)
|
|
29079
|
+
return { tags: [], invalid: false };
|
|
29080
|
+
if ((type.flags & (ts7.TypeFlags.Any | ts7.TypeFlags.Unknown)) !== 0) {
|
|
29081
|
+
return { tags: [], invalid: true };
|
|
29082
|
+
}
|
|
29083
|
+
if (type.isUnion()) {
|
|
29084
|
+
return type.types.reduce((result, part) => {
|
|
29085
|
+
const next = effectFailureTags(part, location, ctx);
|
|
29086
|
+
result.tags.push(...next.tags);
|
|
29087
|
+
result.invalid ||= next.invalid;
|
|
29088
|
+
return result;
|
|
29089
|
+
}, { tags: [], invalid: false });
|
|
29090
|
+
}
|
|
29091
|
+
const apparent = ctx.checker.getApparentType(type);
|
|
29092
|
+
for (const propertyName of ["_tag", "tag"]) {
|
|
29093
|
+
const property = ctx.checker.getPropertyOfType(apparent, propertyName);
|
|
29094
|
+
if (!property)
|
|
29095
|
+
continue;
|
|
29096
|
+
const propertyType = ctx.checker.getTypeOfSymbolAtLocation(property, location);
|
|
29097
|
+
const tag = stringLiteralTypeValue(propertyType);
|
|
29098
|
+
if (tag)
|
|
29099
|
+
return { tags: [tag], invalid: false };
|
|
29100
|
+
}
|
|
29101
|
+
return { tags: [], invalid: true };
|
|
29102
|
+
}
|
|
29103
|
+
function effectEnvironmentNames(type, location, ctx) {
|
|
29104
|
+
if ((type.flags & ts7.TypeFlags.Never) !== 0)
|
|
29105
|
+
return { names: [], invalid: false };
|
|
29106
|
+
if ((type.flags & (ts7.TypeFlags.Any | ts7.TypeFlags.Unknown)) !== 0) {
|
|
29107
|
+
return { names: [], invalid: true };
|
|
29108
|
+
}
|
|
29109
|
+
if (type.isUnion() || type.isIntersection()) {
|
|
29110
|
+
return type.types.reduce((result, part) => {
|
|
29111
|
+
const next = effectEnvironmentNames(part, location, ctx);
|
|
29112
|
+
result.names.push(...next.names);
|
|
29113
|
+
result.invalid ||= next.invalid;
|
|
29114
|
+
return result;
|
|
29115
|
+
}, { names: [], invalid: false });
|
|
29116
|
+
}
|
|
29117
|
+
const literal = stringLiteralTypeValue(type);
|
|
29118
|
+
if (literal)
|
|
29119
|
+
return { names: [literal], invalid: false };
|
|
29120
|
+
const symbolName = type.getSymbol()?.getName();
|
|
29121
|
+
if (symbolName && symbolName !== "__type" && symbolName !== "__object") {
|
|
29122
|
+
return { names: [symbolName], invalid: false };
|
|
29123
|
+
}
|
|
29124
|
+
const text = effectTypeName(type, location, ctx).trim();
|
|
29125
|
+
if (text && text !== "object")
|
|
29126
|
+
return { names: [text], invalid: false };
|
|
29127
|
+
return { names: [], invalid: true };
|
|
29128
|
+
}
|
|
29129
|
+
function sortedUnique(values) {
|
|
29130
|
+
return [...new Set(values)].sort((left, right) => left.localeCompare(right, "en"));
|
|
29131
|
+
}
|
|
29132
|
+
function pushEffectDiagnostic(ctx, code, file, line, message, suggestion) {
|
|
29133
|
+
const metadata = COMPILER_DIAGNOSTIC_CODES[code];
|
|
29134
|
+
ctx.diagnostics.push({
|
|
29135
|
+
severity: "error",
|
|
29136
|
+
code,
|
|
29137
|
+
...metadata ? { errorCode: metadata.code, docsUrl: metadata.docsUrl } : {},
|
|
29138
|
+
file,
|
|
29139
|
+
line,
|
|
29140
|
+
message,
|
|
29141
|
+
suggestion
|
|
29142
|
+
});
|
|
29143
|
+
}
|
|
29144
|
+
function scanEffectRuntimeEscapes(sourceFile, ctx) {
|
|
29145
|
+
const relativeFile = sourcePath(ctx.rootDir, sourceFile.fileName);
|
|
29146
|
+
if (/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(relativeFile))
|
|
29147
|
+
return;
|
|
29148
|
+
const namespaceBindings = new Set;
|
|
29149
|
+
const runtimeBindings = new Set;
|
|
29150
|
+
for (const statement of sourceFile.statements) {
|
|
29151
|
+
if (!ts7.isImportDeclaration(statement) || !ts7.isStringLiteral(statement.moduleSpecifier))
|
|
29152
|
+
continue;
|
|
29153
|
+
const moduleName = statement.moduleSpecifier.text;
|
|
29154
|
+
if (moduleName !== "effect" && moduleName !== "effect/Effect" && moduleName !== "effect/Runtime")
|
|
29155
|
+
continue;
|
|
29156
|
+
const clause = statement.importClause;
|
|
29157
|
+
if (!clause)
|
|
29158
|
+
continue;
|
|
29159
|
+
if (clause.name)
|
|
29160
|
+
namespaceBindings.add(clause.name.text);
|
|
29161
|
+
if (clause.namedBindings && ts7.isNamespaceImport(clause.namedBindings)) {
|
|
29162
|
+
namespaceBindings.add(clause.namedBindings.name.text);
|
|
29163
|
+
} else if (clause.namedBindings && ts7.isNamedImports(clause.namedBindings)) {
|
|
29164
|
+
for (const element of clause.namedBindings.elements) {
|
|
29165
|
+
const imported = element.propertyName?.text ?? element.name.text;
|
|
29166
|
+
if (moduleName === "effect" && (imported === "Effect" || imported === "Runtime")) {
|
|
29167
|
+
namespaceBindings.add(element.name.text);
|
|
29168
|
+
}
|
|
29169
|
+
if (["runPromise", "runPromiseExit", "runSync", "runFork"].includes(imported)) {
|
|
29170
|
+
runtimeBindings.add(element.name.text);
|
|
29171
|
+
}
|
|
29172
|
+
}
|
|
29173
|
+
}
|
|
29174
|
+
}
|
|
29175
|
+
if (namespaceBindings.size === 0 && runtimeBindings.size === 0)
|
|
29176
|
+
return;
|
|
29177
|
+
for (const call of descendantsOfKind(sourceFile, ts7.isCallExpression)) {
|
|
29178
|
+
let runtimeName;
|
|
29179
|
+
if (ts7.isPropertyAccessExpression(call.expression) && namespaceBindings.has(call.expression.expression.getText(sourceFile)) && ["runPromise", "runPromiseExit", "runSync", "runFork"].includes(call.expression.name.text)) {
|
|
29180
|
+
runtimeName = call.expression.name.text;
|
|
29181
|
+
} else if (ts7.isIdentifier(call.expression) && runtimeBindings.has(call.expression.text)) {
|
|
29182
|
+
runtimeName = call.expression.text;
|
|
29183
|
+
}
|
|
29184
|
+
if (!runtimeName)
|
|
29185
|
+
continue;
|
|
29186
|
+
pushEffectDiagnostic(ctx, "effect-direct-runtime-execution", sourcePath(ctx.rootDir, sourceFile.fileName), lineOf(call), `Direct ${runtimeName} execution escapes the Effect boundary.`, "Return the Effect program to the Elysia boundary; only the framework adapter may interpret it.");
|
|
29187
|
+
}
|
|
29188
|
+
}
|
|
29001
29189
|
function isUnknownOrAny(type) {
|
|
29002
29190
|
return (type.flags & (ts7.TypeFlags.Any | ts7.TypeFlags.Unknown)) !== 0;
|
|
29003
29191
|
}
|
|
@@ -29061,7 +29249,8 @@ function parseController(input, ctx) {
|
|
|
29061
29249
|
handler: propertyName(method.name)
|
|
29062
29250
|
};
|
|
29063
29251
|
const signature = ctx.checker.getSignatureFromDeclaration(method);
|
|
29064
|
-
const
|
|
29252
|
+
const returnType = signature?.getReturnType();
|
|
29253
|
+
const resultType = returnType && ctx.checker.typeToString(returnType);
|
|
29065
29254
|
if (resultType && /\bResponse\b/.test(resultType))
|
|
29066
29255
|
route.nativeResponse = true;
|
|
29067
29256
|
const pathParams = [];
|
|
@@ -29289,6 +29478,235 @@ function parseController(input, ctx) {
|
|
|
29289
29478
|
Object.assign(route.contract, { [field]: value });
|
|
29290
29479
|
}
|
|
29291
29480
|
}
|
|
29481
|
+
const effectExpr = getProp(optionsObject, "effect");
|
|
29482
|
+
if (effectExpr) {
|
|
29483
|
+
const effectObject = resolveStaticObjectLiteral(effectExpr, ctx);
|
|
29484
|
+
if (!effectObject) {
|
|
29485
|
+
ctx.diagnostics.push({
|
|
29486
|
+
severity: "error",
|
|
29487
|
+
code: "invalid-effect-contract",
|
|
29488
|
+
errorCode: "SC3030",
|
|
29489
|
+
docsUrl: "https://supacloud.dev/errors/SC3030",
|
|
29490
|
+
file,
|
|
29491
|
+
line: lineOf(effectExpr),
|
|
29492
|
+
message: `Route ${route.handler} effect must be a statically resolvable object literal.`,
|
|
29493
|
+
suggestion: "Use effect: { required: true, errors: [...], dependencies: [...] }."
|
|
29494
|
+
});
|
|
29495
|
+
} else {
|
|
29496
|
+
const effect = { required: true };
|
|
29497
|
+
const requiredExpr = getProp(effectObject, "required");
|
|
29498
|
+
if (requiredExpr !== undefined && booleanProp(effectObject, "required") !== true) {
|
|
29499
|
+
ctx.diagnostics.push({
|
|
29500
|
+
severity: "error",
|
|
29501
|
+
code: "invalid-effect-contract",
|
|
29502
|
+
errorCode: "SC3030",
|
|
29503
|
+
docsUrl: "https://supacloud.dev/errors/SC3030",
|
|
29504
|
+
file,
|
|
29505
|
+
line: lineOf(requiredExpr),
|
|
29506
|
+
message: `Route ${route.handler} effect.required must be the literal true.`
|
|
29507
|
+
});
|
|
29508
|
+
}
|
|
29509
|
+
const retry = stringLiteralProp(effectObject, "retry");
|
|
29510
|
+
if (retry !== undefined) {
|
|
29511
|
+
if (retry !== "none" && retry !== "explicit") {
|
|
29512
|
+
ctx.diagnostics.push({
|
|
29513
|
+
severity: "error",
|
|
29514
|
+
code: "invalid-effect-retry",
|
|
29515
|
+
errorCode: "SC3031",
|
|
29516
|
+
docsUrl: "https://supacloud.dev/errors/SC3031",
|
|
29517
|
+
file,
|
|
29518
|
+
line: lineOf(getProp(effectObject, "retry")),
|
|
29519
|
+
message: `Route ${route.handler} effect.retry must be "none" or "explicit".`
|
|
29520
|
+
});
|
|
29521
|
+
} else {
|
|
29522
|
+
effect.retry = retry;
|
|
29523
|
+
}
|
|
29524
|
+
}
|
|
29525
|
+
const maxAttemptsExpr = getProp(effectObject, "maxAttempts");
|
|
29526
|
+
if (maxAttemptsExpr !== undefined) {
|
|
29527
|
+
const maxAttempts = numberLiteralProp(effectObject, "maxAttempts");
|
|
29528
|
+
if (maxAttempts === undefined || maxAttempts < 1 || !Number.isInteger(maxAttempts)) {
|
|
29529
|
+
ctx.diagnostics.push({
|
|
29530
|
+
severity: "error",
|
|
29531
|
+
code: "invalid-effect-retry",
|
|
29532
|
+
errorCode: "SC3031",
|
|
29533
|
+
docsUrl: "https://supacloud.dev/errors/SC3031",
|
|
29534
|
+
file,
|
|
29535
|
+
line: lineOf(maxAttemptsExpr),
|
|
29536
|
+
message: `Route ${route.handler} effect.maxAttempts must be a positive integer.`
|
|
29537
|
+
});
|
|
29538
|
+
} else {
|
|
29539
|
+
effect.maxAttempts = maxAttempts;
|
|
29540
|
+
}
|
|
29541
|
+
}
|
|
29542
|
+
if (effect.retry === "explicit" && effect.maxAttempts === undefined) {
|
|
29543
|
+
ctx.diagnostics.push({
|
|
29544
|
+
severity: "error",
|
|
29545
|
+
code: "invalid-effect-retry",
|
|
29546
|
+
errorCode: "SC3031",
|
|
29547
|
+
docsUrl: "https://supacloud.dev/errors/SC3031",
|
|
29548
|
+
file,
|
|
29549
|
+
line: lineOf(effectExpr),
|
|
29550
|
+
message: `Route ${route.handler} effect.maxAttempts is required when retry is "explicit".`
|
|
29551
|
+
});
|
|
29552
|
+
}
|
|
29553
|
+
const timeoutExpr = getProp(effectObject, "timeoutMs");
|
|
29554
|
+
if (timeoutExpr !== undefined) {
|
|
29555
|
+
const timeoutMs = numberLiteralProp(effectObject, "timeoutMs");
|
|
29556
|
+
if (timeoutMs === undefined || timeoutMs <= 0 || !Number.isInteger(timeoutMs)) {
|
|
29557
|
+
ctx.diagnostics.push({
|
|
29558
|
+
severity: "error",
|
|
29559
|
+
code: "invalid-effect-timeout",
|
|
29560
|
+
errorCode: "SC3032",
|
|
29561
|
+
docsUrl: "https://supacloud.dev/errors/SC3032",
|
|
29562
|
+
file,
|
|
29563
|
+
line: lineOf(timeoutExpr),
|
|
29564
|
+
message: `Route ${route.handler} effect.timeoutMs must be a positive integer.`
|
|
29565
|
+
});
|
|
29566
|
+
} else {
|
|
29567
|
+
effect.timeoutMs = timeoutMs;
|
|
29568
|
+
}
|
|
29569
|
+
}
|
|
29570
|
+
const dependenciesExpr = getProp(effectObject, "dependencies");
|
|
29571
|
+
if (dependenciesExpr !== undefined) {
|
|
29572
|
+
if (!ts7.isArrayLiteralExpression(dependenciesExpr) || dependenciesExpr.elements.some((element) => !ts7.isStringLiteral(element) || element.text.trim().length === 0)) {
|
|
29573
|
+
ctx.diagnostics.push({
|
|
29574
|
+
severity: "error",
|
|
29575
|
+
code: "invalid-effect-dependencies",
|
|
29576
|
+
errorCode: "SC3033",
|
|
29577
|
+
docsUrl: "https://supacloud.dev/errors/SC3033",
|
|
29578
|
+
file,
|
|
29579
|
+
line: lineOf(dependenciesExpr),
|
|
29580
|
+
message: `Route ${route.handler} effect.dependencies must be an explicit string array.`
|
|
29581
|
+
});
|
|
29582
|
+
} else {
|
|
29583
|
+
effect.dependencies = dependenciesExpr.elements.map((element) => element.text);
|
|
29584
|
+
if (new Set(effect.dependencies).size !== effect.dependencies.length) {
|
|
29585
|
+
ctx.diagnostics.push({
|
|
29586
|
+
severity: "error",
|
|
29587
|
+
code: "invalid-effect-dependencies",
|
|
29588
|
+
errorCode: "SC3033",
|
|
29589
|
+
docsUrl: "https://supacloud.dev/errors/SC3033",
|
|
29590
|
+
file,
|
|
29591
|
+
line: lineOf(dependenciesExpr),
|
|
29592
|
+
message: `Route ${route.handler} effect.dependencies must not contain duplicate service names.`,
|
|
29593
|
+
suggestion: "List each logical Effect dependency exactly once."
|
|
29594
|
+
});
|
|
29595
|
+
}
|
|
29596
|
+
}
|
|
29597
|
+
}
|
|
29598
|
+
const errorsExpr = getProp(effectObject, "errors");
|
|
29599
|
+
if (errorsExpr !== undefined) {
|
|
29600
|
+
if (!ts7.isArrayLiteralExpression(errorsExpr)) {
|
|
29601
|
+
ctx.diagnostics.push({
|
|
29602
|
+
severity: "error",
|
|
29603
|
+
code: "invalid-effect-errors",
|
|
29604
|
+
errorCode: "SC3034",
|
|
29605
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
29606
|
+
file,
|
|
29607
|
+
line: lineOf(errorsExpr),
|
|
29608
|
+
message: `Route ${route.handler} effect.errors must be an explicit object array.`
|
|
29609
|
+
});
|
|
29610
|
+
} else {
|
|
29611
|
+
const errors = [];
|
|
29612
|
+
for (const errorExpr of errorsExpr.elements) {
|
|
29613
|
+
if (!ts7.isObjectLiteralExpression(errorExpr)) {
|
|
29614
|
+
ctx.diagnostics.push({
|
|
29615
|
+
severity: "error",
|
|
29616
|
+
code: "invalid-effect-errors",
|
|
29617
|
+
errorCode: "SC3034",
|
|
29618
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
29619
|
+
file,
|
|
29620
|
+
line: lineOf(errorExpr),
|
|
29621
|
+
message: `Route ${route.handler} effect.errors entries must be object literals.`
|
|
29622
|
+
});
|
|
29623
|
+
continue;
|
|
29624
|
+
}
|
|
29625
|
+
const tag = stringLiteralProp(errorExpr, "tag");
|
|
29626
|
+
const code = stringLiteralProp(errorExpr, "code");
|
|
29627
|
+
const status = numberLiteralProp(errorExpr, "status");
|
|
29628
|
+
const message = stringLiteralProp(errorExpr, "message");
|
|
29629
|
+
if (!tag || !code || status === undefined || !Number.isInteger(status) || status < 400 || status > 599) {
|
|
29630
|
+
ctx.diagnostics.push({
|
|
29631
|
+
severity: "error",
|
|
29632
|
+
code: "invalid-effect-errors",
|
|
29633
|
+
errorCode: "SC3034",
|
|
29634
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
29635
|
+
file,
|
|
29636
|
+
line: lineOf(errorExpr),
|
|
29637
|
+
message: `Route ${route.handler} effect.errors entries require tag, code and an HTTP status from 400 to 599.`
|
|
29638
|
+
});
|
|
29639
|
+
continue;
|
|
29640
|
+
}
|
|
29641
|
+
errors.push({ tag, code, status, ...message === undefined ? {} : { message } });
|
|
29642
|
+
}
|
|
29643
|
+
effect.errors = errors;
|
|
29644
|
+
if (new Set(errors.map((entry) => entry.tag)).size !== errors.length) {
|
|
29645
|
+
ctx.diagnostics.push({
|
|
29646
|
+
severity: "error",
|
|
29647
|
+
code: "invalid-effect-errors",
|
|
29648
|
+
errorCode: "SC3034",
|
|
29649
|
+
docsUrl: "https://supacloud.dev/errors/SC3034",
|
|
29650
|
+
file,
|
|
29651
|
+
line: lineOf(errorsExpr),
|
|
29652
|
+
message: `Route ${route.handler} effect.errors must not contain duplicate tags.`,
|
|
29653
|
+
suggestion: "Map each tagged failure exactly once."
|
|
29654
|
+
});
|
|
29655
|
+
}
|
|
29656
|
+
}
|
|
29657
|
+
}
|
|
29658
|
+
route.effect = effect;
|
|
29659
|
+
const effectArguments = effectTypeArgumentsOf(returnType, ctx);
|
|
29660
|
+
const promiseReturn = Boolean(resultType && /\bPromise(?:<|\s)/.test(resultType));
|
|
29661
|
+
if (promiseReturn) {
|
|
29662
|
+
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.");
|
|
29663
|
+
} else if (!effectArguments) {
|
|
29664
|
+
ctx.diagnostics.push({
|
|
29665
|
+
severity: "error",
|
|
29666
|
+
code: "effect-return-type",
|
|
29667
|
+
errorCode: "SC3035",
|
|
29668
|
+
docsUrl: "https://supacloud.dev/errors/SC3035",
|
|
29669
|
+
file,
|
|
29670
|
+
line: lineOf(method),
|
|
29671
|
+
message: `Route ${route.handler} declares an Effect contract but returns ${resultType ?? "an unresolved type"}.`,
|
|
29672
|
+
suggestion: "Return Effect.Effect<Success, Failure, Requirements> from the handler."
|
|
29673
|
+
});
|
|
29674
|
+
}
|
|
29675
|
+
if (effectArguments) {
|
|
29676
|
+
const [, failureType, environmentType] = effectArguments;
|
|
29677
|
+
const failure = effectFailureTags(failureType, method, ctx);
|
|
29678
|
+
if (ctx.effectOptions.requireTaggedErrorTypes && failure.invalid) {
|
|
29679
|
+
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 ${effectTypeName(failureType, method, ctx)}.`, "Replace unknown/any with never or a union of objects carrying a literal _tag.");
|
|
29680
|
+
} else if (ctx.effectOptions.requireTaggedErrorTypes && effect.errors !== undefined && !failure.invalid) {
|
|
29681
|
+
const declaredTags = sortedUnique(effect.errors.map((entry) => entry.tag));
|
|
29682
|
+
const typedTags = sortedUnique(failure.tags);
|
|
29683
|
+
if (declaredTags.join("\x00") !== typedTags.join("\x00")) {
|
|
29684
|
+
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.");
|
|
29685
|
+
}
|
|
29686
|
+
}
|
|
29687
|
+
const environment = effectEnvironmentNames(environmentType, method, ctx);
|
|
29688
|
+
if (ctx.effectOptions.requireExactDependencyTypes && effect.dependencies !== undefined) {
|
|
29689
|
+
if (environment.invalid) {
|
|
29690
|
+
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.");
|
|
29691
|
+
} else {
|
|
29692
|
+
const declaredDependencies = sortedUnique(effect.dependencies);
|
|
29693
|
+
const typedDependencies = sortedUnique(environment.names);
|
|
29694
|
+
if (declaredDependencies.join("\x00") !== typedDependencies.join("\x00")) {
|
|
29695
|
+
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.");
|
|
29696
|
+
}
|
|
29697
|
+
}
|
|
29698
|
+
}
|
|
29699
|
+
if (ctx.effectOptions.requireTimeoutForDependencies && (effect.dependencies?.length ?? 0) > 0 && effect.timeoutMs === undefined) {
|
|
29700
|
+
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.");
|
|
29701
|
+
}
|
|
29702
|
+
}
|
|
29703
|
+
if (ctx.effectOptions.forbidDirectThrows && method.body) {
|
|
29704
|
+
for (const throwStatement of descendantsOfKind(method.body, ts7.isThrowStatement)) {
|
|
29705
|
+
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.");
|
|
29706
|
+
}
|
|
29707
|
+
}
|
|
29708
|
+
}
|
|
29709
|
+
}
|
|
29292
29710
|
for (const field of ["body", "params", "query", "headers", "cookie", "response"]) {
|
|
29293
29711
|
const schemaExpr = getProp(optionsObject, field);
|
|
29294
29712
|
if (schemaExpr && ts7.isIdentifier(schemaExpr)) {
|
|
@@ -29872,6 +30290,13 @@ function stringLiteralProp(obj, name) {
|
|
|
29872
30290
|
const expr = getProp(obj, name);
|
|
29873
30291
|
return expr && ts7.isStringLiteral(expr) ? expr.text : undefined;
|
|
29874
30292
|
}
|
|
30293
|
+
function numberLiteralProp(obj, name) {
|
|
30294
|
+
const expr = getProp(obj, name);
|
|
30295
|
+
if (!expr || !ts7.isNumericLiteral(expr))
|
|
30296
|
+
return;
|
|
30297
|
+
const value = Number(expr.text);
|
|
30298
|
+
return Number.isFinite(value) ? value : undefined;
|
|
30299
|
+
}
|
|
29875
30300
|
function arrayProp(obj, name) {
|
|
29876
30301
|
const expr = getProp(obj, name);
|
|
29877
30302
|
return expr && ts7.isArrayLiteralExpression(expr) ? [...expr.elements] : [];
|
|
@@ -30535,6 +30960,57 @@ function validateClientSchemaBoundaries(graph, options) {
|
|
|
30535
30960
|
return diagnostics;
|
|
30536
30961
|
}
|
|
30537
30962
|
|
|
30963
|
+
// src/effect-governance.ts
|
|
30964
|
+
init_validate();
|
|
30965
|
+
function diagnostic(code, message, file, suggestion) {
|
|
30966
|
+
const metadata = COMPILER_DIAGNOSTIC_CODES[code];
|
|
30967
|
+
return {
|
|
30968
|
+
severity: "error",
|
|
30969
|
+
code,
|
|
30970
|
+
message,
|
|
30971
|
+
...file === undefined ? {} : { file },
|
|
30972
|
+
...suggestion === undefined ? {} : { suggestion },
|
|
30973
|
+
...metadata ? { errorCode: metadata.code, docsUrl: metadata.docsUrl } : {}
|
|
30974
|
+
};
|
|
30975
|
+
}
|
|
30976
|
+
function routeLabel(route, controllerPath) {
|
|
30977
|
+
return `${route.method} ${controllerPath}${route.path}`;
|
|
30978
|
+
}
|
|
30979
|
+
function validateEffectPolicies(graph, options = {}) {
|
|
30980
|
+
const diagnostics = [];
|
|
30981
|
+
for (const module of graph.modules) {
|
|
30982
|
+
const commands = new Map(module.commands.map((command) => [command.className, command]));
|
|
30983
|
+
for (const controller of module.controllers) {
|
|
30984
|
+
for (const route of controller.routes) {
|
|
30985
|
+
const label = routeLabel(route, controller.path);
|
|
30986
|
+
if (options.requireRouteEffects && route.effect === undefined) {
|
|
30987
|
+
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.'));
|
|
30988
|
+
continue;
|
|
30989
|
+
}
|
|
30990
|
+
const effect = route.effect;
|
|
30991
|
+
if (effect === undefined)
|
|
30992
|
+
continue;
|
|
30993
|
+
if (options.requireErrorMappings && effect.errors === undefined) {
|
|
30994
|
+
diagnostics.push(diagnostic("effect-error-mapping-required", `Route ${label} declares an Effect contract without public error mappings.`, controller.file, "Map every expected domain failure to a stable HTTP status and public code."));
|
|
30995
|
+
}
|
|
30996
|
+
if (options.requireDependencies && effect.dependencies === undefined) {
|
|
30997
|
+
diagnostics.push(diagnostic("effect-dependencies-required", `Route ${label} declares an Effect contract without dependencies.`, controller.file, "Declare the logical services required by the Effect environment."));
|
|
30998
|
+
}
|
|
30999
|
+
if (effect.retry === "explicit" && effect.maxAttempts === undefined) {
|
|
31000
|
+
diagnostics.push(diagnostic("invalid-effect-retry", `Route ${label} enables explicit Effect retry without maxAttempts.`, controller.file, "Declare a positive maxAttempts value including the initial attempt."));
|
|
31001
|
+
}
|
|
31002
|
+
if (route.command && effect.retry === "explicit") {
|
|
31003
|
+
const command = commands.get(route.command);
|
|
31004
|
+
if (command && command.idempotency !== "required") {
|
|
31005
|
+
diagnostics.push(diagnostic("effect-command-retry-forbidden", `Route ${label} enables explicit Effect retry for non-idempotent command "${command.name}".`, controller.file, 'Set command idempotency to "required" or use effect.retry: "none".'));
|
|
31006
|
+
}
|
|
31007
|
+
}
|
|
31008
|
+
}
|
|
31009
|
+
}
|
|
31010
|
+
}
|
|
31011
|
+
return diagnostics;
|
|
31012
|
+
}
|
|
31013
|
+
|
|
30538
31014
|
// src/compile.ts
|
|
30539
31015
|
function withDefaultGovernance(options) {
|
|
30540
31016
|
return options.commandCapabilities === undefined ? { ...options, commandCapabilities: {
|
|
@@ -30550,11 +31026,12 @@ async function renderOptionalGraphql(options) {
|
|
|
30550
31026
|
}
|
|
30551
31027
|
async function compileProject(options) {
|
|
30552
31028
|
options = withDefaultGovernance(options);
|
|
30553
|
-
const graph = await analyzeProject(options.rootDir, options.include, options.cache, options.changedPaths);
|
|
31029
|
+
const graph = await analyzeProject(options.rootDir, options.include, options.cache, options.changedPaths, options.effect);
|
|
30554
31030
|
const diagnostics = [
|
|
30555
31031
|
...graph.diagnostics ?? [],
|
|
30556
31032
|
...validateGraph(graph, options),
|
|
30557
|
-
...validateClientSchemaBoundaries(graph, options)
|
|
31033
|
+
...validateClientSchemaBoundaries(graph, options),
|
|
31034
|
+
...validateEffectPolicies(graph, options.effect)
|
|
30558
31035
|
];
|
|
30559
31036
|
if (diagnostics.some((item) => item.code === "runtime-injection-disallowed")) {
|
|
30560
31037
|
return { diagnostics, graph, written: [] };
|
|
@@ -30614,11 +31091,12 @@ async function compileProject(options) {
|
|
|
30614
31091
|
}
|
|
30615
31092
|
async function checkProject(options) {
|
|
30616
31093
|
options = withDefaultGovernance(options);
|
|
30617
|
-
const graph = await analyzeProject(options.rootDir, options.include, options.cache, options.changedPaths);
|
|
31094
|
+
const graph = await analyzeProject(options.rootDir, options.include, options.cache, options.changedPaths, options.effect);
|
|
30618
31095
|
const diagnostics = [
|
|
30619
31096
|
...graph.diagnostics ?? [],
|
|
30620
31097
|
...validateGraph(graph, options),
|
|
30621
|
-
...validateClientSchemaBoundaries(graph, options)
|
|
31098
|
+
...validateClientSchemaBoundaries(graph, options),
|
|
31099
|
+
...validateEffectPolicies(graph, options.effect)
|
|
30622
31100
|
];
|
|
30623
31101
|
if (diagnostics.some((item) => item.code === "runtime-injection-disallowed")) {
|
|
30624
31102
|
return { diagnostics, graph, upToDate: false, mismatches: ["Runtime DI must be migrated to constructor injection."] };
|
|
@@ -30704,7 +31182,7 @@ init_delivery_schema();
|
|
|
30704
31182
|
init_validate();
|
|
30705
31183
|
var compare2 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
30706
31184
|
var sorted = (values) => [...new Set(values)].sort(compare2);
|
|
30707
|
-
function
|
|
31185
|
+
function diagnostic2(error) {
|
|
30708
31186
|
return {
|
|
30709
31187
|
severity: error.severity,
|
|
30710
31188
|
code: error.code,
|
|
@@ -30729,8 +31207,8 @@ function createDeliveryPlan(graph, input) {
|
|
|
30729
31207
|
}] };
|
|
30730
31208
|
}
|
|
30731
31209
|
const diagnostics = [
|
|
30732
|
-
...(graph.diagnostics ?? []).map(
|
|
30733
|
-
...validateGraph(graph, { strict: true }).map(
|
|
31210
|
+
...(graph.diagnostics ?? []).map(diagnostic2),
|
|
31211
|
+
...validateGraph(graph, { strict: true }).map(diagnostic2)
|
|
30734
31212
|
];
|
|
30735
31213
|
const fail = (code, message, suggestion) => {
|
|
30736
31214
|
diagnostics.push({ severity: "error", code, message, suggestion });
|
|
@@ -32264,6 +32742,7 @@ function optionsKeyOf(options) {
|
|
|
32264
32742
|
openApi: options.openApi,
|
|
32265
32743
|
generatePermissions: options.generatePermissions,
|
|
32266
32744
|
typeSafety: options.typeSafety,
|
|
32745
|
+
effect: options.effect,
|
|
32267
32746
|
treeShakeUnusedProviders: options.treeShakeUnusedProviders,
|
|
32268
32747
|
graphql: options.graphql
|
|
32269
32748
|
});
|
|
@@ -32573,10 +33052,10 @@ import { readFileSync as readFileSync4 } from "node:fs";
|
|
|
32573
33052
|
import { resolve as resolve17 } from "node:path";
|
|
32574
33053
|
|
|
32575
33054
|
// src/migration-app-version.ts
|
|
32576
|
-
var MIGRATION_APP_VERSION = "0.
|
|
33055
|
+
var MIGRATION_APP_VERSION = "0.24.0";
|
|
32577
33056
|
|
|
32578
33057
|
// src/migration-elysia-version.ts
|
|
32579
|
-
var MIGRATION_ELYSIA_VERSION = "0.
|
|
33058
|
+
var MIGRATION_ELYSIA_VERSION = "0.29.0";
|
|
32580
33059
|
|
|
32581
33060
|
// src/migration-policy.ts
|
|
32582
33061
|
var cachedCompilerVersion;
|
|
@@ -33746,7 +34225,17 @@ var DEFAULT_SUPACLOUD_CONFIG = {
|
|
|
33746
34225
|
idempotency: true,
|
|
33747
34226
|
transaction: true
|
|
33748
34227
|
},
|
|
33749
|
-
moduleBoundaryPreset: "modular-monolith"
|
|
34228
|
+
moduleBoundaryPreset: "modular-monolith",
|
|
34229
|
+
effect: {
|
|
34230
|
+
requireRouteEffects: true,
|
|
34231
|
+
requireErrorMappings: true,
|
|
34232
|
+
requireDependencies: true,
|
|
34233
|
+
requireTaggedErrorTypes: true,
|
|
34234
|
+
requireExactDependencyTypes: true,
|
|
34235
|
+
requireTimeoutForDependencies: true,
|
|
34236
|
+
forbidDirectRuntimeExecution: true,
|
|
34237
|
+
forbidDirectThrows: true
|
|
34238
|
+
}
|
|
33750
34239
|
};
|
|
33751
34240
|
function defineSupacloudConfig(config = {}) {
|
|
33752
34241
|
if (config.delivery !== undefined)
|
|
@@ -33758,7 +34247,8 @@ function defineSupacloudConfig(config = {}) {
|
|
|
33758
34247
|
...DEFAULT_SUPACLOUD_CONFIG,
|
|
33759
34248
|
...config,
|
|
33760
34249
|
include: config.include ?? [...DEFAULT_SUPACLOUD_CONFIG.include],
|
|
33761
|
-
graphql: config.graphql ?? DEFAULT_SUPACLOUD_CONFIG.graphql
|
|
34250
|
+
graphql: config.graphql ?? DEFAULT_SUPACLOUD_CONFIG.graphql,
|
|
34251
|
+
effect: config.effect === undefined ? { ...DEFAULT_SUPACLOUD_CONFIG.effect } : { ...DEFAULT_SUPACLOUD_CONFIG.effect, ...config.effect }
|
|
33762
34252
|
};
|
|
33763
34253
|
}
|
|
33764
34254
|
function validateGovernanceConfig(config) {
|
|
@@ -33815,6 +34305,34 @@ function validateGovernanceConfig(config) {
|
|
|
33815
34305
|
if (config[key] !== undefined && typeof config[key] !== "boolean")
|
|
33816
34306
|
throw new Error(`${key} must be a boolean.`);
|
|
33817
34307
|
}
|
|
34308
|
+
if (config.effect !== undefined) {
|
|
34309
|
+
if (!isRecord(config.effect) || Object.keys(config.effect).some((key) => ![
|
|
34310
|
+
"requireRouteEffects",
|
|
34311
|
+
"requireErrorMappings",
|
|
34312
|
+
"requireDependencies",
|
|
34313
|
+
"requireTaggedErrorTypes",
|
|
34314
|
+
"requireExactDependencyTypes",
|
|
34315
|
+
"requireTimeoutForDependencies",
|
|
34316
|
+
"forbidDirectRuntimeExecution",
|
|
34317
|
+
"forbidDirectThrows"
|
|
34318
|
+
].includes(key))) {
|
|
34319
|
+
throw new Error("effect contains an unsupported governance option.");
|
|
34320
|
+
}
|
|
34321
|
+
for (const key of [
|
|
34322
|
+
"requireRouteEffects",
|
|
34323
|
+
"requireErrorMappings",
|
|
34324
|
+
"requireDependencies",
|
|
34325
|
+
"requireTaggedErrorTypes",
|
|
34326
|
+
"requireExactDependencyTypes",
|
|
34327
|
+
"requireTimeoutForDependencies",
|
|
34328
|
+
"forbidDirectRuntimeExecution",
|
|
34329
|
+
"forbidDirectThrows"
|
|
34330
|
+
]) {
|
|
34331
|
+
if (config.effect[key] !== undefined && typeof config.effect[key] !== "boolean") {
|
|
34332
|
+
throw new Error(`effect.${key} must be boolean.`);
|
|
34333
|
+
}
|
|
34334
|
+
}
|
|
34335
|
+
}
|
|
33818
34336
|
}
|
|
33819
34337
|
function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
|
|
33820
34338
|
const resolved = defineSupacloudConfig(config);
|
|
@@ -33835,6 +34353,7 @@ function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
|
|
|
33835
34353
|
...resolved.allowRouteCommandBindings === undefined ? {} : { allowRouteCommandBindings: resolved.allowRouteCommandBindings },
|
|
33836
34354
|
...resolved.disallowControllerDirectDb === undefined ? {} : { disallowControllerDirectDb: resolved.disallowControllerDirectDb },
|
|
33837
34355
|
...resolved.detectOrphanModules === undefined ? {} : { detectOrphanModules: resolved.detectOrphanModules },
|
|
34356
|
+
effect: resolved.effect === undefined ? { ...DEFAULT_SUPACLOUD_CONFIG.effect } : { ...DEFAULT_SUPACLOUD_CONFIG.effect, ...resolved.effect },
|
|
33838
34357
|
treeShakeUnusedProviders: resolved.treeShakeUnusedProviders ?? DEFAULT_SUPACLOUD_CONFIG.treeShakeUnusedProviders,
|
|
33839
34358
|
graphql: resolved.graphql ? {
|
|
33840
34359
|
...resolved.graphql,
|
|
@@ -34237,6 +34756,7 @@ export {
|
|
|
34237
34756
|
scanProductionSource,
|
|
34238
34757
|
schemaFromSource,
|
|
34239
34758
|
serializeOpenApiJson,
|
|
34759
|
+
validateEffectPolicies,
|
|
34240
34760
|
validateFeatureSpec,
|
|
34241
34761
|
validateGraph,
|
|
34242
34762
|
validateRouteContracts,
|