@supacloud/cli 0.71.1 → 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/README.md CHANGED
@@ -532,6 +532,7 @@ supacloud-cli database query --sql "select now()"
532
532
  supacloud-cli database query --ref abc123 --file ./queries/vector-search.sql
533
533
  supacloud-cli database migration_inventory --ref abc123
534
534
  supacloud-cli database lint_migrations --dir supabase/migrations
535
+ supacloud-cli database rebase_migrations --dir supabase/migrations --baseline_file backups/schema.sql --baseline_version 20261008000000 --output_dir supabase/migrations-rebased
535
536
  supacloud-cli database push_migrations --ref abc123 --dir supabase/migrations --dry_run
536
537
  supacloud-cli supabase migration_new --name add_accounts
537
538
  supacloud-cli supabase db_diff --schema public --name add_accounts
@@ -638,6 +639,17 @@ even when strict destructive-risk mode is not enabled.
638
639
  `CREATE/DROP INDEX CONCURRENTLY`, `VACUUM`, and other non-transactional work must
639
640
  run through an approved maintenance path outside `push_migrations`.
640
641
 
642
+ `database rebase_migrations` is local-only and never contacts the Management
643
+ API. Without `--write` it only prints the plan; with `--write` it writes a
644
+ reconstructed baseline, the retained forward migrations, and a
645
+ `migration-rebase.manifest.json` (with SHA-256 records for the complete source
646
+ history) into a separate `--output_dir`, never the source directory. Capture and
647
+ review a schema-only snapshot first with `--baseline_file` and
648
+ `--baseline_version`, then use `--retain_after_version` to keep newer
649
+ migrations. Review the baseline for omitted data backfills, reference data,
650
+ extensions, grants, RLS, functions and triggers, replay it in a clean database,
651
+ and archive the original directory until clean replay and remote read-back pass.
652
+
641
653
  Migration applications and DDL executions send PostgREST schema reload
642
654
  notifications (`NOTIFY pgrst_<ref>, 'reload schema'`) so schema changes become
643
655
  visible without a manual PostgREST restart. Responses distinguish `notified`
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) {
@@ -213546,21 +213546,18 @@ export default app;
213546
213546
  scripts: { build: "vite build" },
213547
213547
  devDependencies: {
213548
213548
  "@supacloud/function-adapter": "^0.1.0",
213549
- "@sveltejs/kit": "^2.70.3",
213549
+ "@sveltejs/kit": "^3.0.1",
213550
213550
  "@sveltejs/vite-plugin-svelte": "^7.3.0",
213551
213551
  svelte: "^5.57.0",
213552
213552
  vite: "^8.2.2"
213553
213553
  }
213554
213554
  }, null, 2) + `
213555
- `,
213556
- "svelte.config.js": `import adapter from "@supacloud/function-adapter/sveltekit-adapter";
213557
-
213558
- export default { kit: { adapter: adapter() } };
213559
213555
  `,
213560
213556
  "vite.config.ts": `import { sveltekit } from "@sveltejs/kit/vite";
213557
+ import adapter from "@supacloud/function-adapter/sveltekit-adapter";
213561
213558
  import { defineConfig } from "vite";
213562
213559
 
213563
- export default defineConfig({ plugins: [sveltekit()] });
213560
+ export default defineConfig({ plugins: [sveltekit({ adapter: adapter() })] });
213564
213561
  `,
213565
213562
  "src/routes/+server.ts": `export function GET() {
213566
213563
  return Response.json({ function: "${slug}", framework: "sveltekit-function" });
@@ -221501,6 +221498,17 @@ var init_validate = __esm2(() => {
221501
221498
  "route-contract-required": { code: "SC3021", docsUrl: "https://supacloud.dev/errors/SC3021" },
221502
221499
  "route-contract-unverified": { code: "SC3022", docsUrl: "https://supacloud.dev/errors/SC3022" },
221503
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" },
221504
221512
  "invalid-route-response-map": { code: "SC3024", docsUrl: "https://supacloud.dev/errors/SC3024" },
221505
221513
  "invalid-route-response-selector": { code: "SC3025", docsUrl: "https://supacloud.dev/errors/SC3025" },
221506
221514
  "conflicting-route-response-schema": { code: "SC3026", docsUrl: "https://supacloud.dev/errors/SC3026" },
@@ -222029,6 +222037,9 @@ class ModuleGenerator {
222029
222037
  });
222030
222038
  fields.push(`responses: { ${responseFields.join(", ")} }`);
222031
222039
  }
222040
+ if (route.effect !== undefined) {
222041
+ fields.push(`effect: ${JSON.stringify(route.effect)}`);
222042
+ }
222032
222043
  if (route.command)
222033
222044
  fields.push(`command: ${JSON.stringify(route.command)}`);
222034
222045
  if (route.guards && route.guards.length > 0) {
@@ -223841,6 +223852,14 @@ var INTERFACES = `export interface CompiledRoute {
223841
223852
  cookie?: unknown;
223842
223853
  response?: unknown;
223843
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
+ };
223844
223863
  /** Compile-time ownership and transport classification for the route boundary. */
223845
223864
  contract?: {
223846
223865
  body?: "framework" | "domain";
@@ -246268,6 +246287,188 @@ function parseController(input, ctx) {
246268
246287
  Object.assign(route.contract, { [field]: value });
246269
246288
  }
246270
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
+ }
246271
246472
  for (const field of ["body", "params", "query", "headers", "cookie", "response"]) {
246272
246473
  const schemaExpr = getProp(optionsObject, field);
246273
246474
  if (schemaExpr && ts7.isIdentifier(schemaExpr)) {
@@ -246851,6 +247052,13 @@ function stringLiteralProp(obj, name) {
246851
247052
  const expr = getProp(obj, name);
246852
247053
  return expr && ts7.isStringLiteral(expr) ? expr.text : undefined;
246853
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
+ }
246854
247062
  function arrayProp(obj, name) {
246855
247063
  const expr = getProp(obj, name);
246856
247064
  return expr && ts7.isArrayLiteralExpression(expr) ? [...expr.elements] : [];
@@ -247492,6 +247700,55 @@ function validateClientSchemaBoundaries(graph, options) {
247492
247700
  }
247493
247701
  return diagnostics;
247494
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
+ }
247495
247752
  function withDefaultGovernance(options) {
247496
247753
  return options.commandCapabilities === undefined ? { ...options, commandCapabilities: {
247497
247754
  requirePersistentAdapters: true,
@@ -247510,7 +247767,8 @@ async function compileProject(options) {
247510
247767
  const diagnostics = [
247511
247768
  ...graph.diagnostics ?? [],
247512
247769
  ...validateGraph(graph, options),
247513
- ...validateClientSchemaBoundaries(graph, options)
247770
+ ...validateClientSchemaBoundaries(graph, options),
247771
+ ...validateEffectPolicies(graph, options.effect)
247514
247772
  ];
247515
247773
  if (diagnostics.some((item) => item.code === "runtime-injection-disallowed")) {
247516
247774
  return { diagnostics, graph, written: [] };
@@ -247574,7 +247832,8 @@ async function checkProject(options) {
247574
247832
  const diagnostics = [
247575
247833
  ...graph.diagnostics ?? [],
247576
247834
  ...validateGraph(graph, options),
247577
- ...validateClientSchemaBoundaries(graph, options)
247835
+ ...validateClientSchemaBoundaries(graph, options),
247836
+ ...validateEffectPolicies(graph, options.effect)
247578
247837
  ];
247579
247838
  if (diagnostics.some((item) => item.code === "runtime-injection-disallowed")) {
247580
247839
  return { diagnostics, graph, upToDate: false, mismatches: ["Runtime DI must be migrated to constructor injection."] };
@@ -247656,7 +247915,7 @@ init_delivery_schema();
247656
247915
  init_validate();
247657
247916
  var compare2 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
247658
247917
  var sorted = (values) => [...new Set(values)].sort(compare2);
247659
- function diagnostic(error) {
247918
+ function diagnostic2(error) {
247660
247919
  return {
247661
247920
  severity: error.severity,
247662
247921
  code: error.code,
@@ -247681,8 +247940,8 @@ function createDeliveryPlan(graph, input) {
247681
247940
  }] };
247682
247941
  }
247683
247942
  const diagnostics = [
247684
- ...(graph.diagnostics ?? []).map(diagnostic),
247685
- ...validateGraph(graph, { strict: true }).map(diagnostic)
247943
+ ...(graph.diagnostics ?? []).map(diagnostic2),
247944
+ ...validateGraph(graph, { strict: true }).map(diagnostic2)
247686
247945
  ];
247687
247946
  const fail = (code, message, suggestion) => {
247688
247947
  diagnostics.push({ severity: "error", code, message, suggestion });
@@ -249137,7 +249396,12 @@ var DEFAULT_SUPACLOUD_CONFIG = {
249137
249396
  idempotency: true,
249138
249397
  transaction: true
249139
249398
  },
249140
- moduleBoundaryPreset: "modular-monolith"
249399
+ moduleBoundaryPreset: "modular-monolith",
249400
+ effect: {
249401
+ requireRouteEffects: true,
249402
+ requireErrorMappings: true,
249403
+ requireDependencies: true
249404
+ }
249141
249405
  };
249142
249406
  function defineSupacloudConfig(config = {}) {
249143
249407
  if (config.delivery !== undefined)
@@ -249149,7 +249413,8 @@ function defineSupacloudConfig(config = {}) {
249149
249413
  ...DEFAULT_SUPACLOUD_CONFIG,
249150
249414
  ...config,
249151
249415
  include: config.include ?? [...DEFAULT_SUPACLOUD_CONFIG.include],
249152
- 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 }
249153
249418
  };
249154
249419
  }
249155
249420
  function validateGovernanceConfig(config) {
@@ -249206,6 +249471,16 @@ function validateGovernanceConfig(config) {
249206
249471
  if (config[key] !== undefined && typeof config[key] !== "boolean")
249207
249472
  throw new Error(`${key} must be a boolean.`);
249208
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
+ }
249209
249484
  }
249210
249485
  function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
249211
249486
  const resolved = defineSupacloudConfig(config);
@@ -249226,6 +249501,7 @@ function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
249226
249501
  ...resolved.allowRouteCommandBindings === undefined ? {} : { allowRouteCommandBindings: resolved.allowRouteCommandBindings },
249227
249502
  ...resolved.disallowControllerDirectDb === undefined ? {} : { disallowControllerDirectDb: resolved.disallowControllerDirectDb },
249228
249503
  ...resolved.detectOrphanModules === undefined ? {} : { detectOrphanModules: resolved.detectOrphanModules },
249504
+ effect: resolved.effect === undefined ? { ...DEFAULT_SUPACLOUD_CONFIG.effect } : { ...DEFAULT_SUPACLOUD_CONFIG.effect, ...resolved.effect },
249229
249505
  treeShakeUnusedProviders: resolved.treeShakeUnusedProviders ?? DEFAULT_SUPACLOUD_CONFIG.treeShakeUnusedProviders,
249230
249506
  graphql: resolved.graphql ? {
249231
249507
  ...resolved.graphql,
@@ -249374,16 +249650,16 @@ var starter_metadata_default = {
249374
249650
  generatedBy: "supacloud/starter-metadata-v1",
249375
249651
  packages: {
249376
249652
  compiler: {
249377
- version: "0.40.1"
249653
+ version: "0.41.0"
249378
249654
  },
249379
249655
  app: {
249380
- version: "0.23.0",
249656
+ version: "0.24.0",
249381
249657
  dependencies: {
249382
249658
  rxjs: "7.8.2"
249383
249659
  }
249384
249660
  },
249385
249661
  elysia: {
249386
- version: "0.27.1"
249662
+ version: "0.28.0"
249387
249663
  },
249388
249664
  commands: {
249389
249665
  version: "0.9.1"
@@ -249737,6 +250013,7 @@ function baseFiles(name, sdkDependencies) {
249737
250013
  dependencies: {
249738
250014
  "@supacloud/app": `^${appMetadata.version}`,
249739
250015
  "@supacloud/elysia": `^${elysiaMetadata.version}`,
250016
+ effect: "3.22.1",
249740
250017
  elysia: "2.0.0-beta.21",
249741
250018
  ...sdkDependencies ?? { rxjs: appMetadata.dependencies.rxjs }
249742
250019
  },
@@ -249781,6 +250058,11 @@ export default defineSupacloudConfig({
249781
250058
  outDir: "generated",
249782
250059
  strict: true,
249783
250060
  moduleBoundaryPreset: "modular-monolith",
250061
+ effect: {
250062
+ requireRouteEffects: true,
250063
+ requireErrorMappings: true,
250064
+ requireDependencies: true,
250065
+ },
249784
250066
  typeSafety: { scanProductionSource: true, noAnyInGenerated: true },
249785
250067
  disallowControllerDirectDb: true,
249786
250068
  detectOrphanModules: true,
@@ -249809,20 +250091,34 @@ export const CreateOrderBody = t.Object({ name: t.String({ minLength: 1 }) });
249809
250091
  export const OrderResult = t.Object({ id: t.String(), name: t.String() });
249810
250092
  export const HealthResult = t.Object({ ok: t.Boolean() });
249811
250093
  `,
249812
- "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";
249813
250096
  import { status } from "elysia";
249814
250097
  import { OrderParams, CreateOrderBody, OrderResult, HealthResult } from "./contracts";
249815
250098
  @Controller("/orders")
249816
250099
  export class OrdersController {
249817
- @Get("/health", { responses: { 200: HealthResult } })
249818
- health(): { ok: boolean } { return { ok: true }; }
249819
-
249820
- @Get("/:id", { params: OrderParams, responses: { 200: OrderResult } })
249821
- get(@Param("id") id: string): { id: string; name: string } { return { id, name: "demo" }; }
249822
-
249823
- @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
+ })
249824
250120
  create(@Body() body: { name: string }) {
249825
- return status(201, { id: "demo", name: body.name });
250121
+ return Effect.succeed(status(201, { id: "demo", name: body.name }));
249826
250122
  }
249827
250123
  }
249828
250124
 
@@ -249996,22 +250292,27 @@ function minimalTemplate(name, sdkDependencies) {
249996
250292
  "src/features/health/contracts.ts": `import { t } from "elysia";
249997
250293
  export const HealthResult = t.Object({ ok: t.Boolean() });
249998
250294
  `,
249999
- "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";
250000
250297
  import { HealthResult } from "./contracts";
250001
250298
  @Controller("/health")
250002
250299
  export class HealthController {
250003
- @Get("/", { responses: { 200: HealthResult } })
250004
- 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 }); }
250005
250305
  }
250006
250306
 
250007
250307
  @Module({ name: "health", tags: ["type:feature"], controllers: [HealthController] })
250008
250308
  export class HealthModule {}
250009
250309
  `,
250010
- "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";
250011
250312
  import { HealthController } from "./health";
250012
250313
 
250013
- test("health is a public, side-effect-free operation", () => {
250014
- 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 });
250015
250316
  });
250016
250317
  `,
250017
250318
  "src/app.module.ts": `import { Module } from "@supacloud/app/core";
@@ -250536,7 +250837,8 @@ export async function createDeliveryWorker(modules: CompiledModule[], lifecycle:
250536
250837
  `;
250537
250838
 
250538
250839
  // src/shared/tools/app-starter-upload.ts
250539
- 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";
250540
250842
  import { ApplicationError } from "@supacloud/elysia";
250541
250843
  import { UploadBody, UploadLocation, UploadParams, UploadResult } from "./contracts";
250542
250844
 
@@ -250566,13 +250868,47 @@ export class ReviewUploads {
250566
250868
  export class ReviewUploadsController {
250567
250869
  constructor(@Inject(ReviewUploads) private readonly uploads: ReviewUploads) {}
250568
250870
 
250569
- @Post("/:id/attachment-upload", { params: UploadParams, body: UploadBody, responses: { 200: UploadLocation } })
250570
- preview(@Param("id") id: string, @Body() body: { artifactId: string; expectedVersion: number }): Promise<ReviewUploadLocation> {
250571
- return this.uploads.preview({ reviewId: id, artifactId: body.artifactId, expectedVersion: body.expectedVersion });
250572
- }
250573
- @Post("/:id/attachment-registration", { params: UploadParams, body: UploadBody, responses: { 200: UploadResult } })
250574
- register(@Param("id") id: string, @Body() body: { artifactId: string; expectedVersion: number }): Promise<ReviewUploadResult> {
250575
- 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 }));
250576
250912
  }
250577
250913
  }
250578
250914
  `;
@@ -250856,6 +251192,10 @@ function rows(value: unknown): Record<string, unknown>[] {
250856
251192
  if (!Array.isArray(value)) throw new TypeError("Invalid database result");
250857
251193
  return value.map(record);
250858
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
+ }
250859
251199
  function input(value: unknown): Approval {
250860
251200
  const item = record(value);
250861
251201
  if (typeof item.id !== "string" || !item.id.length || typeof item.expectedVersion !== "number"
@@ -250970,7 +251310,7 @@ export async function createReviewPostgresAdapters(options: ReviewPostgresOption
250970
251310
  execute: tx => scope.run(tx, async () => {
250971
251311
  try { return await next(); }
250972
251312
  catch (error) {
250973
- if (error instanceof ApplicationError && error.status === 409) {
251313
+ if (isPublicConflict(error)) {
250974
251314
  throw new CommandError("COMMAND_IDEMPOTENCY_CONFLICT");
250975
251315
  }
250976
251316
  throw error;
@@ -251279,6 +251619,7 @@ function appStarterFiles(name) {
251279
251619
  "drizzle-orm": dbMetadata.peerDependencies["drizzle-orm"],
251280
251620
  "@supacloud/js": `^${sdkMetadata.version}`,
251281
251621
  "@supabase/supabase-js": sdkMetadata.peerDependencies["@supabase/supabase-js"],
251622
+ effect: "3.22.1",
251282
251623
  elysia: "2.0.0-beta.21",
251283
251624
  rxjs: appMetadata2.dependencies.rxjs
251284
251625
  },
@@ -251334,6 +251675,11 @@ export default defineSupacloudConfig({
251334
251675
  outDir: "generated",
251335
251676
  strict: true,
251336
251677
  moduleBoundaryPreset: "modular-monolith",
251678
+ effect: {
251679
+ requireRouteEffects: true,
251680
+ requireErrorMappings: true,
251681
+ requireDependencies: true,
251682
+ },
251337
251683
  typeSafety: { scanProductionSource: true, noAnyInGenerated: true },
251338
251684
  disallowControllerDirectDb: true,
251339
251685
  detectOrphanModules: true,
@@ -251534,7 +251880,8 @@ export function createCompiledModules(): never {
251534
251880
  throw new Error("Run bun run compile before starting the application");
251535
251881
  }
251536
251882
  `,
251537
- "src/review/review.ts": `import {
251883
+ "src/review/review.ts": `import * as Effect from "effect/Effect";
251884
+ import {
251538
251885
  Body, Command, Controller, DB_CLIENT, Get, Inject, Param, Post,
251539
251886
  defineFeatureSlice, defineFeatureSpec, type Aspect,
251540
251887
  } from "@supacloud/app";
@@ -251604,12 +251951,36 @@ export class ApproveReview {
251604
251951
  export class ReviewController {
251605
251952
  constructor(@Inject(ApproveReview) private readonly approveReview: ApproveReview) {}
251606
251953
 
251607
- @Get("/health", { responses: { 200: HealthResult } })
251608
- health(): { ok: boolean } { return { ok: true }; }
251609
-
251610
- @Post("/:id/approve", { command: ApproveReview, params: Params, body: ApproveBody, responses: { 200: ReviewResult } })
251611
- approve(@Param("id") id: string, @Body() body: { expectedVersion: number }): Promise<Review> {
251612
- 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));
251613
251984
  }
251614
251985
  }
251615
251986
 
@@ -252148,7 +252519,8 @@ export class ${className}Service {
252148
252519
  }
252149
252520
  }
252150
252521
  `,
252151
- [`${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";
252152
252524
  import { ${className}Params, ${className}Response, type ${className}Result } from "./${name}.model";
252153
252525
  import { ${className}Service } from "./${name}.service";
252154
252526
 
@@ -252156,9 +252528,13 @@ import { ${className}Service } from "./${name}.service";
252156
252528
  export class ${className}Controller {
252157
252529
  constructor(@Inject(${className}Service) readonly service: Pick<${className}Service, "find">) {}
252158
252530
 
252159
- @Get("/:id", { params: ${className}Params, responses: { 200: ${className}Response } })
252160
- find(@Param("id") id: string): Promise<${className}Result> {
252161
- 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));
252162
252538
  }
252163
252539
  }
252164
252540
  `,
@@ -252193,19 +252569,20 @@ test("${name} preserves permission denial without a privileged fallback", async
252193
252569
  await expect(service.find("example")).rejects.toBe(denied);
252194
252570
  });
252195
252571
  `,
252196
- [`${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";
252197
252574
  import { ${className}Controller } from "./${name}.controller";
252198
252575
 
252199
252576
  test("${name} controller delegates to the supplied asynchronous read port", async () => {
252200
252577
  // Unit-test double only; the generated runtime service remains fail-closed.
252201
252578
  const controller = new ${className}Controller({ find: async (id) => ({ id }) });
252202
- expect(await controller.find("example")).toEqual({ id: "example" });
252579
+ expect(await Effect.runPromise(controller.find("example"))).toEqual({ id: "example" });
252203
252580
  });
252204
252581
 
252205
252582
  test("${name} controller preserves an asynchronous read failure", async () => {
252206
252583
  const failure = new Error("read port unavailable");
252207
252584
  const controller = new ${className}Controller({ find: async () => { throw failure; } });
252208
- await expect(controller.find("example")).rejects.toBe(failure);
252585
+ await expect(Effect.runPromise(controller.find("example"))).rejects.toThrow("read port unavailable");
252209
252586
  });
252210
252587
  `
252211
252588
  };
@@ -253729,6 +254106,91 @@ function formatReconcileReport(report) {
253729
254106
  return lines.join(`
253730
254107
  `);
253731
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
+ }
253732
254194
  async function runModuleCheck(module, executor, schemas = ["public"]) {
253733
254195
  const catalog = await readCatalog(executor, schemas);
253734
254196
  return reconcileModule(module, catalog);
@@ -253772,6 +254234,10 @@ async function runExplain2(args, root) {
253772
254234
  const text = explainObject(manifest, target);
253773
254235
  return textResult2(text, text.startsWith("未找到对象"));
253774
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
+ }
253775
254241
  var defaultLiteSpawn = async (command, cwd) => {
253776
254242
  const proc = Bun.spawn({ cmd: command, cwd, stdout: "pipe", stderr: "pipe" });
253777
254243
  const [exitCode, stdout, stderr] = await Promise.all([
@@ -253829,12 +254295,12 @@ function registerDbGovernanceTools(server2, options = {}) {
253829
254295
  const environment = options.environment || process.env;
253830
254296
  const fallbackRoot = options.currentWorkingDirectory || process.cwd();
253831
254297
  const liteSpawn = options.liteSpawn || defaultLiteSpawn;
253832
- 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", {
253833
- action: withDescription(stringEnum(["lint", "explain", "module_check"]), "Database governance action"),
253834
- 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)"),
253835
254301
  root: optional(String2(), "[*] Project root (default: current directory)"),
253836
254302
  module_file: optional(String2(), "[*] File exporting defineDatabaseModule(...) (default: <root>/db/modules.ts)"),
253837
- target: optional(String2(), "[explain] Policy / function / table name to explain"),
254303
+ target: optional(String2(), "[context/explain] Policy / function / table name to explain"),
253838
254304
  database_url: optional(String2(), "[module_check] Postgres connection URL (default: DATABASE_URL)"),
253839
254305
  schema: optional(String2(), "[module_check] Comma-separated schemas to inspect (default: public)"),
253840
254306
  lite: optional(Boolean2(), "[module_check] Reconcile against a local SupaCloud Lite project (runs supacloud-lite db check)"),
@@ -253842,6 +254308,8 @@ function registerDbGovernanceTools(server2, options = {}) {
253842
254308
  }, async (request) => {
253843
254309
  const root = resolve25(request.root || fallbackRoot);
253844
254310
  switch (request.action) {
254311
+ case "context":
254312
+ return runContext2(request, root);
253845
254313
  case "lint":
253846
254314
  return runLint(request, root);
253847
254315
  case "explain":
@@ -256655,7 +257123,7 @@ function registerRemoteDevTools(server2, options = {}) {
256655
257123
  // package.json
256656
257124
  var package_default = {
256657
257125
  name: "@supacloud/cli",
256658
- version: "0.71.1",
257126
+ version: "0.73.0",
256659
257127
  description: "Project-scoped CLI for SupaCloud users",
256660
257128
  type: "module",
256661
257129
  main: "./dist/index.js",
@@ -256689,7 +257157,7 @@ var package_default = {
256689
257157
  directory: "packages/cli"
256690
257158
  },
256691
257159
  dependencies: {
256692
- "@supacloud/compiler": "^0.40.1",
257160
+ "@supacloud/compiler": "^0.41.0",
256693
257161
  "@supacloud/delivery": "0.8.0",
256694
257162
  "@supacloud/db": "^0.14.0",
256695
257163
  fflate: "^0.8.3",
@@ -256700,8 +257168,9 @@ var package_default = {
256700
257168
  "@graphql-tools/utils": "^12.0.3"
256701
257169
  },
256702
257170
  devDependencies: {
256703
- "@supacloud/elysia": "^0.23.1",
257171
+ "@supacloud/elysia": "^0.24.1",
256704
257172
  "@types/bun": "^1.4.2",
257173
+ effect: "3.22.1",
256705
257174
  elysia: "2.0.0-beta.21",
256706
257175
  typescript: "^7.0.2"
256707
257176
  }
@@ -256984,6 +257453,8 @@ EXAMPLES
256984
257453
  ${preferredCommand} context --format json
256985
257454
  ${preferredCommand} context --target CaseModule --format json
256986
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
256987
257458
  ${preferredCommand} db lint --root . --module_file db/modules.ts
256988
257459
  ${preferredCommand} db explain --target public.cases --module_file db/modules.ts
256989
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.71.1",
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",
@@ -45,8 +45,9 @@
45
45
  "@graphql-tools/utils": "^12.0.3"
46
46
  },
47
47
  "devDependencies": {
48
- "@supacloud/elysia": "^0.23.1",
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