@supacloud/cli 0.104.0 → 0.106.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
@@ -9996,6 +9996,7 @@ var reference = String2({ minLength: 1, maxLength: 128, pattern: "^[A-Za-z][A-Za
9996
9996
  var references = _Array_(reference, { uniqueItems: true });
9997
9997
  var kind = Union([Literal("api"), Literal("jobs"), Literal("webhook"), Literal("function")]);
9998
9998
  var isolation = Union([Literal("shared"), Literal("process")]);
9999
+ var mode = Union([Literal("required"), Literal("none")]);
9999
10000
  var DeliveryFilePathSchema = String2({
10000
10001
  maxLength: 512,
10001
10002
  pattern: "^(?:[A-Za-z0-9_-][A-Za-z0-9_.-]*/)*[A-Za-z0-9_-][A-Za-z0-9_.-]*$"
@@ -10069,6 +10070,7 @@ var routeSchema = _Object_({
10069
10070
  ]),
10070
10071
  path: String2(),
10071
10072
  command: Optional(String2()),
10073
+ idempotency: Optional(mode),
10072
10074
  reason: Union([Literal("default-http"), Literal("explicit-module")])
10073
10075
  }, objectOptions);
10074
10076
  var jobSchema = _Object_({
@@ -10213,7 +10215,8 @@ var DeliveryBuildManifestSchema = _Object_({
10213
10215
  routes: _Array_(_Object_({
10214
10216
  method: String2(),
10215
10217
  path: String2(),
10216
- target: String2()
10218
+ target: String2(),
10219
+ idempotency: Optional(Union([Literal("required"), Literal("none")]))
10217
10220
  }, closed2)),
10218
10221
  jobs: _Array_(_Object_({ name: String2(), target: String2() }, closed2))
10219
10222
  }, closed2);
@@ -10275,7 +10278,12 @@ function parseDeliveryBuildManifest(value) {
10275
10278
  throw new Error("Invalid delivery object inventory.");
10276
10279
  }
10277
10280
  }
10278
- const routes = value.plan.targets.flatMap((target) => target.routes.map((route) => ({ method: route.method, path: route.path, target: target.name })));
10281
+ const routes = value.plan.targets.flatMap((target) => target.routes.map((route) => ({
10282
+ method: route.method,
10283
+ path: route.path,
10284
+ target: target.name,
10285
+ ...route.idempotency === undefined ? {} : { idempotency: route.idempotency }
10286
+ })));
10279
10287
  const jobs = value.plan.targets.flatMap((target) => target.jobs.map((job) => ({ name: job.name, target: target.name })));
10280
10288
  if (canonical(value.routes) !== canonical(routes) || canonical(value.jobs) !== canonical(jobs)) {
10281
10289
  throw new Error("Delivery mappings do not match planned ownership.");
@@ -21481,9 +21489,13 @@ function uniqueToolName(name, moduleName, used) {
21481
21489
  }
21482
21490
  function findRouteForCommand(manifest, commandClassName) {
21483
21491
  for (const module of manifest.modules) {
21492
+ const command = module.commands.find((candidate) => candidate.className === commandClassName || candidate.name === commandClassName);
21493
+ if (!command)
21494
+ continue;
21484
21495
  for (const controller of module.controllers) {
21485
21496
  for (const route of controller.routes) {
21486
- if (route.command !== commandClassName)
21497
+ const transition = module.featureSpec?.transitions.find((candidate) => candidate.route !== undefined && (candidate.command === command.className || candidate.command === command.name) && candidate.route === `${route.method} ${controller.path}${route.path}`);
21498
+ if (route.command !== command.className && route.command !== command.name && transition === undefined)
21487
21499
  continue;
21488
21500
  return {
21489
21501
  httpMethod: route.method,
@@ -21576,25 +21588,25 @@ var starter_metadata_default = {
21576
21588
  generatedBy: "supacloud/starter-metadata-v1",
21577
21589
  packages: {
21578
21590
  compiler: {
21579
- version: "0.48.0"
21591
+ version: "0.50.0"
21580
21592
  },
21581
21593
  app: {
21582
- version: "0.29.0",
21594
+ version: "0.31.0",
21583
21595
  dependencies: {
21584
21596
  rxjs: "7.8.2"
21585
21597
  }
21586
21598
  },
21587
21599
  elysia: {
21588
- version: "0.34.0"
21600
+ version: "0.35.1"
21589
21601
  },
21590
21602
  commands: {
21591
- version: "0.10.0"
21603
+ version: "0.11.0"
21592
21604
  },
21593
21605
  contracts: {
21594
- version: "0.12.0"
21606
+ version: "0.13.0"
21595
21607
  },
21596
21608
  db: {
21597
- version: "0.19.0",
21609
+ version: "0.20.0",
21598
21610
  peerDependencies: {
21599
21611
  "drizzle-orm": "1.0.0-rc.5-5935859"
21600
21612
  },
@@ -21603,7 +21615,7 @@ var starter_metadata_default = {
21603
21615
  }
21604
21616
  },
21605
21617
  sdk: {
21606
- version: "0.44.0",
21618
+ version: "0.45.1",
21607
21619
  peerDependencies: {
21608
21620
  "@supabase/supabase-js": "^2.117.3"
21609
21621
  }
@@ -26241,13 +26253,17 @@ test("${name} controller preserves an asynchronous read failure", async () => {
26241
26253
  // src/shared/tools/app-feature.ts
26242
26254
  function featureScaffold(name, className, governance) {
26243
26255
  const commandName = `${name}.approve`;
26244
- const { permission, rpc } = governance;
26256
+ const { permission, rpc, database } = governance;
26245
26257
  return {
26246
26258
  [`${name}.contracts.ts`]: `import { t } from "elysia";
26247
26259
 
26248
26260
  export const ${className}Params = t.Object({
26249
26261
  id: t.String({ minLength: 1, maxLength: 128, pattern: "^[A-Za-z0-9_-]+$" }),
26250
26262
  });
26263
+ export const ${className}ReceiptParams = t.Object({
26264
+ id: t.String({ minLength: 1, maxLength: 128, pattern: "^[A-Za-z0-9_-]+$" }),
26265
+ operationId: t.String({ minLength: 1, maxLength: 512, pattern: "^[A-Za-z0-9._:-]+$" }),
26266
+ });
26251
26267
  export const ${className}ListQuery = t.Object({
26252
26268
  state: t.Optional(t.Union([t.Literal("draft"), t.Literal("approved")])),
26253
26269
  });
@@ -26260,6 +26276,33 @@ export const ${className}Row = t.Object({
26260
26276
  state: t.String(),
26261
26277
  version: t.Integer({ minimum: 1 }),
26262
26278
  });
26279
+ export const ${className}Receipt = t.Object({
26280
+ operationId: t.String({ minLength: 1, maxLength: 512 }),
26281
+ status: t.Union([
26282
+ t.Literal("pending"), t.Literal("confirmed"), t.Literal("failed"), t.Literal("unknown"),
26283
+ ]),
26284
+ state: t.String(),
26285
+ version: t.Integer({ minimum: 1 }),
26286
+ });
26287
+ export const ${className}JobStatus = t.Object({
26288
+ operationId: t.String({ minLength: 1, maxLength: 512 }),
26289
+ status: t.Union([
26290
+ t.Literal("queued"), t.Literal("running"), t.Literal("completed"),
26291
+ t.Literal("failed"), t.Literal("cancelled"), t.Literal("unknown"),
26292
+ ]),
26293
+ errorCode: t.Optional(t.String({ minLength: 1, maxLength: 128 })),
26294
+ });
26295
+ /** Query tags are explicit cache contracts; use them with @supacloud/query. */
26296
+ export const ${className}QueryTags = {
26297
+ list: ${JSON.stringify(`${name}.list`)},
26298
+ detail: ${JSON.stringify(`${name}.detail`)},
26299
+ receipt: ${JSON.stringify(`${name}.receipt`)},
26300
+ } as const;
26301
+ export const ${className}ApproveInvalidateTags = [
26302
+ ${className}QueryTags.list,
26303
+ ${className}QueryTags.detail,
26304
+ ${className}QueryTags.receipt,
26305
+ ] as const;
26263
26306
  export const ${className}ListResponse = t.Array(${className}Row);
26264
26307
  export const ${className}RefreshInput = t.Object({
26265
26308
  id: t.String({ minLength: 1, maxLength: 128, pattern: "^[A-Za-z0-9_-]+$" }),
@@ -26272,6 +26315,9 @@ export const ${className}RefreshInput = t.Object({
26272
26315
  import type {
26273
26316
  ${className}ApproveBody,
26274
26317
  ${className}ListQuery,
26318
+ ${className}Receipt,
26319
+ ${className}ReceiptParams,
26320
+ ${className}JobStatus,
26275
26321
  ${className}Row,
26276
26322
  ${className}RefreshInput,
26277
26323
  } from "./${name}.contracts";
@@ -26281,7 +26327,9 @@ export type ${className}ListInput = StaticDecode<typeof ${className}ListQuery>;
26281
26327
  export type ${className}ApproveInput = StaticDecode<typeof ${className}ApproveBody>;
26282
26328
  export type ${className}ApproveCommand = ${className}ApproveInput & { id: string };
26283
26329
  export type ${className}Result = StaticDecode<typeof ${className}Row>;
26330
+ export type ${className}ReceiptResult = StaticDecode<typeof ${className}Receipt>;
26284
26331
  export type ${className}RefreshJobInput = StaticDecode<typeof ${className}RefreshInput>;
26332
+ export type ${className}JobStatusResult = StaticDecode<typeof ${className}JobStatus>;
26285
26333
 
26286
26334
  export interface ${className}ReadPort {
26287
26335
  list(context: FeatureRequestContext, input: ${className}ListInput): Promise<${className}Result[]>;
@@ -26292,12 +26340,26 @@ export interface ${className}WritePort {
26292
26340
  approve(context: FeatureRequestContext, input: ${className}ApproveCommand): Promise<${className}Result>;
26293
26341
  }
26294
26342
 
26343
+ export interface ${className}ReceiptPort {
26344
+ lookup(context: FeatureRequestContext, input: { id: string; operationId: string }): Promise<${className}ReceiptResult>;
26345
+ }
26346
+
26347
+ export interface ${className}JobStatusPort {
26348
+ lookup(context: FeatureRequestContext, input: { id: string; operationId: string }): Promise<${className}JobStatusResult>;
26349
+ }
26350
+
26295
26351
  export const ${className}Reader = new InjectionToken<${className}ReadPort>(
26296
26352
  "${name}.reader", { scope: "request" },
26297
26353
  );
26298
26354
  export const ${className}Writer = new InjectionToken<${className}WritePort>(
26299
26355
  "${name}.writer", { scope: "request" },
26300
26356
  );
26357
+ export const ${className}Receipts = new InjectionToken<${className}ReceiptPort>(
26358
+ "${name}.receipts", { scope: "request" },
26359
+ );
26360
+ export const ${className}JobStatuses = new InjectionToken<${className}JobStatusPort>(
26361
+ "${name}.job-statuses", { scope: "request" },
26362
+ );
26301
26363
  `,
26302
26364
  [`${name}.service.ts`]: `import {
26303
26365
  Inject, Injectable, Optional, REQUEST_CONTEXT, requireFeatureRequestContext,
@@ -26305,10 +26367,15 @@ export const ${className}Writer = new InjectionToken<${className}WritePort>(
26305
26367
  } from "@supacloud/app/core";
26306
26368
  import {
26307
26369
  ${className}Reader,
26370
+ ${className}JobStatuses,
26371
+ ${className}Receipts,
26308
26372
  ${className}Writer,
26309
26373
  type ${className}ApproveInput,
26310
26374
  type ${className}ReadPort,
26311
26375
  type ${className}Result,
26376
+ type ${className}ReceiptPort,
26377
+ type ${className}JobStatusPort,
26378
+ type ${className}JobStatusResult,
26312
26379
  type ${className}WritePort,
26313
26380
  } from "./${name}.model";
26314
26381
 
@@ -26318,6 +26385,8 @@ export class ${className}Service {
26318
26385
  @Inject(REQUEST_CONTEXT) private readonly requestContext: unknown,
26319
26386
  @Inject(${className}Reader) @Optional() private readonly reader?: ${className}ReadPort,
26320
26387
  @Inject(${className}Writer) @Optional() private readonly writer?: ${className}WritePort,
26388
+ @Inject(${className}Receipts) @Optional() private readonly receipts?: ${className}ReceiptPort,
26389
+ @Inject(${className}JobStatuses) @Optional() private readonly jobStatuses?: ${className}JobStatusPort,
26321
26390
  ) {}
26322
26391
 
26323
26392
  private context(): FeatureRequestContext {
@@ -26338,6 +26407,16 @@ export class ${className}Service {
26338
26407
  if (!this.writer) throw new Error("Bind ${className}Writer with durable command governance");
26339
26408
  return this.writer.approve(this.context(), { ...input, id });
26340
26409
  }
26410
+
26411
+ async receipt(id: string, operationId: string): Promise<Awaited<ReturnType<${className}ReceiptPort["lookup"]>>> {
26412
+ if (!this.receipts) throw new Error("Bind ${className}Receipts with durable receipt lookup");
26413
+ return this.receipts.lookup(this.context(), { id, operationId });
26414
+ }
26415
+
26416
+ async jobStatus(id: string, operationId: string): Promise<${className}JobStatusResult> {
26417
+ if (!this.jobStatuses) throw new Error("Bind ${className}JobStatuses with the existing Workflow/Queue status adapter");
26418
+ return this.jobStatuses.lookup(this.context(), { id, operationId });
26419
+ }
26341
26420
  }
26342
26421
  `,
26343
26422
  [`${name}.feature.ts`]: `import { Effect } from "effect";
@@ -26349,6 +26428,9 @@ import {
26349
26428
  import {
26350
26429
  ${className}ApproveBody,
26351
26430
  ${className}ListQuery,
26431
+ ${className}Receipt,
26432
+ ${className}ReceiptParams,
26433
+ ${className}JobStatus,
26352
26434
  ${className}ListResponse,
26353
26435
  ${className}Params,
26354
26436
  ${className}RefreshInput,
@@ -26358,6 +26440,8 @@ import { ${className}Service } from "./${name}.service";
26358
26440
  import type {
26359
26441
  ${className}ApproveInput,
26360
26442
  ${className}ListInput,
26443
+ ${className}ReceiptResult,
26444
+ ${className}JobStatusResult,
26361
26445
  ${className}RefreshJobInput,
26362
26446
  ${className}Result,
26363
26447
  } from "./${name}.model";
@@ -26365,6 +26449,7 @@ import type {
26365
26449
  export const featureSpec = defineFeatureSpec({
26366
26450
  name: ${JSON.stringify(name)},
26367
26451
  states: ["draft", "approved"],
26452
+ database: ${JSON.stringify(database)},
26368
26453
  tenantField: "tenant_id",
26369
26454
  transitions: {
26370
26455
  approve: {
@@ -26372,6 +26457,11 @@ export const featureSpec = defineFeatureSpec({
26372
26457
  permission: ${JSON.stringify(permission)}, transaction: "required",
26373
26458
  idempotency: "required", audit: ${JSON.stringify(`${name}.approved`)},
26374
26459
  job: ${JSON.stringify(`${name}.refresh`)},
26460
+ invalidateTags: [
26461
+ ${JSON.stringify(`${name}.list`)},
26462
+ ${JSON.stringify(`${name}.detail`)},
26463
+ ${JSON.stringify(`${name}.receipt`)},
26464
+ ],
26375
26465
  persistence: {
26376
26466
  boundary: "database", optimisticLock: "required", versionField: "version", tenantField: "tenant_id", stateField: "state",
26377
26467
  receipt: "required", reconciliation: "none",
@@ -26435,6 +26525,7 @@ export class ${className}Controller {
26435
26525
 
26436
26526
  @Get("/", {
26437
26527
  query: ${className}ListQuery,
26528
+ tags: [${JSON.stringify(`${name}.list`)}],
26438
26529
  responses: { 200: ${className}ListResponse },
26439
26530
  effect: { required: true, dependencies: [], errors: [], retry: "none" },
26440
26531
  })
@@ -26444,6 +26535,7 @@ export class ${className}Controller {
26444
26535
 
26445
26536
  @Get("/:id", {
26446
26537
  params: ${className}Params,
26538
+ tags: [${JSON.stringify(`${name}.detail`)}],
26447
26539
  responses: { 200: ${className}Row },
26448
26540
  effect: { required: true, dependencies: [], errors: [], retry: "none" },
26449
26541
  })
@@ -26456,6 +26548,7 @@ export class ${className}Controller {
26456
26548
  body: ${className}ApproveBody,
26457
26549
  responses: { 200: ${className}Row },
26458
26550
  command: Approve${className}Command,
26551
+ idempotency: "required",
26459
26552
  effect: { required: true, dependencies: [], errors: [], retry: "none" },
26460
26553
  })
26461
26554
  approve(
@@ -26464,6 +26557,32 @@ export class ${className}Controller {
26464
26557
  ): Effect.Effect<${className}Result, never, never> {
26465
26558
  return Effect.promise(() => this.approveCommand.execute(id, body));
26466
26559
  }
26560
+
26561
+ @Get("/:id/operations/:operationId", {
26562
+ params: ${className}ReceiptParams,
26563
+ tags: [${JSON.stringify(`${name}.receipt`)}],
26564
+ responses: { 200: ${className}Receipt },
26565
+ effect: { required: true, dependencies: [], errors: [], retry: "none" },
26566
+ })
26567
+ receipt(
26568
+ @Param("id") id: string,
26569
+ @Param("operationId") operationId: string,
26570
+ ): Effect.Effect<${className}ReceiptResult, never, never> {
26571
+ return Effect.promise(() => this.service.receipt(id, operationId));
26572
+ }
26573
+
26574
+ @Get("/:id/operations/:operationId/status", {
26575
+ params: ${className}ReceiptParams,
26576
+ tags: [${JSON.stringify(`${name}.job-status`)}],
26577
+ responses: { 200: ${className}JobStatus },
26578
+ effect: { required: true, dependencies: [], errors: [], retry: "none" },
26579
+ })
26580
+ jobStatus(
26581
+ @Param("id") id: string,
26582
+ @Param("operationId") operationId: string,
26583
+ ): Effect.Effect<${className}JobStatusResult, never, never> {
26584
+ return Effect.promise(() => this.service.jobStatus(id, operationId));
26585
+ }
26467
26586
  }
26468
26587
 
26469
26588
  export const ${className}Feature = defineFeatureSlice({
@@ -26500,6 +26619,7 @@ test("${name} fails closed until verified read and durable write ports are bound
26500
26619
  await expect(service.list({})).rejects.toThrow("Bind ${className}Reader");
26501
26620
  await expect(service.detail("example")).rejects.toThrow("Bind ${className}Reader");
26502
26621
  await expect(service.approve("example", { expectedVersion: 1 })).rejects.toThrow("Bind ${className}Writer");
26622
+ await expect(service.jobStatus("example", "operation-example")).rejects.toThrow("Bind ${className}JobStatuses");
26503
26623
  });
26504
26624
 
26505
26625
  test("${name} preserves denied reads and rejected writes without fallback or retry", async () => {
@@ -26611,6 +26731,22 @@ test.skipIf(!enabled)("${name} authenticated list/detail/approval E2E", async ()
26611
26731
  const firstBody = await json(first);
26612
26732
  expect([200, 409, 422]).toContain(first.status);
26613
26733
 
26734
+ // A lost response is recovered through the durable receipt, never by
26735
+ // submitting the approval again.
26736
+ const recovered = await fetch(
26737
+ new URL("/${name}/" + encodeURIComponent(fixtureId) + "/operations/" + encodeURIComponent(config.idempotencyKey), config.baseUrl),
26738
+ { headers },
26739
+ );
26740
+ expect(recovered.ok).toBe(true);
26741
+ const recoveredBody = await json(recovered);
26742
+ expect(recoveredBody).toMatchObject({ operationId: config.idempotencyKey });
26743
+ const jobStatus = await fetch(
26744
+ new URL("/${name}/" + encodeURIComponent(fixtureId) + "/operations/" + encodeURIComponent(config.idempotencyKey) + "/status", config.baseUrl),
26745
+ { headers },
26746
+ );
26747
+ expect(jobStatus.ok).toBe(true);
26748
+ expect(await json(jobStatus)).toMatchObject({ operationId: config.idempotencyKey });
26749
+
26614
26750
  // Replaying an identical command may return the original success or a
26615
26751
  // domain conflict, but it must never create a second successful transition.
26616
26752
  const replay = await approve(config.idempotencyKey);
@@ -26670,6 +26806,10 @@ test.skipIf(!tenantIsolationEnabled)("${name} rejects cross-tenant access", asyn
26670
26806
  - [ ] Bind ${className}Writer to a transactional RPC or persistent Command adapter.
26671
26807
  - [ ] Register the \`${rpc}\` compiler and runtime RPC capability.
26672
26808
  - [ ] Verify RLS, permission denial, stale version, identical replay and conflicting replay.
26809
+ - [ ] Verify receipt lookup recovers a lost response without resubmitting the Command.
26810
+ - [ ] Pass \`${className}ApproveInvalidateTags\` to the generated mutation's \`invalidateTags\` option.
26811
+ - [ ] Bind \`${className}JobStatuses\` to the existing Workflow/Queue status read path.
26812
+ - [ ] Verify status lookup is tenant-scoped and distinguishes queued, running, completed, failed and cancelled.
26673
26813
  - [ ] Verify the refresh job uses the existing Workflow/Queue delivery path.
26674
26814
  - [ ] Run the generated client contract and authenticated E2E test.
26675
26815
  - [ ] Run \`supacloud app check --format json\`, package tests and the selected deployment receipt.
@@ -26696,10 +26836,14 @@ used by the request-scoped Writer, or own the complete command instead; never ex
26696
26836
  both paths. Lock/check the version and state, reauthorize replays, and commit the
26697
26837
  business write, receipt, audit and outbox together. Do not put a network side effect
26698
26838
  inside this transaction. The refresh Job is an explicit unimplemented integration
26699
- point, not an automatically scheduled workflow.
26839
+ point, not an automatically scheduled workflow. The generated status route is also
26840
+ an explicit read adapter boundary; it does not treat a queue message ID or current
26841
+ business row as proof of task completion.
26700
26842
 
26701
26843
  Compile the routes with the existing client generator and use @supacloud/query for
26702
- identity-scoped caching/invalidation. There is deliberately no second hand-maintained
26844
+ identity-scoped caching/invalidation. Use the generated \`${className}ApproveInvalidateTags\`
26845
+ constant for the approval mutation; tags are explicit and never inferred from route names.
26846
+ There is deliberately no second hand-maintained
26703
26847
  HTTP client. A committed approval followed by a failed refresh is still committed;
26704
26848
  recover its receipt instead of resending. Use the host's standard error translation
26705
26849
  and requestId propagation; the scaffold does not claim to implement those adapters.
@@ -27272,6 +27416,12 @@ function requireFeatureCapability(value, flag) {
27272
27416
  }
27273
27417
  return value;
27274
27418
  }
27419
+ function requireFeatureDatabase(value, flag, pattern) {
27420
+ if (typeof value !== "string" || !pattern.test(value)) {
27421
+ throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", `feature requires an explicit valid --${flag}`);
27422
+ }
27423
+ return value;
27424
+ }
27275
27425
  function moduleScaffold(name) {
27276
27426
  return `import { Module } from "@supacloud/app";
27277
27427
 
@@ -27370,7 +27520,12 @@ async function generateScaffold(args) {
27370
27520
  }
27371
27521
  const featureGovernance = kind === "feature" ? {
27372
27522
  permission: requireFeatureCapability(args.permission, "permission"),
27373
- rpc: requireFeatureCapability(args.rpc, "rpc")
27523
+ rpc: requireFeatureCapability(args.rpc, "rpc"),
27524
+ database: {
27525
+ source: requireFeatureDatabase(args.database_source, "database-source", /^(?!\/)(?!.*\\)(?!.*(?:^|\/)\.\.(?:\/|$))[A-Za-z0-9._/-]+$/),
27526
+ table: requireFeatureDatabase(args.database_table, "database-table", /^[A-Za-z_][A-Za-z0-9_]*\.[A-Za-z_][A-Za-z0-9_]*$/),
27527
+ rowType: requireFeatureDatabase(args.database_type, "database-type", /^[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*$/)
27528
+ }
27374
27529
  } : undefined;
27375
27530
  const writes = [];
27376
27531
  let modulePath;
@@ -28648,7 +28803,7 @@ Execution: skipped`);
28648
28803
  return format === "json" || json ? result : formatPromotionExecution(result);
28649
28804
  }
28650
28805
  async function runAppTool(request, options = {}) {
28651
- if (request.dry_run !== undefined && request["dry-run"] !== undefined || request.register_in !== undefined && request["register-in"] !== undefined || request.database_url !== undefined && request["database-url"] !== undefined || request.request_id !== undefined && request["request-id"] !== undefined) {
28806
+ if (request.dry_run !== undefined && request["dry-run"] !== undefined || request.register_in !== undefined && request["register-in"] !== undefined || request.database_url !== undefined && request["database-url"] !== undefined || request.database_source !== undefined && request["database-source"] !== undefined || request.database_table !== undefined && request["database-table"] !== undefined || request.database_type !== undefined && request["database-type"] !== undefined || request.request_id !== undefined && request["request-id"] !== undefined) {
28652
28807
  throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "Do not combine hyphenated and underscored aliases of the same flag");
28653
28808
  }
28654
28809
  request = {
@@ -28656,6 +28811,9 @@ async function runAppTool(request, options = {}) {
28656
28811
  ...request["dry-run"] === undefined ? {} : { dry_run: request["dry-run"] },
28657
28812
  ...request["register-in"] === undefined ? {} : { register_in: request["register-in"] },
28658
28813
  ...request["database-url"] === undefined ? {} : { database_url: request["database-url"] },
28814
+ ...request["database-source"] === undefined ? {} : { database_source: request["database-source"] },
28815
+ ...request["database-table"] === undefined ? {} : { database_table: request["database-table"] },
28816
+ ...request["database-type"] === undefined ? {} : { database_type: request["database-type"] },
28659
28817
  ...request["request-id"] === undefined ? {} : { request_id: request["request-id"] }
28660
28818
  };
28661
28819
  if (request.events !== undefined || request.request_id !== undefined) {
@@ -28831,6 +28989,12 @@ function registerAppTools(server, options = {}) {
28831
28989
  module: optional(String2(), "[generate] Target feature module (required for command/query/controller)"),
28832
28990
  permission: optional(String2(), "[generate feature] Explicit approval permission; not inferred from name"),
28833
28991
  rpc: optional(String2(), "[generate feature] Explicit durable RPC capability; must be registered by the host"),
28992
+ database_source: optional(String2(), "[generate feature] Project-relative database source path"),
28993
+ database_table: optional(String2(), "[generate feature] Schema-qualified table owned by the feature"),
28994
+ database_type: optional(String2(), "[generate feature] Exported row type derived from the database source"),
28995
+ "database-source": optional(String2(), "[generate feature] CLI alias of database_source"),
28996
+ "database-table": optional(String2(), "[generate feature] CLI alias of database_table"),
28997
+ "database-type": optional(String2(), "[generate feature] CLI alias of database_type"),
28834
28998
  dir: optional(String2(), "[generate] Feature root directory (default: src/features)"),
28835
28999
  force: optional(Boolean2(), "[generate] Overwrite a single regular file; resource/feature/controller never overwrite"),
28836
29000
  dry_run: optional(Boolean2(), "[generate] Validate and preview the complete change set without writing"),
@@ -28865,6 +29029,12 @@ function registerAppAliases(server) {
28865
29029
  module: optional(String2(), "[generate] Target feature module"),
28866
29030
  permission: optional(String2(), "[generate feature] Explicit approval permission; not inferred from name"),
28867
29031
  rpc: optional(String2(), "[generate feature] Explicit durable RPC capability; must be registered by the host"),
29032
+ database_source: optional(String2(), "[generate feature] Project-relative database source path"),
29033
+ database_table: optional(String2(), "[generate feature] Schema-qualified table owned by the feature"),
29034
+ database_type: optional(String2(), "[generate feature] Exported row type derived from the database source"),
29035
+ "database-source": optional(String2(), "[generate feature] CLI alias of database_source"),
29036
+ "database-table": optional(String2(), "[generate feature] CLI alias of database_table"),
29037
+ "database-type": optional(String2(), "[generate feature] CLI alias of database_type"),
28868
29038
  dir: optional(String2(), "[generate] Feature root directory (default: src/features)"),
28869
29039
  force: optional(Boolean2(), "[generate] Overwrite a single regular file; resource/feature/controller never overwrite"),
28870
29040
  dry_run: optional(Boolean2(), "[generate] Validate and preview the complete change set without writing"),
@@ -32583,7 +32753,7 @@ async function runProjectDeployment(http, args) {
32583
32753
  // package.json
32584
32754
  var package_default = {
32585
32755
  name: "@supacloud/cli",
32586
- version: "0.104.0",
32756
+ version: "0.106.0",
32587
32757
  description: "Project-scoped CLI for SupaCloud users",
32588
32758
  type: "module",
32589
32759
  main: "./dist/index.js",
@@ -32635,19 +32805,19 @@ var package_default = {
32635
32805
  directory: "packages/cli"
32636
32806
  },
32637
32807
  dependencies: {
32638
- "@supacloud/compiler": "^0.48.0",
32639
- "@supacloud/delivery": "0.20.0",
32640
- "@supacloud/db": "0.19.0",
32808
+ "@supacloud/compiler": "^0.50.0",
32809
+ "@supacloud/delivery": "0.21.0",
32810
+ "@supacloud/db": "0.20.0",
32641
32811
  typebox: "1.3.37"
32642
32812
  },
32643
32813
  overrides: {
32644
- "@supacloud/contracts": "0.12.0",
32645
- "@supacloud/delivery": "0.20.0",
32814
+ "@supacloud/contracts": "0.13.0",
32815
+ "@supacloud/delivery": "0.21.0",
32646
32816
  "@graphql-tools/utils": "^12.0.4"
32647
32817
  },
32648
32818
  devDependencies: {
32649
- "@supabase/supabase-js": "2.117.3",
32650
- "@supacloud/elysia": "0.34.0",
32819
+ "@supabase/supabase-js": "2.118.0",
32820
+ "@supacloud/elysia": "0.35.1",
32651
32821
  "@types/bun": "^1.4.3",
32652
32822
  effect: "4.0.5",
32653
32823
  elysia: "2.0.0-beta.32",
@@ -5,4 +5,9 @@
5
5
  export declare function featureScaffold(name: string, className: string, governance: {
6
6
  permission: string;
7
7
  rpc: string;
8
+ database: {
9
+ source: string;
10
+ table: string;
11
+ rowType: string;
12
+ };
8
13
  }): Record<string, string>;
@@ -0,0 +1 @@
1
+ export {};
@@ -72,6 +72,12 @@ export interface AppToolArguments {
72
72
  module?: string;
73
73
  permission?: string;
74
74
  rpc?: string;
75
+ database_source?: string;
76
+ database_table?: string;
77
+ database_type?: string;
78
+ "database-source"?: string;
79
+ "database-table"?: string;
80
+ "database-type"?: string;
75
81
  dir?: string;
76
82
  force?: boolean;
77
83
  dry_run?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.104.0",
3
+ "version": "0.106.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -52,19 +52,19 @@
52
52
  "directory": "packages/cli"
53
53
  },
54
54
  "dependencies": {
55
- "@supacloud/compiler": "^0.48.0",
56
- "@supacloud/delivery": "0.20.0",
57
- "@supacloud/db": "0.19.0",
55
+ "@supacloud/compiler": "^0.50.0",
56
+ "@supacloud/delivery": "0.21.0",
57
+ "@supacloud/db": "0.20.0",
58
58
  "typebox": "1.3.37"
59
59
  },
60
60
  "overrides": {
61
- "@supacloud/contracts": "0.12.0",
62
- "@supacloud/delivery": "0.20.0",
61
+ "@supacloud/contracts": "0.13.0",
62
+ "@supacloud/delivery": "0.21.0",
63
63
  "@graphql-tools/utils": "^12.0.4"
64
64
  },
65
65
  "devDependencies": {
66
- "@supabase/supabase-js": "2.117.3",
67
- "@supacloud/elysia": "0.34.0",
66
+ "@supabase/supabase-js": "2.118.0",
67
+ "@supacloud/elysia": "0.35.1",
68
68
  "@types/bun": "^1.4.3",
69
69
  "effect": "4.0.5",
70
70
  "elysia": "2.0.0-beta.32",