@supacloud/cli 0.72.0 → 0.73.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/index.js CHANGED
@@ -207715,7 +207715,7 @@ var ACTION_POLICY = {
207715
207715
  read: ["status", "logs"],
207716
207716
  write: ["upload", "configure", "deploy", "rollback", "reconcile", "retire"]
207717
207717
  },
207718
- db: { local: ["lint", "explain"], read: ["module_check"] },
207718
+ db: { local: ["context", "lint", "explain"], read: ["module_check"] },
207719
207719
  dev: { read: ["status"], write: ["sync", "watch", "migrate"] }
207720
207720
  };
207721
207721
  function declaredMode(moduleName, action) {
@@ -221498,6 +221498,17 @@ var init_validate = __esm2(() => {
221498
221498
  "route-contract-required": { code: "SC3021", docsUrl: "https://supacloud.dev/errors/SC3021" },
221499
221499
  "route-contract-unverified": { code: "SC3022", docsUrl: "https://supacloud.dev/errors/SC3022" },
221500
221500
  "route-contract-evidence-required": { code: "SC3023", docsUrl: "https://supacloud.dev/errors/SC3023" },
221501
+ "invalid-effect-contract": { code: "SC3030", docsUrl: "https://supacloud.dev/errors/SC3030" },
221502
+ "invalid-effect-retry": { code: "SC3031", docsUrl: "https://supacloud.dev/errors/SC3031" },
221503
+ "invalid-effect-timeout": { code: "SC3032", docsUrl: "https://supacloud.dev/errors/SC3032" },
221504
+ "invalid-effect-dependencies": { code: "SC3033", docsUrl: "https://supacloud.dev/errors/SC3033" },
221505
+ "invalid-effect-errors": { code: "SC3034", docsUrl: "https://supacloud.dev/errors/SC3034" },
221506
+ "effect-return-type": { code: "SC3035", docsUrl: "https://supacloud.dev/errors/SC3035" },
221507
+ "effect-contract-required": { code: "SC3036", docsUrl: "https://supacloud.dev/errors/SC3036" },
221508
+ "effect-error-mapping-required": { code: "SC3037", docsUrl: "https://supacloud.dev/errors/SC3037" },
221509
+ "effect-dependencies-required": { code: "SC3038", docsUrl: "https://supacloud.dev/errors/SC3038" },
221510
+ "effect-command-retry-forbidden": { code: "SC3039", docsUrl: "https://supacloud.dev/errors/SC3039" },
221511
+ "effect-dependency-type": { code: "SC3040", docsUrl: "https://supacloud.dev/errors/SC3040" },
221501
221512
  "invalid-route-response-map": { code: "SC3024", docsUrl: "https://supacloud.dev/errors/SC3024" },
221502
221513
  "invalid-route-response-selector": { code: "SC3025", docsUrl: "https://supacloud.dev/errors/SC3025" },
221503
221514
  "conflicting-route-response-schema": { code: "SC3026", docsUrl: "https://supacloud.dev/errors/SC3026" },
@@ -222026,6 +222037,9 @@ class ModuleGenerator {
222026
222037
  });
222027
222038
  fields.push(`responses: { ${responseFields.join(", ")} }`);
222028
222039
  }
222040
+ if (route.effect !== undefined) {
222041
+ fields.push(`effect: ${JSON.stringify(route.effect)}`);
222042
+ }
222029
222043
  if (route.command)
222030
222044
  fields.push(`command: ${JSON.stringify(route.command)}`);
222031
222045
  if (route.guards && route.guards.length > 0) {
@@ -223838,6 +223852,14 @@ var INTERFACES = `export interface CompiledRoute {
223838
223852
  cookie?: unknown;
223839
223853
  response?: unknown;
223840
223854
  responses?: Record<string | number, unknown>;
223855
+ effect?: {
223856
+ required: true;
223857
+ errors?: Array<{ tag: string; status: number; code: string; message?: string }>;
223858
+ dependencies?: string[];
223859
+ retry?: "none" | "explicit";
223860
+ maxAttempts?: number;
223861
+ timeoutMs?: number;
223862
+ };
223841
223863
  /** Compile-time ownership and transport classification for the route boundary. */
223842
223864
  contract?: {
223843
223865
  body?: "framework" | "domain";
@@ -246265,6 +246287,188 @@ function parseController(input, ctx) {
246265
246287
  Object.assign(route.contract, { [field]: value });
246266
246288
  }
246267
246289
  }
246290
+ const effectExpr = getProp(optionsObject, "effect");
246291
+ if (effectExpr) {
246292
+ const effectObject = resolveStaticObjectLiteral(effectExpr, ctx);
246293
+ if (!effectObject) {
246294
+ ctx.diagnostics.push({
246295
+ severity: "error",
246296
+ code: "invalid-effect-contract",
246297
+ errorCode: "SC3030",
246298
+ docsUrl: "https://supacloud.dev/errors/SC3030",
246299
+ file,
246300
+ line: lineOf2(effectExpr),
246301
+ message: `Route ${route.handler} effect must be a statically resolvable object literal.`,
246302
+ suggestion: "Use effect: { required: true, errors: [...], dependencies: [...] }."
246303
+ });
246304
+ } else {
246305
+ const effect = { required: true };
246306
+ const requiredExpr = getProp(effectObject, "required");
246307
+ if (requiredExpr !== undefined && booleanProp(effectObject, "required") !== true) {
246308
+ ctx.diagnostics.push({
246309
+ severity: "error",
246310
+ code: "invalid-effect-contract",
246311
+ errorCode: "SC3030",
246312
+ docsUrl: "https://supacloud.dev/errors/SC3030",
246313
+ file,
246314
+ line: lineOf2(requiredExpr),
246315
+ message: `Route ${route.handler} effect.required must be the literal true.`
246316
+ });
246317
+ }
246318
+ const retry = stringLiteralProp(effectObject, "retry");
246319
+ if (retry !== undefined) {
246320
+ if (retry !== "none" && retry !== "explicit") {
246321
+ ctx.diagnostics.push({
246322
+ severity: "error",
246323
+ code: "invalid-effect-retry",
246324
+ errorCode: "SC3031",
246325
+ docsUrl: "https://supacloud.dev/errors/SC3031",
246326
+ file,
246327
+ line: lineOf2(getProp(effectObject, "retry")),
246328
+ message: `Route ${route.handler} effect.retry must be "none" or "explicit".`
246329
+ });
246330
+ } else {
246331
+ effect.retry = retry;
246332
+ }
246333
+ }
246334
+ const maxAttemptsExpr = getProp(effectObject, "maxAttempts");
246335
+ if (maxAttemptsExpr !== undefined) {
246336
+ const maxAttempts = numberLiteralProp(effectObject, "maxAttempts");
246337
+ if (maxAttempts === undefined || maxAttempts < 1 || !Number.isInteger(maxAttempts)) {
246338
+ ctx.diagnostics.push({
246339
+ severity: "error",
246340
+ code: "invalid-effect-retry",
246341
+ errorCode: "SC3031",
246342
+ docsUrl: "https://supacloud.dev/errors/SC3031",
246343
+ file,
246344
+ line: lineOf2(maxAttemptsExpr),
246345
+ message: `Route ${route.handler} effect.maxAttempts must be a positive integer.`
246346
+ });
246347
+ } else {
246348
+ effect.maxAttempts = maxAttempts;
246349
+ }
246350
+ }
246351
+ if (effect.retry === "explicit" && effect.maxAttempts === undefined) {
246352
+ ctx.diagnostics.push({
246353
+ severity: "error",
246354
+ code: "invalid-effect-retry",
246355
+ errorCode: "SC3031",
246356
+ docsUrl: "https://supacloud.dev/errors/SC3031",
246357
+ file,
246358
+ line: lineOf2(effectExpr),
246359
+ message: `Route ${route.handler} effect.maxAttempts is required when retry is "explicit".`
246360
+ });
246361
+ }
246362
+ const timeoutExpr = getProp(effectObject, "timeoutMs");
246363
+ if (timeoutExpr !== undefined) {
246364
+ const timeoutMs = numberLiteralProp(effectObject, "timeoutMs");
246365
+ if (timeoutMs === undefined || timeoutMs <= 0 || !Number.isInteger(timeoutMs)) {
246366
+ ctx.diagnostics.push({
246367
+ severity: "error",
246368
+ code: "invalid-effect-timeout",
246369
+ errorCode: "SC3032",
246370
+ docsUrl: "https://supacloud.dev/errors/SC3032",
246371
+ file,
246372
+ line: lineOf2(timeoutExpr),
246373
+ message: `Route ${route.handler} effect.timeoutMs must be a positive integer.`
246374
+ });
246375
+ } else {
246376
+ effect.timeoutMs = timeoutMs;
246377
+ }
246378
+ }
246379
+ const dependenciesExpr = getProp(effectObject, "dependencies");
246380
+ if (dependenciesExpr !== undefined) {
246381
+ if (!ts7.isArrayLiteralExpression(dependenciesExpr) || dependenciesExpr.elements.some((element) => !ts7.isStringLiteral(element) || element.text.trim().length === 0)) {
246382
+ ctx.diagnostics.push({
246383
+ severity: "error",
246384
+ code: "invalid-effect-dependencies",
246385
+ errorCode: "SC3033",
246386
+ docsUrl: "https://supacloud.dev/errors/SC3033",
246387
+ file,
246388
+ line: lineOf2(dependenciesExpr),
246389
+ message: `Route ${route.handler} effect.dependencies must be an explicit string array.`
246390
+ });
246391
+ } else {
246392
+ effect.dependencies = dependenciesExpr.elements.map((element) => element.text);
246393
+ }
246394
+ }
246395
+ const errorsExpr = getProp(effectObject, "errors");
246396
+ if (errorsExpr !== undefined) {
246397
+ if (!ts7.isArrayLiteralExpression(errorsExpr)) {
246398
+ ctx.diagnostics.push({
246399
+ severity: "error",
246400
+ code: "invalid-effect-errors",
246401
+ errorCode: "SC3034",
246402
+ docsUrl: "https://supacloud.dev/errors/SC3034",
246403
+ file,
246404
+ line: lineOf2(errorsExpr),
246405
+ message: `Route ${route.handler} effect.errors must be an explicit object array.`
246406
+ });
246407
+ } else {
246408
+ const errors = [];
246409
+ for (const errorExpr of errorsExpr.elements) {
246410
+ if (!ts7.isObjectLiteralExpression(errorExpr)) {
246411
+ ctx.diagnostics.push({
246412
+ severity: "error",
246413
+ code: "invalid-effect-errors",
246414
+ errorCode: "SC3034",
246415
+ docsUrl: "https://supacloud.dev/errors/SC3034",
246416
+ file,
246417
+ line: lineOf2(errorExpr),
246418
+ message: `Route ${route.handler} effect.errors entries must be object literals.`
246419
+ });
246420
+ continue;
246421
+ }
246422
+ const tag = stringLiteralProp(errorExpr, "tag");
246423
+ const code = stringLiteralProp(errorExpr, "code");
246424
+ const status = numberLiteralProp(errorExpr, "status");
246425
+ const message = stringLiteralProp(errorExpr, "message");
246426
+ if (!tag || !code || status === undefined || !Number.isInteger(status) || status < 400 || status > 599) {
246427
+ ctx.diagnostics.push({
246428
+ severity: "error",
246429
+ code: "invalid-effect-errors",
246430
+ errorCode: "SC3034",
246431
+ docsUrl: "https://supacloud.dev/errors/SC3034",
246432
+ file,
246433
+ line: lineOf2(errorExpr),
246434
+ message: `Route ${route.handler} effect.errors entries require tag, code and an HTTP status from 400 to 599.`
246435
+ });
246436
+ continue;
246437
+ }
246438
+ errors.push({ tag, code, status, ...message === undefined ? {} : { message } });
246439
+ }
246440
+ effect.errors = errors;
246441
+ }
246442
+ }
246443
+ route.effect = effect;
246444
+ if (!resultType || !/\bEffect(?:\.Effect)?(?:<|\s)/.test(resultType)) {
246445
+ ctx.diagnostics.push({
246446
+ severity: "error",
246447
+ code: "effect-return-type",
246448
+ errorCode: "SC3035",
246449
+ docsUrl: "https://supacloud.dev/errors/SC3035",
246450
+ file,
246451
+ line: lineOf2(method),
246452
+ message: `Route ${route.handler} declares an Effect contract but returns ${resultType ?? "an unresolved type"}.`,
246453
+ suggestion: "Return Effect.Effect<Success, Failure, Requirements> from the handler."
246454
+ });
246455
+ }
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
+ });
246468
+ }
246469
+ }
246470
+ }
246471
+ }
246268
246472
  for (const field of ["body", "params", "query", "headers", "cookie", "response"]) {
246269
246473
  const schemaExpr = getProp(optionsObject, field);
246270
246474
  if (schemaExpr && ts7.isIdentifier(schemaExpr)) {
@@ -246848,6 +247052,13 @@ function stringLiteralProp(obj, name) {
246848
247052
  const expr = getProp(obj, name);
246849
247053
  return expr && ts7.isStringLiteral(expr) ? expr.text : undefined;
246850
247054
  }
247055
+ function numberLiteralProp(obj, name) {
247056
+ const expr = getProp(obj, name);
247057
+ if (!expr || !ts7.isNumericLiteral(expr))
247058
+ return;
247059
+ const value = Number(expr.text);
247060
+ return Number.isFinite(value) ? value : undefined;
247061
+ }
246851
247062
  function arrayProp(obj, name) {
246852
247063
  const expr = getProp(obj, name);
246853
247064
  return expr && ts7.isArrayLiteralExpression(expr) ? [...expr.elements] : [];
@@ -247489,6 +247700,55 @@ function validateClientSchemaBoundaries(graph, options) {
247489
247700
  }
247490
247701
  return diagnostics;
247491
247702
  }
247703
+ init_validate();
247704
+ function diagnostic(code, message, file, suggestion) {
247705
+ const metadata = COMPILER_DIAGNOSTIC_CODES[code];
247706
+ return {
247707
+ severity: "error",
247708
+ code,
247709
+ message,
247710
+ ...file === undefined ? {} : { file },
247711
+ ...suggestion === undefined ? {} : { suggestion },
247712
+ ...metadata ? { errorCode: metadata.code, docsUrl: metadata.docsUrl } : {}
247713
+ };
247714
+ }
247715
+ function routeLabel(route, controllerPath) {
247716
+ return `${route.method} ${controllerPath}${route.path}`;
247717
+ }
247718
+ function validateEffectPolicies(graph, options = {}) {
247719
+ const diagnostics = [];
247720
+ for (const module of graph.modules) {
247721
+ const commands = new Map(module.commands.map((command) => [command.className, command]));
247722
+ for (const controller of module.controllers) {
247723
+ for (const route of controller.routes) {
247724
+ const label = routeLabel(route, controller.path);
247725
+ if (options.requireRouteEffects && route.effect === undefined) {
247726
+ 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.'));
247727
+ continue;
247728
+ }
247729
+ const effect = route.effect;
247730
+ if (effect === undefined)
247731
+ continue;
247732
+ if (options.requireErrorMappings && effect.errors === undefined) {
247733
+ 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."));
247734
+ }
247735
+ if (options.requireDependencies && effect.dependencies === undefined) {
247736
+ 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."));
247737
+ }
247738
+ if (effect.retry === "explicit" && effect.maxAttempts === undefined) {
247739
+ 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."));
247740
+ }
247741
+ if (route.command && effect.retry === "explicit") {
247742
+ const command = commands.get(route.command);
247743
+ if (command && command.idempotency !== "required") {
247744
+ 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".'));
247745
+ }
247746
+ }
247747
+ }
247748
+ }
247749
+ }
247750
+ return diagnostics;
247751
+ }
247492
247752
  function withDefaultGovernance(options) {
247493
247753
  return options.commandCapabilities === undefined ? { ...options, commandCapabilities: {
247494
247754
  requirePersistentAdapters: true,
@@ -247507,7 +247767,8 @@ async function compileProject(options) {
247507
247767
  const diagnostics = [
247508
247768
  ...graph.diagnostics ?? [],
247509
247769
  ...validateGraph(graph, options),
247510
- ...validateClientSchemaBoundaries(graph, options)
247770
+ ...validateClientSchemaBoundaries(graph, options),
247771
+ ...validateEffectPolicies(graph, options.effect)
247511
247772
  ];
247512
247773
  if (diagnostics.some((item) => item.code === "runtime-injection-disallowed")) {
247513
247774
  return { diagnostics, graph, written: [] };
@@ -247571,7 +247832,8 @@ async function checkProject(options) {
247571
247832
  const diagnostics = [
247572
247833
  ...graph.diagnostics ?? [],
247573
247834
  ...validateGraph(graph, options),
247574
- ...validateClientSchemaBoundaries(graph, options)
247835
+ ...validateClientSchemaBoundaries(graph, options),
247836
+ ...validateEffectPolicies(graph, options.effect)
247575
247837
  ];
247576
247838
  if (diagnostics.some((item) => item.code === "runtime-injection-disallowed")) {
247577
247839
  return { diagnostics, graph, upToDate: false, mismatches: ["Runtime DI must be migrated to constructor injection."] };
@@ -247653,7 +247915,7 @@ init_delivery_schema();
247653
247915
  init_validate();
247654
247916
  var compare2 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
247655
247917
  var sorted = (values) => [...new Set(values)].sort(compare2);
247656
- function diagnostic(error) {
247918
+ function diagnostic2(error) {
247657
247919
  return {
247658
247920
  severity: error.severity,
247659
247921
  code: error.code,
@@ -247678,8 +247940,8 @@ function createDeliveryPlan(graph, input) {
247678
247940
  }] };
247679
247941
  }
247680
247942
  const diagnostics = [
247681
- ...(graph.diagnostics ?? []).map(diagnostic),
247682
- ...validateGraph(graph, { strict: true }).map(diagnostic)
247943
+ ...(graph.diagnostics ?? []).map(diagnostic2),
247944
+ ...validateGraph(graph, { strict: true }).map(diagnostic2)
247683
247945
  ];
247684
247946
  const fail = (code, message, suggestion) => {
247685
247947
  diagnostics.push({ severity: "error", code, message, suggestion });
@@ -249134,7 +249396,12 @@ var DEFAULT_SUPACLOUD_CONFIG = {
249134
249396
  idempotency: true,
249135
249397
  transaction: true
249136
249398
  },
249137
- moduleBoundaryPreset: "modular-monolith"
249399
+ moduleBoundaryPreset: "modular-monolith",
249400
+ effect: {
249401
+ requireRouteEffects: true,
249402
+ requireErrorMappings: true,
249403
+ requireDependencies: true
249404
+ }
249138
249405
  };
249139
249406
  function defineSupacloudConfig(config = {}) {
249140
249407
  if (config.delivery !== undefined)
@@ -249146,7 +249413,8 @@ function defineSupacloudConfig(config = {}) {
249146
249413
  ...DEFAULT_SUPACLOUD_CONFIG,
249147
249414
  ...config,
249148
249415
  include: config.include ?? [...DEFAULT_SUPACLOUD_CONFIG.include],
249149
- graphql: config.graphql ?? DEFAULT_SUPACLOUD_CONFIG.graphql
249416
+ graphql: config.graphql ?? DEFAULT_SUPACLOUD_CONFIG.graphql,
249417
+ effect: config.effect === undefined ? { ...DEFAULT_SUPACLOUD_CONFIG.effect } : { ...DEFAULT_SUPACLOUD_CONFIG.effect, ...config.effect }
249150
249418
  };
249151
249419
  }
249152
249420
  function validateGovernanceConfig(config) {
@@ -249203,6 +249471,16 @@ function validateGovernanceConfig(config) {
249203
249471
  if (config[key] !== undefined && typeof config[key] !== "boolean")
249204
249472
  throw new Error(`${key} must be a boolean.`);
249205
249473
  }
249474
+ 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"]) {
249479
+ if (config.effect[key] !== undefined && typeof config.effect[key] !== "boolean") {
249480
+ throw new Error(`effect.${key} must be boolean.`);
249481
+ }
249482
+ }
249483
+ }
249206
249484
  }
249207
249485
  function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
249208
249486
  const resolved = defineSupacloudConfig(config);
@@ -249223,6 +249501,7 @@ function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
249223
249501
  ...resolved.allowRouteCommandBindings === undefined ? {} : { allowRouteCommandBindings: resolved.allowRouteCommandBindings },
249224
249502
  ...resolved.disallowControllerDirectDb === undefined ? {} : { disallowControllerDirectDb: resolved.disallowControllerDirectDb },
249225
249503
  ...resolved.detectOrphanModules === undefined ? {} : { detectOrphanModules: resolved.detectOrphanModules },
249504
+ effect: resolved.effect === undefined ? { ...DEFAULT_SUPACLOUD_CONFIG.effect } : { ...DEFAULT_SUPACLOUD_CONFIG.effect, ...resolved.effect },
249226
249505
  treeShakeUnusedProviders: resolved.treeShakeUnusedProviders ?? DEFAULT_SUPACLOUD_CONFIG.treeShakeUnusedProviders,
249227
249506
  graphql: resolved.graphql ? {
249228
249507
  ...resolved.graphql,
@@ -249371,16 +249650,16 @@ var starter_metadata_default = {
249371
249650
  generatedBy: "supacloud/starter-metadata-v1",
249372
249651
  packages: {
249373
249652
  compiler: {
249374
- version: "0.40.1"
249653
+ version: "0.41.0"
249375
249654
  },
249376
249655
  app: {
249377
- version: "0.23.0",
249656
+ version: "0.24.0",
249378
249657
  dependencies: {
249379
249658
  rxjs: "7.8.2"
249380
249659
  }
249381
249660
  },
249382
249661
  elysia: {
249383
- version: "0.27.1"
249662
+ version: "0.28.0"
249384
249663
  },
249385
249664
  commands: {
249386
249665
  version: "0.9.1"
@@ -249734,6 +250013,7 @@ function baseFiles(name, sdkDependencies) {
249734
250013
  dependencies: {
249735
250014
  "@supacloud/app": `^${appMetadata.version}`,
249736
250015
  "@supacloud/elysia": `^${elysiaMetadata.version}`,
250016
+ effect: "3.22.1",
249737
250017
  elysia: "2.0.0-beta.21",
249738
250018
  ...sdkDependencies ?? { rxjs: appMetadata.dependencies.rxjs }
249739
250019
  },
@@ -249778,6 +250058,11 @@ export default defineSupacloudConfig({
249778
250058
  outDir: "generated",
249779
250059
  strict: true,
249780
250060
  moduleBoundaryPreset: "modular-monolith",
250061
+ effect: {
250062
+ requireRouteEffects: true,
250063
+ requireErrorMappings: true,
250064
+ requireDependencies: true,
250065
+ },
249781
250066
  typeSafety: { scanProductionSource: true, noAnyInGenerated: true },
249782
250067
  disallowControllerDirectDb: true,
249783
250068
  detectOrphanModules: true,
@@ -249806,20 +250091,34 @@ export const CreateOrderBody = t.Object({ name: t.String({ minLength: 1 }) });
249806
250091
  export const OrderResult = t.Object({ id: t.String(), name: t.String() });
249807
250092
  export const HealthResult = t.Object({ ok: t.Boolean() });
249808
250093
  `,
249809
- "src/orders/orders.ts": `import { Body, Controller, Get, Param, Post, defineFeatureSlice } from "@supacloud/app";
250094
+ "src/orders/orders.ts": `import * as Effect from "effect/Effect";
250095
+ import { Body, Controller, Get, Param, Post, defineFeatureSlice } from "@supacloud/app";
249810
250096
  import { status } from "elysia";
249811
250097
  import { OrderParams, CreateOrderBody, OrderResult, HealthResult } from "./contracts";
249812
250098
  @Controller("/orders")
249813
250099
  export class OrdersController {
249814
- @Get("/health", { responses: { 200: HealthResult } })
249815
- health(): { ok: boolean } { return { ok: true }; }
249816
-
249817
- @Get("/:id", { params: OrderParams, responses: { 200: OrderResult } })
249818
- get(@Param("id") id: string): { id: string; name: string } { return { id, name: "demo" }; }
249819
-
249820
- @Post("/", { body: CreateOrderBody, responses: { 201: OrderResult } })
250100
+ @Get("/health", {
250101
+ responses: { 200: HealthResult },
250102
+ effect: { required: true, dependencies: [], errors: [], retry: "none" },
250103
+ })
250104
+ health(): Effect.Effect<{ ok: boolean }, never, never> { return Effect.succeed({ ok: true }); }
250105
+
250106
+ @Get("/:id", {
250107
+ params: OrderParams,
250108
+ responses: { 200: OrderResult },
250109
+ effect: { required: true, dependencies: [], errors: [], retry: "none" },
250110
+ })
250111
+ get(@Param("id") id: string): Effect.Effect<{ id: string; name: string }, never, never> {
250112
+ return Effect.succeed({ id, name: "demo" });
250113
+ }
250114
+
250115
+ @Post("/", {
250116
+ body: CreateOrderBody,
250117
+ responses: { 201: OrderResult },
250118
+ effect: { required: true, dependencies: [], errors: [], retry: "none" },
250119
+ })
249821
250120
  create(@Body() body: { name: string }) {
249822
- return status(201, { id: "demo", name: body.name });
250121
+ return Effect.succeed(status(201, { id: "demo", name: body.name }));
249823
250122
  }
249824
250123
  }
249825
250124
 
@@ -249993,22 +250292,27 @@ function minimalTemplate(name, sdkDependencies) {
249993
250292
  "src/features/health/contracts.ts": `import { t } from "elysia";
249994
250293
  export const HealthResult = t.Object({ ok: t.Boolean() });
249995
250294
  `,
249996
- "src/features/health/health.ts": `import { Controller, Get, Module } from "@supacloud/app/core";
250295
+ "src/features/health/health.ts": `import * as Effect from "effect/Effect";
250296
+ import { Controller, Get, Module } from "@supacloud/app/core";
249997
250297
  import { HealthResult } from "./contracts";
249998
250298
  @Controller("/health")
249999
250299
  export class HealthController {
250000
- @Get("/", { responses: { 200: HealthResult } })
250001
- health(): { ok: boolean } { return { ok: true }; }
250300
+ @Get("/", {
250301
+ responses: { 200: HealthResult },
250302
+ effect: { required: true, dependencies: [], errors: [], retry: "none" },
250303
+ })
250304
+ health(): Effect.Effect<{ ok: boolean }, never, never> { return Effect.succeed({ ok: true }); }
250002
250305
  }
250003
250306
 
250004
250307
  @Module({ name: "health", tags: ["type:feature"], controllers: [HealthController] })
250005
250308
  export class HealthModule {}
250006
250309
  `,
250007
- "src/features/health/health.test.ts": `import { expect, test } from "bun:test";
250310
+ "src/features/health/health.test.ts": `import * as Effect from "effect/Effect";
250311
+ import { expect, test } from "bun:test";
250008
250312
  import { HealthController } from "./health";
250009
250313
 
250010
- test("health is a public, side-effect-free operation", () => {
250011
- expect(new HealthController().health()).toEqual({ ok: true });
250314
+ test("health is a public, side-effect-free operation", async () => {
250315
+ expect(await Effect.runPromise(new HealthController().health())).toEqual({ ok: true });
250012
250316
  });
250013
250317
  `,
250014
250318
  "src/app.module.ts": `import { Module } from "@supacloud/app/core";
@@ -250533,7 +250837,8 @@ export async function createDeliveryWorker(modules: CompiledModule[], lifecycle:
250533
250837
  `;
250534
250838
 
250535
250839
  // src/shared/tools/app-starter-upload.ts
250536
- var STARTER_UPLOAD_FEATURE = `import { Body, Controller, Inject, Injectable, Param, Post, DB_CLIENT, REQUEST_CONTEXT } from "@supacloud/app";
250840
+ var STARTER_UPLOAD_FEATURE = `import * as Effect from "effect/Effect";
250841
+ import { Body, Controller, Inject, Injectable, Param, Post, DB_CLIENT, REQUEST_CONTEXT } from "@supacloud/app";
250537
250842
  import { ApplicationError } from "@supacloud/elysia";
250538
250843
  import { UploadBody, UploadLocation, UploadParams, UploadResult } from "./contracts";
250539
250844
 
@@ -250563,13 +250868,47 @@ export class ReviewUploads {
250563
250868
  export class ReviewUploadsController {
250564
250869
  constructor(@Inject(ReviewUploads) private readonly uploads: ReviewUploads) {}
250565
250870
 
250566
- @Post("/:id/attachment-upload", { params: UploadParams, body: UploadBody, responses: { 200: UploadLocation } })
250567
- preview(@Param("id") id: string, @Body() body: { artifactId: string; expectedVersion: number }): Promise<ReviewUploadLocation> {
250568
- return this.uploads.preview({ reviewId: id, artifactId: body.artifactId, expectedVersion: body.expectedVersion });
250569
- }
250570
- @Post("/:id/attachment-registration", { params: UploadParams, body: UploadBody, responses: { 200: UploadResult } })
250571
- register(@Param("id") id: string, @Body() body: { artifactId: string; expectedVersion: number }): Promise<ReviewUploadResult> {
250572
- return this.uploads.register({ reviewId: id, artifactId: body.artifactId, expectedVersion: body.expectedVersion });
250871
+ @Post("/:id/attachment-upload", {
250872
+ params: UploadParams,
250873
+ body: UploadBody,
250874
+ responses: { 200: UploadLocation },
250875
+ effect: {
250876
+ required: true,
250877
+ dependencies: [],
250878
+ errors: [
250879
+ { tag: "AUTHENTICATION_REQUIRED", status: 401, code: "AUTHENTICATION_REQUIRED" },
250880
+ { tag: "UPLOAD_INPUT_INVALID", status: 400, code: "UPLOAD_INPUT_INVALID" },
250881
+ { tag: "UPLOAD_DENIED", status: 403, code: "UPLOAD_DENIED" },
250882
+ { tag: "UPLOADS_UNAVAILABLE", status: 501, code: "UPLOADS_UNAVAILABLE" },
250883
+ ],
250884
+ retry: "none",
250885
+ },
250886
+ })
250887
+ preview(@Param("id") id: string, @Body() body: { artifactId: string; expectedVersion: number }): Effect.Effect<ReviewUploadLocation, unknown, never> {
250888
+ return Effect.promise(() => this.uploads.preview({ reviewId: id, artifactId: body.artifactId, expectedVersion: body.expectedVersion }));
250889
+ }
250890
+ @Post("/:id/attachment-registration", {
250891
+ params: UploadParams,
250892
+ body: UploadBody,
250893
+ responses: { 200: UploadResult },
250894
+ effect: {
250895
+ required: true,
250896
+ dependencies: [],
250897
+ errors: [
250898
+ { tag: "AUTHENTICATION_REQUIRED", status: 401, code: "AUTHENTICATION_REQUIRED" },
250899
+ { tag: "UPLOAD_INPUT_INVALID", status: 400, code: "UPLOAD_INPUT_INVALID" },
250900
+ { tag: "UPLOAD_DENIED", status: 403, code: "UPLOAD_DENIED" },
250901
+ { tag: "UPLOAD_CONTENT_INVALID", status: 400, code: "UPLOAD_CONTENT_INVALID" },
250902
+ { tag: "COMMAND_IDEMPOTENCY_CONFLICT", status: 409, code: "COMMAND_IDEMPOTENCY_CONFLICT" },
250903
+ { tag: "COMMAND_REJECTED", status: 403, code: "COMMAND_REJECTED" },
250904
+ { tag: "UPLOADS_UNAVAILABLE", status: 501, code: "UPLOADS_UNAVAILABLE" },
250905
+ { tag: "UPLOAD_OUTCOME_UNKNOWN", status: 503, code: "UPLOAD_OUTCOME_UNKNOWN" },
250906
+ ],
250907
+ retry: "none",
250908
+ },
250909
+ })
250910
+ register(@Param("id") id: string, @Body() body: { artifactId: string; expectedVersion: number }): Effect.Effect<ReviewUploadResult, unknown, never> {
250911
+ return Effect.promise(() => this.uploads.register({ reviewId: id, artifactId: body.artifactId, expectedVersion: body.expectedVersion }));
250573
250912
  }
250574
250913
  }
250575
250914
  `;
@@ -250853,6 +251192,10 @@ function rows(value: unknown): Record<string, unknown>[] {
250853
251192
  if (!Array.isArray(value)) throw new TypeError("Invalid database result");
250854
251193
  return value.map(record);
250855
251194
  }
251195
+ function isPublicConflict(value: unknown): boolean {
251196
+ return value instanceof ApplicationError && value.status === 409
251197
+ || isRecord(value) && value.expose === true && value.status === 409 && typeof value.code === "string";
251198
+ }
250856
251199
  function input(value: unknown): Approval {
250857
251200
  const item = record(value);
250858
251201
  if (typeof item.id !== "string" || !item.id.length || typeof item.expectedVersion !== "number"
@@ -250967,7 +251310,7 @@ export async function createReviewPostgresAdapters(options: ReviewPostgresOption
250967
251310
  execute: tx => scope.run(tx, async () => {
250968
251311
  try { return await next(); }
250969
251312
  catch (error) {
250970
- if (error instanceof ApplicationError && error.status === 409) {
251313
+ if (isPublicConflict(error)) {
250971
251314
  throw new CommandError("COMMAND_IDEMPOTENCY_CONFLICT");
250972
251315
  }
250973
251316
  throw error;
@@ -251276,6 +251619,7 @@ function appStarterFiles(name) {
251276
251619
  "drizzle-orm": dbMetadata.peerDependencies["drizzle-orm"],
251277
251620
  "@supacloud/js": `^${sdkMetadata.version}`,
251278
251621
  "@supabase/supabase-js": sdkMetadata.peerDependencies["@supabase/supabase-js"],
251622
+ effect: "3.22.1",
251279
251623
  elysia: "2.0.0-beta.21",
251280
251624
  rxjs: appMetadata2.dependencies.rxjs
251281
251625
  },
@@ -251331,6 +251675,11 @@ export default defineSupacloudConfig({
251331
251675
  outDir: "generated",
251332
251676
  strict: true,
251333
251677
  moduleBoundaryPreset: "modular-monolith",
251678
+ effect: {
251679
+ requireRouteEffects: true,
251680
+ requireErrorMappings: true,
251681
+ requireDependencies: true,
251682
+ },
251334
251683
  typeSafety: { scanProductionSource: true, noAnyInGenerated: true },
251335
251684
  disallowControllerDirectDb: true,
251336
251685
  detectOrphanModules: true,
@@ -251531,7 +251880,8 @@ export function createCompiledModules(): never {
251531
251880
  throw new Error("Run bun run compile before starting the application");
251532
251881
  }
251533
251882
  `,
251534
- "src/review/review.ts": `import {
251883
+ "src/review/review.ts": `import * as Effect from "effect/Effect";
251884
+ import {
251535
251885
  Body, Command, Controller, DB_CLIENT, Get, Inject, Param, Post,
251536
251886
  defineFeatureSlice, defineFeatureSpec, type Aspect,
251537
251887
  } from "@supacloud/app";
@@ -251601,12 +251951,36 @@ export class ApproveReview {
251601
251951
  export class ReviewController {
251602
251952
  constructor(@Inject(ApproveReview) private readonly approveReview: ApproveReview) {}
251603
251953
 
251604
- @Get("/health", { responses: { 200: HealthResult } })
251605
- health(): { ok: boolean } { return { ok: true }; }
251606
-
251607
- @Post("/:id/approve", { command: ApproveReview, params: Params, body: ApproveBody, responses: { 200: ReviewResult } })
251608
- approve(@Param("id") id: string, @Body() body: { expectedVersion: number }): Promise<Review> {
251609
- return this.approveReview.execute(id, body.expectedVersion);
251954
+ @Get("/health", {
251955
+ responses: { 200: HealthResult },
251956
+ effect: { required: true, dependencies: [], errors: [], retry: "none" },
251957
+ })
251958
+ health(): Effect.Effect<{ ok: boolean }, never, never> { return Effect.succeed({ ok: true }); }
251959
+
251960
+ @Post("/:id/approve", {
251961
+ command: ApproveReview,
251962
+ params: Params,
251963
+ body: ApproveBody,
251964
+ responses: { 200: ReviewResult },
251965
+ effect: {
251966
+ required: true,
251967
+ dependencies: [],
251968
+ errors: [
251969
+ { tag: "AUTHENTICATION_REQUIRED", status: 401, code: "AUTHENTICATION_REQUIRED" },
251970
+ { tag: "REVIEW_NOT_FOUND", status: 404, code: "REVIEW_NOT_FOUND" },
251971
+ { tag: "REVIEW_PERMISSION_DENIED", status: 403, code: "REVIEW_PERMISSION_DENIED" },
251972
+ { tag: "REVIEW_CONFLICT", status: 409, code: "REVIEW_CONFLICT" },
251973
+ { tag: "FEATURE_TRANSITION_CONFLICT", status: 409, code: "FEATURE_TRANSITION_CONFLICT" },
251974
+ { tag: "COMMAND_IDEMPOTENCY_CONFLICT", status: 409, code: "COMMAND_IDEMPOTENCY_CONFLICT" },
251975
+ { tag: "COMMAND_REJECTED", status: 403, code: "COMMAND_REJECTED" },
251976
+ { tag: "REVIEW_COMMAND_UNSUPPORTED", status: 501, code: "REVIEW_COMMAND_UNSUPPORTED" },
251977
+ { tag: "COMMAND_OUTCOME_UNKNOWN", status: 503, code: "COMMAND_OUTCOME_UNKNOWN" },
251978
+ ],
251979
+ retry: "none",
251980
+ },
251981
+ })
251982
+ approve(@Param("id") id: string, @Body() body: { expectedVersion: number }): Effect.Effect<Review, unknown, never> {
251983
+ return Effect.promise(() => this.approveReview.execute(id, body.expectedVersion));
251610
251984
  }
251611
251985
  }
251612
251986
 
@@ -252145,7 +252519,8 @@ export class ${className}Service {
252145
252519
  }
252146
252520
  }
252147
252521
  `,
252148
- [`${name}.controller.ts`]: `import { Controller, Get, Inject, Param } from "@supacloud/app";
252522
+ [`${name}.controller.ts`]: `import * as Effect from "effect/Effect";
252523
+ import { Controller, Get, Inject, Param } from "@supacloud/app";
252149
252524
  import { ${className}Params, ${className}Response, type ${className}Result } from "./${name}.model";
252150
252525
  import { ${className}Service } from "./${name}.service";
252151
252526
 
@@ -252153,9 +252528,13 @@ import { ${className}Service } from "./${name}.service";
252153
252528
  export class ${className}Controller {
252154
252529
  constructor(@Inject(${className}Service) readonly service: Pick<${className}Service, "find">) {}
252155
252530
 
252156
- @Get("/:id", { params: ${className}Params, responses: { 200: ${className}Response } })
252157
- find(@Param("id") id: string): Promise<${className}Result> {
252158
- return this.service.find(id);
252531
+ @Get("/:id", {
252532
+ params: ${className}Params,
252533
+ responses: { 200: ${className}Response },
252534
+ effect: { required: true, dependencies: [], errors: [], retry: "none" },
252535
+ })
252536
+ find(@Param("id") id: string): Effect.Effect<${className}Result, unknown, never> {
252537
+ return Effect.promise(() => this.service.find(id));
252159
252538
  }
252160
252539
  }
252161
252540
  `,
@@ -252190,19 +252569,20 @@ test("${name} preserves permission denial without a privileged fallback", async
252190
252569
  await expect(service.find("example")).rejects.toBe(denied);
252191
252570
  });
252192
252571
  `,
252193
- [`${name}.controller.test.ts`]: `import { expect, test } from "bun:test";
252572
+ [`${name}.controller.test.ts`]: `import * as Effect from "effect/Effect";
252573
+ import { expect, test } from "bun:test";
252194
252574
  import { ${className}Controller } from "./${name}.controller";
252195
252575
 
252196
252576
  test("${name} controller delegates to the supplied asynchronous read port", async () => {
252197
252577
  // Unit-test double only; the generated runtime service remains fail-closed.
252198
252578
  const controller = new ${className}Controller({ find: async (id) => ({ id }) });
252199
- expect(await controller.find("example")).toEqual({ id: "example" });
252579
+ expect(await Effect.runPromise(controller.find("example"))).toEqual({ id: "example" });
252200
252580
  });
252201
252581
 
252202
252582
  test("${name} controller preserves an asynchronous read failure", async () => {
252203
252583
  const failure = new Error("read port unavailable");
252204
252584
  const controller = new ${className}Controller({ find: async () => { throw failure; } });
252205
- await expect(controller.find("example")).rejects.toBe(failure);
252585
+ await expect(Effect.runPromise(controller.find("example"))).rejects.toThrow("read port unavailable");
252206
252586
  });
252207
252587
  `
252208
252588
  };
@@ -253726,6 +254106,91 @@ function formatReconcileReport(report) {
253726
254106
  return lines.join(`
253727
254107
  `);
253728
254108
  }
254109
+ function moduleObjects2(module) {
254110
+ return [
254111
+ ...module.tables.map((table) => ({
254112
+ kind: "table",
254113
+ name: table,
254114
+ module: module.name,
254115
+ source: null,
254116
+ tests: []
254117
+ })),
254118
+ ...module.policies.map((policy) => ({
254119
+ kind: "policy",
254120
+ name: policy.name,
254121
+ module: module.name,
254122
+ table: policy.table,
254123
+ source: policy.source,
254124
+ tests: policy.tests ?? []
254125
+ })),
254126
+ ...module.functions.map((fn) => ({
254127
+ kind: "function",
254128
+ name: fn.name,
254129
+ module: module.name,
254130
+ source: fn.source,
254131
+ tests: fn.tests ?? [],
254132
+ security: fn.security,
254133
+ permission: fn.permission,
254134
+ transaction: fn.transaction,
254135
+ audit: fn.audit,
254136
+ idempotency: fn.idempotency
254137
+ })),
254138
+ ...module.triggers.map((trigger) => ({
254139
+ kind: "trigger",
254140
+ name: trigger.name,
254141
+ module: module.name,
254142
+ table: trigger.table,
254143
+ source: trigger.source,
254144
+ tests: []
254145
+ })),
254146
+ ...module.grants.map((grant) => ({
254147
+ kind: "grant",
254148
+ name: `${grant.object}:${grant.privilege}:${grant.role}`,
254149
+ module: module.name,
254150
+ source: grant.source,
254151
+ tests: []
254152
+ }))
254153
+ ];
254154
+ }
254155
+ function objectMatchesTarget(object, target) {
254156
+ return object.name === target || object.table === target || object.kind === "policy" && `${object.table}.${object.name}` === target || object.kind === "grant" && object.name.startsWith(`${target}:`);
254157
+ }
254158
+ function buildDatabaseAiContext(modules, root, moduleName, target) {
254159
+ const selected = moduleName ? modules.filter((module) => module.name === moduleName) : [...modules];
254160
+ if (moduleName && selected.length === 0) {
254161
+ throw new Error(`未找到数据库模块: ${moduleName}(可用: ${modules.map((module) => module.name).join(", ") || "none"})`);
254162
+ }
254163
+ const allObjects = selected.flatMap(moduleObjects2);
254164
+ const objects = target ? allObjects.filter((object) => objectMatchesTarget(object, target)) : allObjects;
254165
+ if (target && objects.length === 0)
254166
+ throw new Error(`未找到数据库对象: ${target}`);
254167
+ return {
254168
+ version: 1,
254169
+ scope: "database-ai-context",
254170
+ root,
254171
+ selectedModules: selected.map((module) => module.name),
254172
+ target: target ?? null,
254173
+ objects,
254174
+ maintenance: {
254175
+ structureSource: "drizzle",
254176
+ behaviorSource: "sql",
254177
+ generatedPaths: ["generated/", "bootstrap/schema.sql", "output/database-audit/"],
254178
+ protectedPaths: ["migrations/", "supabase/migrations/", "generated/"],
254179
+ forwardMigrationPath: "migrations/<timestamp>_<intent>.sql",
254180
+ rules: [
254181
+ "Edit the selected maintained source, not a generated snapshot.",
254182
+ "Keep RPC, RLS, trigger and grant behavior in explicit SQL sources.",
254183
+ "Create a new forward migration; never rewrite an applied migration.",
254184
+ "Run lint, focused behavior tests and catalog reconciliation after edits."
254185
+ ]
254186
+ },
254187
+ verification: [
254188
+ "supacloud-cli db lint --module_file db/modules.ts",
254189
+ "supacloud-cli db explain --target <object>",
254190
+ 'supacloud-cli db module_check --module_file db/modules.ts --database_url "$DATABASE_URL"'
254191
+ ]
254192
+ };
254193
+ }
253729
254194
  async function runModuleCheck(module, executor, schemas = ["public"]) {
253730
254195
  const catalog = await readCatalog(executor, schemas);
253731
254196
  return reconcileModule(module, catalog);
@@ -253769,6 +254234,10 @@ async function runExplain2(args, root) {
253769
254234
  const text = explainObject(manifest, target);
253770
254235
  return textResult2(text, text.startsWith("未找到对象"));
253771
254236
  }
254237
+ async function runContext2(args, root) {
254238
+ const modules = await loadDatabaseModules(root, args.module_file);
254239
+ return textResult2(JSON.stringify(buildDatabaseAiContext(modules, root, args.module, args.target), null, 2));
254240
+ }
253772
254241
  var defaultLiteSpawn = async (command, cwd) => {
253773
254242
  const proc = Bun.spawn({ cmd: command, cwd, stdout: "pipe", stderr: "pipe" });
253774
254243
  const [exitCode, stdout, stderr] = await Promise.all([
@@ -253826,12 +254295,12 @@ function registerDbGovernanceTools(server2, options = {}) {
253826
254295
  const environment = options.environment || process.env;
253827
254296
  const fallbackRoot = options.currentWorkingDirectory || process.cwd();
253828
254297
  const liteSpawn = options.liteSpawn || defaultLiteSpawn;
253829
- server2.tool("db", "Local database governance (@supacloud/db): lint declared modules, explain objects, reconcile against a live catalog or a local SupaCloud Lite project. Actions: lint, explain, module_check", {
253830
- action: withDescription(stringEnum(["lint", "explain", "module_check"]), "Database governance action"),
253831
- module: optional(String2(), "[lint] Only lint this manifest module (default: all)"),
254298
+ server2.tool("db", "Local database governance (@supacloud/db): provide bounded AI context, lint declared modules, explain objects, reconcile against a live catalog or a local SupaCloud Lite project. Actions: context, lint, explain, module_check", {
254299
+ action: withDescription(stringEnum(["context", "lint", "explain", "module_check"]), "Database governance action"),
254300
+ module: optional(String2(), "[context/lint] Only select this manifest module (default: all)"),
253832
254301
  root: optional(String2(), "[*] Project root (default: current directory)"),
253833
254302
  module_file: optional(String2(), "[*] File exporting defineDatabaseModule(...) (default: <root>/db/modules.ts)"),
253834
- target: optional(String2(), "[explain] Policy / function / table name to explain"),
254303
+ target: optional(String2(), "[context/explain] Policy / function / table name to explain"),
253835
254304
  database_url: optional(String2(), "[module_check] Postgres connection URL (default: DATABASE_URL)"),
253836
254305
  schema: optional(String2(), "[module_check] Comma-separated schemas to inspect (default: public)"),
253837
254306
  lite: optional(Boolean2(), "[module_check] Reconcile against a local SupaCloud Lite project (runs supacloud-lite db check)"),
@@ -253839,6 +254308,8 @@ function registerDbGovernanceTools(server2, options = {}) {
253839
254308
  }, async (request) => {
253840
254309
  const root = resolve25(request.root || fallbackRoot);
253841
254310
  switch (request.action) {
254311
+ case "context":
254312
+ return runContext2(request, root);
253842
254313
  case "lint":
253843
254314
  return runLint(request, root);
253844
254315
  case "explain":
@@ -256652,7 +257123,7 @@ function registerRemoteDevTools(server2, options = {}) {
256652
257123
  // package.json
256653
257124
  var package_default = {
256654
257125
  name: "@supacloud/cli",
256655
- version: "0.72.0",
257126
+ version: "0.73.0",
256656
257127
  description: "Project-scoped CLI for SupaCloud users",
256657
257128
  type: "module",
256658
257129
  main: "./dist/index.js",
@@ -256686,7 +257157,7 @@ var package_default = {
256686
257157
  directory: "packages/cli"
256687
257158
  },
256688
257159
  dependencies: {
256689
- "@supacloud/compiler": "^0.40.1",
257160
+ "@supacloud/compiler": "^0.41.0",
256690
257161
  "@supacloud/delivery": "0.8.0",
256691
257162
  "@supacloud/db": "^0.14.0",
256692
257163
  fflate: "^0.8.3",
@@ -256699,6 +257170,7 @@ var package_default = {
256699
257170
  devDependencies: {
256700
257171
  "@supacloud/elysia": "^0.24.1",
256701
257172
  "@types/bun": "^1.4.2",
257173
+ effect: "3.22.1",
256702
257174
  elysia: "2.0.0-beta.21",
256703
257175
  typescript: "^7.0.2"
256704
257176
  }
@@ -256981,6 +257453,8 @@ EXAMPLES
256981
257453
  ${preferredCommand} context --format json
256982
257454
  ${preferredCommand} context --target CaseModule --format json
256983
257455
  ${preferredCommand} doctor
257456
+ ${preferredCommand} db context --root . --module_file db/modules.ts --module cases
257457
+ ${preferredCommand} db context --root . --module_file db/modules.ts --target public.case_create
256984
257458
  ${preferredCommand} db lint --root . --module_file db/modules.ts
256985
257459
  ${preferredCommand} db explain --target public.cases --module_file db/modules.ts
256986
257460
  ${preferredCommand} db module_check --module_file db/modules.ts --database_url "postgresql://..."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.72.0",
3
+ "version": "0.73.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.40.1",
37
+ "@supacloud/compiler": "^0.41.0",
38
38
  "@supacloud/delivery": "0.8.0",
39
39
  "@supacloud/db": "^0.14.0",
40
40
  "fflate": "^0.8.3",
@@ -47,6 +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
51
  "elysia": "2.0.0-beta.21",
51
52
  "typescript": "^7.0.2"
52
53
  }
@@ -31,12 +31,21 @@ Use `supacloud-cli` as the project-level control surface and keep durable change
31
31
 
32
32
  1. Classify the request using [references/command-map.md](references/command-map.md).
33
33
  2. For database work, load [references/database-workflow.md](references/database-workflow.md) and choose either new-change or historical-drift reconciliation.
34
- 3. Prefer the smallest read-only inspection that proves current state.
35
- 4. Create or update version-controlled artifacts before remote writes.
36
- 5. Verify locally, inspect the diff, and run the narrowest relevant tests.
37
- 6. Preview remote changes with `--dry_run`.
38
- 7. Apply only within the user-authorized environment and scope.
39
- 8. Read back migration history and affected resources; report exact evidence and any remaining drift.
34
+ 3. For AI-assisted database changes, first request bounded context:
35
+
36
+ ```bash
37
+ supacloud-cli db context --module_file db/modules.ts --module <module>
38
+ supacloud-cli db context --module_file db/modules.ts --target <object>
39
+ ```
40
+
41
+ Use the returned source/test paths and ownership boundary; do not scan or
42
+ edit the complete schema dump.
43
+ 4. Prefer the smallest read-only inspection that proves current state.
44
+ 5. Create or update version-controlled artifacts before remote writes.
45
+ 6. Verify locally, inspect the diff, and run the narrowest relevant tests.
46
+ 7. Preview remote changes with `--dry_run`.
47
+ 8. Apply only within the user-authorized environment and scope.
48
+ 9. Read back migration history and affected resources; report exact evidence and any remaining drift.
40
49
 
41
50
  ## Project endpoint inspection
42
51