@supacloud/cli 0.73.0 → 0.74.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.
Files changed (2) hide show
  1. package/dist/index.js +312 -42
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -221509,6 +221509,13 @@ var init_validate = __esm2(() => {
221509
221509
  "effect-dependencies-required": { code: "SC3038", docsUrl: "https://supacloud.dev/errors/SC3038" },
221510
221510
  "effect-command-retry-forbidden": { code: "SC3039", docsUrl: "https://supacloud.dev/errors/SC3039" },
221511
221511
  "effect-dependency-type": { code: "SC3040", docsUrl: "https://supacloud.dev/errors/SC3040" },
221512
+ "effect-error-type": { code: "SC3041", docsUrl: "https://supacloud.dev/errors/SC3041" },
221513
+ "effect-error-mapping-type": { code: "SC3042", docsUrl: "https://supacloud.dev/errors/SC3042" },
221514
+ "effect-dependency-mismatch": { code: "SC3043", docsUrl: "https://supacloud.dev/errors/SC3043" },
221515
+ "effect-timeout-required": { code: "SC3044", docsUrl: "https://supacloud.dev/errors/SC3044" },
221516
+ "effect-direct-runtime-execution": { code: "SC3045", docsUrl: "https://supacloud.dev/errors/SC3045" },
221517
+ "effect-direct-throw": { code: "SC3046", docsUrl: "https://supacloud.dev/errors/SC3046" },
221518
+ "effect-promise-return": { code: "SC3047", docsUrl: "https://supacloud.dev/errors/SC3047" },
221512
221519
  "invalid-route-response-map": { code: "SC3024", docsUrl: "https://supacloud.dev/errors/SC3024" },
221513
221520
  "invalid-route-response-selector": { code: "SC3025", docsUrl: "https://supacloud.dev/errors/SC3025" },
221514
221521
  "conflicting-route-response-schema": { code: "SC3026", docsUrl: "https://supacloud.dev/errors/SC3026" },
@@ -244750,6 +244757,13 @@ function withDiagnosticMetadata(diagnostic) {
244750
244757
  const metadata = COMPILER_DIAGNOSTIC_CODES[diagnostic.code];
244751
244758
  return metadata ? { ...diagnostic, errorCode: metadata.code, docsUrl: metadata.docsUrl } : diagnostic;
244752
244759
  }
244760
+ var DEFAULT_EFFECT_ANALYSIS_OPTIONS = {
244761
+ requireTaggedErrorTypes: true,
244762
+ requireExactDependencyTypes: true,
244763
+ requireTimeoutForDependencies: true,
244764
+ forbidDirectRuntimeExecution: true,
244765
+ forbidDirectThrows: true
244766
+ };
244753
244767
  function nodeText(node) {
244754
244768
  return node.getText(node.getSourceFile());
244755
244769
  }
@@ -244795,7 +244809,7 @@ function descendantsOfKind(root, predicate) {
244795
244809
  visit(root);
244796
244810
  return result;
244797
244811
  }
244798
- async function analyzeProject(rootDir, include, cache, changedPaths) {
244812
+ async function analyzeProject(rootDir, include, cache, changedPaths, effectOptions = {}) {
244799
244813
  const session = cache?.programSession ?? createIncrementalProgramSession(rootDir);
244800
244814
  if (cache)
244801
244815
  cache.programSession = session;
@@ -244814,8 +244828,21 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
244814
244828
  moduleHandlerFiles: new Map,
244815
244829
  infraResources: new Map,
244816
244830
  infraResourceClasses: new Map,
244817
- diagnostics: []
244831
+ diagnostics: [],
244832
+ effectOptions: {
244833
+ ...DEFAULT_EFFECT_ANALYSIS_OPTIONS,
244834
+ requireTaggedErrorTypes: effectOptions.requireTaggedErrorTypes ?? DEFAULT_EFFECT_ANALYSIS_OPTIONS.requireTaggedErrorTypes,
244835
+ requireExactDependencyTypes: effectOptions.requireExactDependencyTypes ?? DEFAULT_EFFECT_ANALYSIS_OPTIONS.requireExactDependencyTypes,
244836
+ requireTimeoutForDependencies: effectOptions.requireTimeoutForDependencies ?? DEFAULT_EFFECT_ANALYSIS_OPTIONS.requireTimeoutForDependencies,
244837
+ forbidDirectRuntimeExecution: effectOptions.forbidDirectRuntimeExecution ?? DEFAULT_EFFECT_ANALYSIS_OPTIONS.forbidDirectRuntimeExecution,
244838
+ forbidDirectThrows: effectOptions.forbidDirectThrows ?? DEFAULT_EFFECT_ANALYSIS_OPTIONS.forbidDirectThrows
244839
+ }
244818
244840
  };
244841
+ if (ctx.effectOptions.forbidDirectRuntimeExecution) {
244842
+ for (const sourceFile of sourceFiles) {
244843
+ scanEffectRuntimeEscapes(sourceFile, ctx);
244844
+ }
244845
+ }
244819
244846
  const nativeTraitFiles = new Map;
244820
244847
  for (const diagnostic of session.getDiagnostics()) {
244821
244848
  ctx.diagnostics.push(toCompilerDiagnostic(diagnostic, rootDir));
@@ -245991,11 +246018,150 @@ function declaredClassType(declaration, ctx) {
245991
246018
  return symbol ? ctx.checker.getDeclaredTypeOfSymbol(symbol) : undefined;
245992
246019
  }
245993
246020
  function typeArgumentsOf(type, ctx) {
245994
- return isTypeReference(type) ? ctx.checker.getTypeArguments(type) : [];
246021
+ if (!isTypeReference(type))
246022
+ return [];
246023
+ const argumentsFromChecker = ctx.checker.getTypeArguments(type);
246024
+ if (argumentsFromChecker.length > 0)
246025
+ return argumentsFromChecker;
246026
+ const reference = type;
246027
+ if (reference.resolvedTypeArguments && reference.resolvedTypeArguments.length > 0) {
246028
+ return reference.resolvedTypeArguments;
246029
+ }
246030
+ return reference.aliasTypeArguments ?? [];
245995
246031
  }
245996
246032
  function isTypeReference(type) {
245997
246033
  return "target" in type;
245998
246034
  }
246035
+ function effectTypeArgumentsOf(type, ctx) {
246036
+ if (!type)
246037
+ return;
246038
+ const reference = isTypeReference(type) ? type : undefined;
246039
+ const target = reference?.target ?? type;
246040
+ const symbol = target.aliasSymbol ?? target.getSymbol();
246041
+ const text = ctx.checker.typeToString(type, undefined, ts7.TypeFormatFlags.NoTruncation);
246042
+ if (symbol?.getName() !== "Effect" && !/^(?:Effect\.)?Effect</.test(text))
246043
+ return;
246044
+ const args = typeArgumentsOf(type, ctx);
246045
+ return args.length >= 3 ? [args[0], args[1], args[2]] : undefined;
246046
+ }
246047
+ function effectTypeName(type, location, ctx) {
246048
+ return ctx.checker.typeToString(type, location, ts7.TypeFormatFlags.NoTruncation);
246049
+ }
246050
+ function stringLiteralTypeValue(type) {
246051
+ return (type.flags & ts7.TypeFlags.StringLiteral) !== 0 ? type.value : undefined;
246052
+ }
246053
+ function effectFailureTags(type, location, ctx) {
246054
+ if ((type.flags & ts7.TypeFlags.Never) !== 0)
246055
+ return { tags: [], invalid: false };
246056
+ if ((type.flags & (ts7.TypeFlags.Any | ts7.TypeFlags.Unknown)) !== 0) {
246057
+ return { tags: [], invalid: true };
246058
+ }
246059
+ if (type.isUnion()) {
246060
+ return type.types.reduce((result, part) => {
246061
+ const next = effectFailureTags(part, location, ctx);
246062
+ result.tags.push(...next.tags);
246063
+ result.invalid ||= next.invalid;
246064
+ return result;
246065
+ }, { tags: [], invalid: false });
246066
+ }
246067
+ const apparent = ctx.checker.getApparentType(type);
246068
+ for (const propertyName of ["_tag", "tag"]) {
246069
+ const property = ctx.checker.getPropertyOfType(apparent, propertyName);
246070
+ if (!property)
246071
+ continue;
246072
+ const propertyType = ctx.checker.getTypeOfSymbolAtLocation(property, location);
246073
+ const tag = stringLiteralTypeValue(propertyType);
246074
+ if (tag)
246075
+ return { tags: [tag], invalid: false };
246076
+ }
246077
+ return { tags: [], invalid: true };
246078
+ }
246079
+ function effectEnvironmentNames(type, location, ctx) {
246080
+ if ((type.flags & ts7.TypeFlags.Never) !== 0)
246081
+ return { names: [], invalid: false };
246082
+ if ((type.flags & (ts7.TypeFlags.Any | ts7.TypeFlags.Unknown)) !== 0) {
246083
+ return { names: [], invalid: true };
246084
+ }
246085
+ if (type.isUnion() || type.isIntersection()) {
246086
+ return type.types.reduce((result, part) => {
246087
+ const next = effectEnvironmentNames(part, location, ctx);
246088
+ result.names.push(...next.names);
246089
+ result.invalid ||= next.invalid;
246090
+ return result;
246091
+ }, { names: [], invalid: false });
246092
+ }
246093
+ const literal = stringLiteralTypeValue(type);
246094
+ if (literal)
246095
+ return { names: [literal], invalid: false };
246096
+ const symbolName = type.getSymbol()?.getName();
246097
+ if (symbolName && symbolName !== "__type" && symbolName !== "__object") {
246098
+ return { names: [symbolName], invalid: false };
246099
+ }
246100
+ const text = effectTypeName(type, location, ctx).trim();
246101
+ if (text && text !== "object")
246102
+ return { names: [text], invalid: false };
246103
+ return { names: [], invalid: true };
246104
+ }
246105
+ function sortedUnique(values) {
246106
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right, "en"));
246107
+ }
246108
+ function pushEffectDiagnostic(ctx, code, file, line, message, suggestion) {
246109
+ const metadata = COMPILER_DIAGNOSTIC_CODES[code];
246110
+ ctx.diagnostics.push({
246111
+ severity: "error",
246112
+ code,
246113
+ ...metadata ? { errorCode: metadata.code, docsUrl: metadata.docsUrl } : {},
246114
+ file,
246115
+ line,
246116
+ message,
246117
+ suggestion
246118
+ });
246119
+ }
246120
+ function scanEffectRuntimeEscapes(sourceFile, ctx) {
246121
+ const relativeFile = sourcePath(ctx.rootDir, sourceFile.fileName);
246122
+ if (/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(relativeFile))
246123
+ return;
246124
+ const namespaceBindings = new Set;
246125
+ const runtimeBindings = new Set;
246126
+ for (const statement of sourceFile.statements) {
246127
+ if (!ts7.isImportDeclaration(statement) || !ts7.isStringLiteral(statement.moduleSpecifier))
246128
+ continue;
246129
+ const moduleName = statement.moduleSpecifier.text;
246130
+ if (moduleName !== "effect" && moduleName !== "effect/Effect" && moduleName !== "effect/Runtime")
246131
+ continue;
246132
+ const clause = statement.importClause;
246133
+ if (!clause)
246134
+ continue;
246135
+ if (clause.name)
246136
+ namespaceBindings.add(clause.name.text);
246137
+ if (clause.namedBindings && ts7.isNamespaceImport(clause.namedBindings)) {
246138
+ namespaceBindings.add(clause.namedBindings.name.text);
246139
+ } else if (clause.namedBindings && ts7.isNamedImports(clause.namedBindings)) {
246140
+ for (const element of clause.namedBindings.elements) {
246141
+ const imported = element.propertyName?.text ?? element.name.text;
246142
+ if (moduleName === "effect" && (imported === "Effect" || imported === "Runtime")) {
246143
+ namespaceBindings.add(element.name.text);
246144
+ }
246145
+ if (["runPromise", "runPromiseExit", "runSync", "runFork"].includes(imported)) {
246146
+ runtimeBindings.add(element.name.text);
246147
+ }
246148
+ }
246149
+ }
246150
+ }
246151
+ if (namespaceBindings.size === 0 && runtimeBindings.size === 0)
246152
+ return;
246153
+ for (const call of descendantsOfKind(sourceFile, ts7.isCallExpression)) {
246154
+ let runtimeName;
246155
+ if (ts7.isPropertyAccessExpression(call.expression) && namespaceBindings.has(call.expression.expression.getText(sourceFile)) && ["runPromise", "runPromiseExit", "runSync", "runFork"].includes(call.expression.name.text)) {
246156
+ runtimeName = call.expression.name.text;
246157
+ } else if (ts7.isIdentifier(call.expression) && runtimeBindings.has(call.expression.text)) {
246158
+ runtimeName = call.expression.text;
246159
+ }
246160
+ if (!runtimeName)
246161
+ continue;
246162
+ pushEffectDiagnostic(ctx, "effect-direct-runtime-execution", sourcePath(ctx.rootDir, sourceFile.fileName), lineOf2(call), `Direct ${runtimeName} execution escapes the Effect boundary.`, "Return the Effect program to the Elysia boundary; only the framework adapter may interpret it.");
246163
+ }
246164
+ }
245999
246165
  function isUnknownOrAny(type) {
246000
246166
  return (type.flags & (ts7.TypeFlags.Any | ts7.TypeFlags.Unknown)) !== 0;
246001
246167
  }
@@ -246059,7 +246225,8 @@ function parseController(input, ctx) {
246059
246225
  handler: propertyName(method.name)
246060
246226
  };
246061
246227
  const signature = ctx.checker.getSignatureFromDeclaration(method);
246062
- const resultType = signature && ctx.checker.typeToString(signature.getReturnType());
246228
+ const returnType = signature?.getReturnType();
246229
+ const resultType = returnType && ctx.checker.typeToString(returnType);
246063
246230
  if (resultType && /\bResponse\b/.test(resultType))
246064
246231
  route.nativeResponse = true;
246065
246232
  const pathParams = [];
@@ -246390,6 +246557,18 @@ function parseController(input, ctx) {
246390
246557
  });
246391
246558
  } else {
246392
246559
  effect.dependencies = dependenciesExpr.elements.map((element) => element.text);
246560
+ if (new Set(effect.dependencies).size !== effect.dependencies.length) {
246561
+ ctx.diagnostics.push({
246562
+ severity: "error",
246563
+ code: "invalid-effect-dependencies",
246564
+ errorCode: "SC3033",
246565
+ docsUrl: "https://supacloud.dev/errors/SC3033",
246566
+ file,
246567
+ line: lineOf2(dependenciesExpr),
246568
+ message: `Route ${route.handler} effect.dependencies must not contain duplicate service names.`,
246569
+ suggestion: "List each logical Effect dependency exactly once."
246570
+ });
246571
+ }
246393
246572
  }
246394
246573
  }
246395
246574
  const errorsExpr = getProp(effectObject, "errors");
@@ -246438,10 +246617,26 @@ function parseController(input, ctx) {
246438
246617
  errors.push({ tag, code, status, ...message === undefined ? {} : { message } });
246439
246618
  }
246440
246619
  effect.errors = errors;
246620
+ if (new Set(errors.map((entry) => entry.tag)).size !== errors.length) {
246621
+ ctx.diagnostics.push({
246622
+ severity: "error",
246623
+ code: "invalid-effect-errors",
246624
+ errorCode: "SC3034",
246625
+ docsUrl: "https://supacloud.dev/errors/SC3034",
246626
+ file,
246627
+ line: lineOf2(errorsExpr),
246628
+ message: `Route ${route.handler} effect.errors must not contain duplicate tags.`,
246629
+ suggestion: "Map each tagged failure exactly once."
246630
+ });
246631
+ }
246441
246632
  }
246442
246633
  }
246443
246634
  route.effect = effect;
246444
- if (!resultType || !/\bEffect(?:\.Effect)?(?:<|\s)/.test(resultType)) {
246635
+ const effectArguments = effectTypeArgumentsOf(returnType, ctx);
246636
+ const promiseReturn = Boolean(resultType && /\bPromise(?:<|\s)/.test(resultType));
246637
+ if (promiseReturn) {
246638
+ pushEffectDiagnostic(ctx, "effect-promise-return", file, lineOf2(method), `Route ${route.handler} returns Promise instead of an Effect program.`, "Return Effect.Effect<Success, Failure, Requirements>; the Elysia boundary owns Promise interpretation.");
246639
+ } else if (!effectArguments) {
246445
246640
  ctx.diagnostics.push({
246446
246641
  severity: "error",
246447
246642
  code: "effect-return-type",
@@ -246453,18 +246648,37 @@ function parseController(input, ctx) {
246453
246648
  suggestion: "Return Effect.Effect<Success, Failure, Requirements> from the handler."
246454
246649
  });
246455
246650
  }
246456
- for (const dependency of effect.dependencies ?? []) {
246457
- if (!resultType?.includes(dependency)) {
246458
- ctx.diagnostics.push({
246459
- severity: "error",
246460
- code: "effect-dependency-type",
246461
- errorCode: "SC3040",
246462
- docsUrl: "https://supacloud.dev/errors/SC3040",
246463
- file,
246464
- line: lineOf2(method),
246465
- message: `Route ${route.handler} declares Effect dependency "${dependency}", but it is absent from the return type environment ${resultType ?? "unknown"}.`,
246466
- suggestion: "Keep effect.dependencies aligned with the third Effect type parameter."
246467
- });
246651
+ if (effectArguments) {
246652
+ const [, failureType, environmentType] = effectArguments;
246653
+ const failure = effectFailureTags(failureType, method, ctx);
246654
+ if (ctx.effectOptions.requireTaggedErrorTypes && failure.invalid) {
246655
+ pushEffectDiagnostic(ctx, "effect-error-type", file, lineOf2(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.");
246656
+ } else if (ctx.effectOptions.requireTaggedErrorTypes && effect.errors !== undefined && !failure.invalid) {
246657
+ const declaredTags = sortedUnique(effect.errors.map((entry) => entry.tag));
246658
+ const typedTags = sortedUnique(failure.tags);
246659
+ if (declaredTags.join("\x00") !== typedTags.join("\x00")) {
246660
+ pushEffectDiagnostic(ctx, "effect-error-mapping-type", file, lineOf2(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.");
246661
+ }
246662
+ }
246663
+ const environment = effectEnvironmentNames(environmentType, method, ctx);
246664
+ if (ctx.effectOptions.requireExactDependencyTypes && effect.dependencies !== undefined) {
246665
+ if (environment.invalid) {
246666
+ pushEffectDiagnostic(ctx, "effect-dependency-mismatch", file, lineOf2(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.");
246667
+ } else {
246668
+ const declaredDependencies = sortedUnique(effect.dependencies);
246669
+ const typedDependencies = sortedUnique(environment.names);
246670
+ if (declaredDependencies.join("\x00") !== typedDependencies.join("\x00")) {
246671
+ pushEffectDiagnostic(ctx, "effect-dependency-mismatch", file, lineOf2(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.");
246672
+ }
246673
+ }
246674
+ }
246675
+ if (ctx.effectOptions.requireTimeoutForDependencies && (effect.dependencies?.length ?? 0) > 0 && effect.timeoutMs === undefined) {
246676
+ pushEffectDiagnostic(ctx, "effect-timeout-required", file, lineOf2(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.");
246677
+ }
246678
+ }
246679
+ if (ctx.effectOptions.forbidDirectThrows && method.body) {
246680
+ for (const throwStatement of descendantsOfKind(method.body, ts7.isThrowStatement)) {
246681
+ pushEffectDiagnostic(ctx, "effect-direct-throw", file, lineOf2(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.");
246468
246682
  }
246469
246683
  }
246470
246684
  }
@@ -247763,7 +247977,7 @@ async function renderOptionalGraphql(options) {
247763
247977
  }
247764
247978
  async function compileProject(options) {
247765
247979
  options = withDefaultGovernance(options);
247766
- const graph = await analyzeProject(options.rootDir, options.include, options.cache, options.changedPaths);
247980
+ const graph = await analyzeProject(options.rootDir, options.include, options.cache, options.changedPaths, options.effect);
247767
247981
  const diagnostics = [
247768
247982
  ...graph.diagnostics ?? [],
247769
247983
  ...validateGraph(graph, options),
@@ -247828,7 +248042,7 @@ async function compileProject(options) {
247828
248042
  }
247829
248043
  async function checkProject(options) {
247830
248044
  options = withDefaultGovernance(options);
247831
- const graph = await analyzeProject(options.rootDir, options.include, options.cache, options.changedPaths);
248045
+ const graph = await analyzeProject(options.rootDir, options.include, options.cache, options.changedPaths, options.effect);
247832
248046
  const diagnostics = [
247833
248047
  ...graph.diagnostics ?? [],
247834
248048
  ...validateGraph(graph, options),
@@ -249069,6 +249283,7 @@ function optionsKeyOf(options) {
249069
249283
  openApi: options.openApi,
249070
249284
  generatePermissions: options.generatePermissions,
249071
249285
  typeSafety: options.typeSafety,
249286
+ effect: options.effect,
249072
249287
  treeShakeUnusedProviders: options.treeShakeUnusedProviders,
249073
249288
  graphql: options.graphql
249074
249289
  });
@@ -249400,7 +249615,12 @@ var DEFAULT_SUPACLOUD_CONFIG = {
249400
249615
  effect: {
249401
249616
  requireRouteEffects: true,
249402
249617
  requireErrorMappings: true,
249403
- requireDependencies: true
249618
+ requireDependencies: true,
249619
+ requireTaggedErrorTypes: true,
249620
+ requireExactDependencyTypes: true,
249621
+ requireTimeoutForDependencies: true,
249622
+ forbidDirectRuntimeExecution: true,
249623
+ forbidDirectThrows: true
249404
249624
  }
249405
249625
  };
249406
249626
  function defineSupacloudConfig(config = {}) {
@@ -249472,10 +249692,28 @@ function validateGovernanceConfig(config) {
249472
249692
  throw new Error(`${key} must be a boolean.`);
249473
249693
  }
249474
249694
  if (config.effect !== undefined) {
249475
- if (!isRecord(config.effect) || Object.keys(config.effect).some((key) => !["requireRouteEffects", "requireErrorMappings", "requireDependencies"].includes(key))) {
249476
- throw new Error("effect accepts requireRouteEffects, requireErrorMappings and requireDependencies.");
249477
- }
249478
- for (const key of ["requireRouteEffects", "requireErrorMappings", "requireDependencies"]) {
249695
+ if (!isRecord(config.effect) || Object.keys(config.effect).some((key) => ![
249696
+ "requireRouteEffects",
249697
+ "requireErrorMappings",
249698
+ "requireDependencies",
249699
+ "requireTaggedErrorTypes",
249700
+ "requireExactDependencyTypes",
249701
+ "requireTimeoutForDependencies",
249702
+ "forbidDirectRuntimeExecution",
249703
+ "forbidDirectThrows"
249704
+ ].includes(key))) {
249705
+ throw new Error("effect contains an unsupported governance option.");
249706
+ }
249707
+ for (const key of [
249708
+ "requireRouteEffects",
249709
+ "requireErrorMappings",
249710
+ "requireDependencies",
249711
+ "requireTaggedErrorTypes",
249712
+ "requireExactDependencyTypes",
249713
+ "requireTimeoutForDependencies",
249714
+ "forbidDirectRuntimeExecution",
249715
+ "forbidDirectThrows"
249716
+ ]) {
249479
249717
  if (config.effect[key] !== undefined && typeof config.effect[key] !== "boolean") {
249480
249718
  throw new Error(`effect.${key} must be boolean.`);
249481
249719
  }
@@ -249650,7 +249888,7 @@ var starter_metadata_default = {
249650
249888
  generatedBy: "supacloud/starter-metadata-v1",
249651
249889
  packages: {
249652
249890
  compiler: {
249653
- version: "0.41.0"
249891
+ version: "0.42.0"
249654
249892
  },
249655
249893
  app: {
249656
249894
  version: "0.24.0",
@@ -249659,7 +249897,7 @@ var starter_metadata_default = {
249659
249897
  }
249660
249898
  },
249661
249899
  elysia: {
249662
- version: "0.28.0"
249900
+ version: "0.29.0"
249663
249901
  },
249664
249902
  commands: {
249665
249903
  version: "0.9.1"
@@ -250013,7 +250251,7 @@ function baseFiles(name, sdkDependencies) {
250013
250251
  dependencies: {
250014
250252
  "@supacloud/app": `^${appMetadata.version}`,
250015
250253
  "@supacloud/elysia": `^${elysiaMetadata.version}`,
250016
- effect: "3.22.1",
250254
+ effect: "4.0.2",
250017
250255
  elysia: "2.0.0-beta.21",
250018
250256
  ...sdkDependencies ?? { rxjs: appMetadata.dependencies.rxjs }
250019
250257
  },
@@ -250062,6 +250300,11 @@ export default defineSupacloudConfig({
250062
250300
  requireRouteEffects: true,
250063
250301
  requireErrorMappings: true,
250064
250302
  requireDependencies: true,
250303
+ requireTaggedErrorTypes: true,
250304
+ requireExactDependencyTypes: true,
250305
+ requireTimeoutForDependencies: true,
250306
+ forbidDirectRuntimeExecution: true,
250307
+ forbidDirectThrows: true,
250065
250308
  },
250066
250309
  typeSafety: { scanProductionSource: true, noAnyInGenerated: true },
250067
250310
  disallowControllerDirectDb: true,
@@ -250091,7 +250334,7 @@ export const CreateOrderBody = t.Object({ name: t.String({ minLength: 1 }) });
250091
250334
  export const OrderResult = t.Object({ id: t.String(), name: t.String() });
250092
250335
  export const HealthResult = t.Object({ ok: t.Boolean() });
250093
250336
  `,
250094
- "src/orders/orders.ts": `import * as Effect from "effect/Effect";
250337
+ "src/orders/orders.ts": `import { Effect } from "effect";
250095
250338
  import { Body, Controller, Get, Param, Post, defineFeatureSlice } from "@supacloud/app";
250096
250339
  import { status } from "elysia";
250097
250340
  import { OrderParams, CreateOrderBody, OrderResult, HealthResult } from "./contracts";
@@ -250292,7 +250535,7 @@ function minimalTemplate(name, sdkDependencies) {
250292
250535
  "src/features/health/contracts.ts": `import { t } from "elysia";
250293
250536
  export const HealthResult = t.Object({ ok: t.Boolean() });
250294
250537
  `,
250295
- "src/features/health/health.ts": `import * as Effect from "effect/Effect";
250538
+ "src/features/health/health.ts": `import { Effect } from "effect";
250296
250539
  import { Controller, Get, Module } from "@supacloud/app/core";
250297
250540
  import { HealthResult } from "./contracts";
250298
250541
  @Controller("/health")
@@ -250307,7 +250550,7 @@ export class HealthController {
250307
250550
  @Module({ name: "health", tags: ["type:feature"], controllers: [HealthController] })
250308
250551
  export class HealthModule {}
250309
250552
  `,
250310
- "src/features/health/health.test.ts": `import * as Effect from "effect/Effect";
250553
+ "src/features/health/health.test.ts": `import { Effect } from "effect";
250311
250554
  import { expect, test } from "bun:test";
250312
250555
  import { HealthController } from "./health";
250313
250556
 
@@ -250837,7 +251080,7 @@ export async function createDeliveryWorker(modules: CompiledModule[], lifecycle:
250837
251080
  `;
250838
251081
 
250839
251082
  // src/shared/tools/app-starter-upload.ts
250840
- var STARTER_UPLOAD_FEATURE = `import * as Effect from "effect/Effect";
251083
+ var STARTER_UPLOAD_FEATURE = `import { Effect } from "effect";
250841
251084
  import { Body, Controller, Inject, Injectable, Param, Post, DB_CLIENT, REQUEST_CONTEXT } from "@supacloud/app";
250842
251085
  import { ApplicationError } from "@supacloud/elysia";
250843
251086
  import { UploadBody, UploadLocation, UploadParams, UploadResult } from "./contracts";
@@ -250845,6 +251088,17 @@ import { UploadBody, UploadLocation, UploadParams, UploadResult } from "./contra
250845
251088
  export interface ReviewUploadInput { reviewId: string; artifactId: string; expectedVersion: number }
250846
251089
  export interface ReviewUploadLocation { artifactId: string; bucketId: string; objectPath: string }
250847
251090
  export interface ReviewUploadResult { artifactId: string; runId: string; objectPath: string; sha256: string; bytes: number }
251091
+ type PreviewUploadError =
251092
+ | { readonly _tag: "AUTHENTICATION_REQUIRED" }
251093
+ | { readonly _tag: "UPLOAD_INPUT_INVALID" }
251094
+ | { readonly _tag: "UPLOAD_DENIED" }
251095
+ | { readonly _tag: "UPLOADS_UNAVAILABLE" };
251096
+ type RegisterUploadError =
251097
+ | PreviewUploadError
251098
+ | { readonly _tag: "UPLOAD_CONTENT_INVALID" }
251099
+ | { readonly _tag: "COMMAND_IDEMPOTENCY_CONFLICT" }
251100
+ | { readonly _tag: "COMMAND_REJECTED" }
251101
+ | { readonly _tag: "UPLOAD_OUTCOME_UNKNOWN" };
250848
251102
  export interface ReviewUploadPort {
250849
251103
  previewUpload(context: unknown, input: ReviewUploadInput): Promise<ReviewUploadLocation>;
250850
251104
  registerUpload(context: unknown, input: ReviewUploadInput): Promise<ReviewUploadResult>;
@@ -250884,7 +251138,7 @@ export class ReviewUploadsController {
250884
251138
  retry: "none",
250885
251139
  },
250886
251140
  })
250887
- preview(@Param("id") id: string, @Body() body: { artifactId: string; expectedVersion: number }): Effect.Effect<ReviewUploadLocation, unknown, never> {
251141
+ preview(@Param("id") id: string, @Body() body: { artifactId: string; expectedVersion: number }): Effect.Effect<ReviewUploadLocation, PreviewUploadError, never> {
250888
251142
  return Effect.promise(() => this.uploads.preview({ reviewId: id, artifactId: body.artifactId, expectedVersion: body.expectedVersion }));
250889
251143
  }
250890
251144
  @Post("/:id/attachment-registration", {
@@ -250907,7 +251161,7 @@ export class ReviewUploadsController {
250907
251161
  retry: "none",
250908
251162
  },
250909
251163
  })
250910
- register(@Param("id") id: string, @Body() body: { artifactId: string; expectedVersion: number }): Effect.Effect<ReviewUploadResult, unknown, never> {
251164
+ register(@Param("id") id: string, @Body() body: { artifactId: string; expectedVersion: number }): Effect.Effect<ReviewUploadResult, RegisterUploadError, never> {
250911
251165
  return Effect.promise(() => this.uploads.register({ reviewId: id, artifactId: body.artifactId, expectedVersion: body.expectedVersion }));
250912
251166
  }
250913
251167
  }
@@ -251619,7 +251873,7 @@ function appStarterFiles(name) {
251619
251873
  "drizzle-orm": dbMetadata.peerDependencies["drizzle-orm"],
251620
251874
  "@supacloud/js": `^${sdkMetadata.version}`,
251621
251875
  "@supabase/supabase-js": sdkMetadata.peerDependencies["@supabase/supabase-js"],
251622
- effect: "3.22.1",
251876
+ effect: "4.0.2",
251623
251877
  elysia: "2.0.0-beta.21",
251624
251878
  rxjs: appMetadata2.dependencies.rxjs
251625
251879
  },
@@ -251679,6 +251933,11 @@ export default defineSupacloudConfig({
251679
251933
  requireRouteEffects: true,
251680
251934
  requireErrorMappings: true,
251681
251935
  requireDependencies: true,
251936
+ requireTaggedErrorTypes: true,
251937
+ requireExactDependencyTypes: true,
251938
+ requireTimeoutForDependencies: true,
251939
+ forbidDirectRuntimeExecution: true,
251940
+ forbidDirectThrows: true,
251682
251941
  },
251683
251942
  typeSafety: { scanProductionSource: true, noAnyInGenerated: true },
251684
251943
  disallowControllerDirectDb: true,
@@ -251880,7 +252139,7 @@ export function createCompiledModules(): never {
251880
252139
  throw new Error("Run bun run compile before starting the application");
251881
252140
  }
251882
252141
  `,
251883
- "src/review/review.ts": `import * as Effect from "effect/Effect";
252142
+ "src/review/review.ts": `import { Effect } from "effect";
251884
252143
  import {
251885
252144
  Body, Command, Controller, DB_CLIENT, Get, Inject, Param, Post,
251886
252145
  defineFeatureSlice, defineFeatureSpec, type Aspect,
@@ -251947,6 +252206,17 @@ export class ApproveReview {
251947
252206
  }
251948
252207
  }
251949
252208
 
252209
+ type ReviewError =
252210
+ | { readonly _tag: "AUTHENTICATION_REQUIRED" }
252211
+ | { readonly _tag: "REVIEW_NOT_FOUND" }
252212
+ | { readonly _tag: "REVIEW_PERMISSION_DENIED" }
252213
+ | { readonly _tag: "REVIEW_CONFLICT" }
252214
+ | { readonly _tag: "FEATURE_TRANSITION_CONFLICT" }
252215
+ | { readonly _tag: "COMMAND_IDEMPOTENCY_CONFLICT" }
252216
+ | { readonly _tag: "COMMAND_REJECTED" }
252217
+ | { readonly _tag: "REVIEW_COMMAND_UNSUPPORTED" }
252218
+ | { readonly _tag: "COMMAND_OUTCOME_UNKNOWN" };
252219
+
251950
252220
  @Controller("/reviews")
251951
252221
  export class ReviewController {
251952
252222
  constructor(@Inject(ApproveReview) private readonly approveReview: ApproveReview) {}
@@ -251979,7 +252249,7 @@ export class ReviewController {
251979
252249
  retry: "none",
251980
252250
  },
251981
252251
  })
251982
- approve(@Param("id") id: string, @Body() body: { expectedVersion: number }): Effect.Effect<Review, unknown, never> {
252252
+ approve(@Param("id") id: string, @Body() body: { expectedVersion: number }): Effect.Effect<Review, ReviewError, never> {
251983
252253
  return Effect.promise(() => this.approveReview.execute(id, body.expectedVersion));
251984
252254
  }
251985
252255
  }
@@ -252519,7 +252789,7 @@ export class ${className}Service {
252519
252789
  }
252520
252790
  }
252521
252791
  `,
252522
- [`${name}.controller.ts`]: `import * as Effect from "effect/Effect";
252792
+ [`${name}.controller.ts`]: `import { Effect } from "effect";
252523
252793
  import { Controller, Get, Inject, Param } from "@supacloud/app";
252524
252794
  import { ${className}Params, ${className}Response, type ${className}Result } from "./${name}.model";
252525
252795
  import { ${className}Service } from "./${name}.service";
@@ -252533,7 +252803,7 @@ export class ${className}Controller {
252533
252803
  responses: { 200: ${className}Response },
252534
252804
  effect: { required: true, dependencies: [], errors: [], retry: "none" },
252535
252805
  })
252536
- find(@Param("id") id: string): Effect.Effect<${className}Result, unknown, never> {
252806
+ find(@Param("id") id: string): Effect.Effect<${className}Result, never, never> {
252537
252807
  return Effect.promise(() => this.service.find(id));
252538
252808
  }
252539
252809
  }
@@ -252569,7 +252839,7 @@ test("${name} preserves permission denial without a privileged fallback", async
252569
252839
  await expect(service.find("example")).rejects.toBe(denied);
252570
252840
  });
252571
252841
  `,
252572
- [`${name}.controller.test.ts`]: `import * as Effect from "effect/Effect";
252842
+ [`${name}.controller.test.ts`]: `import { Effect } from "effect";
252573
252843
  import { expect, test } from "bun:test";
252574
252844
  import { ${className}Controller } from "./${name}.controller";
252575
252845
 
@@ -257123,7 +257393,7 @@ function registerRemoteDevTools(server2, options = {}) {
257123
257393
  // package.json
257124
257394
  var package_default = {
257125
257395
  name: "@supacloud/cli",
257126
- version: "0.73.0",
257396
+ version: "0.74.0",
257127
257397
  description: "Project-scoped CLI for SupaCloud users",
257128
257398
  type: "module",
257129
257399
  main: "./dist/index.js",
@@ -257157,7 +257427,7 @@ var package_default = {
257157
257427
  directory: "packages/cli"
257158
257428
  },
257159
257429
  dependencies: {
257160
- "@supacloud/compiler": "^0.41.0",
257430
+ "@supacloud/compiler": "^0.42.0",
257161
257431
  "@supacloud/delivery": "0.8.0",
257162
257432
  "@supacloud/db": "^0.14.0",
257163
257433
  fflate: "^0.8.3",
@@ -257170,7 +257440,7 @@ var package_default = {
257170
257440
  devDependencies: {
257171
257441
  "@supacloud/elysia": "^0.24.1",
257172
257442
  "@types/bun": "^1.4.2",
257173
- effect: "3.22.1",
257443
+ effect: "4.0.2",
257174
257444
  elysia: "2.0.0-beta.21",
257175
257445
  typescript: "^7.0.2"
257176
257446
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.73.0",
3
+ "version": "0.74.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,7 +34,7 @@
34
34
  "directory": "packages/cli"
35
35
  },
36
36
  "dependencies": {
37
- "@supacloud/compiler": "^0.41.0",
37
+ "@supacloud/compiler": "^0.42.0",
38
38
  "@supacloud/delivery": "0.8.0",
39
39
  "@supacloud/db": "^0.14.0",
40
40
  "fflate": "^0.8.3",
@@ -47,7 +47,7 @@
47
47
  "devDependencies": {
48
48
  "@supacloud/elysia": "^0.24.1",
49
49
  "@types/bun": "^1.4.2",
50
- "effect": "3.22.1",
50
+ "effect": "4.0.2",
51
51
  "elysia": "2.0.0-beta.21",
52
52
  "typescript": "^7.0.2"
53
53
  }