@supacloud/cli 0.103.7 → 0.105.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
@@ -9405,9 +9405,9 @@ var $pack = require_pack();
9405
9405
  var FRONTEND_ARCHIVE_FORMAT = "tar.zst";
9406
9406
  var FRONTEND_ARCHIVE_RELEASE_SCHEMA = "supacloud.frontend-release.v2";
9407
9407
  var FRONTEND_ARCHIVE_CONTENT_TYPE = "application/vnd.supacloud.frontend.tar+zstd";
9408
- var FRONTEND_ARCHIVE_MAX_BYTES = 100 * 1024 * 1024;
9408
+ var FRONTEND_ARCHIVE_MAX_BYTES = 500 * 1024 * 1024;
9409
9409
  var FRONTEND_ARCHIVE_MAX_FILES = 1e4;
9410
- var FRONTEND_ARCHIVE_MAX_SOURCE_BYTES = 300 * 1024 * 1024;
9410
+ var FRONTEND_ARCHIVE_MAX_SOURCE_BYTES = 500 * 1024 * 1024;
9411
9411
  var FRONTEND_ARCHIVE_MAX_TAR_BYTES = FRONTEND_ARCHIVE_MAX_SOURCE_BYTES + FRONTEND_ARCHIVE_MAX_FILES * 2048 + 1024;
9412
9412
  function assertFrontendArchivePath(path) {
9413
9413
  if (!path || path.length > 1024 || Buffer.byteLength(path) > 4096 || path.startsWith("/") || path.includes("\\") || /[\u0000-\u001f\u007f]/u.test(path) || path.split("/").some((segment) => !segment || segment === "." || segment === "..") || /^[A-Za-z]:/u.test(path))
@@ -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.");
@@ -10285,6 +10293,8 @@ function parseDeliveryBuildManifest(value) {
10285
10293
 
10286
10294
  // node_modules/@supacloud/delivery/src/delivery-artifact.ts
10287
10295
  var JSON_LIMIT = 1048576;
10296
+ var DELIVERY_ARTIFACT_MAX_FILE_BYTES = 500 * 1024 * 1024;
10297
+ var DELIVERY_ARTIFACT_MAX_TOTAL_BYTES = 500 * 1024 * 1024;
10288
10298
  async function readDeliveryManifest(manifestPath) {
10289
10299
  const selected = resolve4(manifestPath);
10290
10300
  const root = await realpath2(dirname2(selected));
@@ -10298,7 +10308,7 @@ async function readDeliverySelection(manifestPath, target) {
10298
10308
  return { root, object, planned };
10299
10309
  }
10300
10310
  async function readVerifiedDeliveryFiles(root, object, target, selectedPaths) {
10301
- if (object.files.length > 1024 || object.files.some((file) => file.bytes > 64 * 1024 * 1024) || object.files.reduce((bytes, file) => bytes + file.bytes, 0) > 128 * 1024 * 1024) {
10311
+ if (object.files.length > 1024 || object.files.some((file) => file.bytes > DELIVERY_ARTIFACT_MAX_FILE_BYTES) || object.files.reduce((bytes, file) => bytes + file.bytes, 0) > DELIVERY_ARTIFACT_MAX_TOTAL_BYTES) {
10302
10312
  throw new Error("Artifact inventory exceeds limits.");
10303
10313
  }
10304
10314
  const objectRoot = join2(root, "objects", object.objectId);
@@ -11420,7 +11430,7 @@ function buildDeliveryMigrationPlan(archive, inventory, ref) {
11420
11430
  // node_modules/@supacloud/delivery/src/index.ts
11421
11431
  async function readDeliveryExecutableArchive(manifestPath) {
11422
11432
  const { root, manifest } = await readDeliveryManifest(manifestPath);
11423
- if (manifest.objects.length === 0 || manifest.objects.length > 32 || manifest.objects.some((object) => object.entryKind === "compiled-module-factory") || manifest.objects.reduce((total, object) => total + object.files.reduce((bytes, file) => bytes + file.bytes, 0), 0) > 128 * 1024 * 1024) {
11433
+ if (manifest.objects.length === 0 || manifest.objects.length > 32 || manifest.objects.some((object) => object.entryKind === "compiled-module-factory") || manifest.objects.reduce((total, object) => total + object.files.reduce((bytes, file) => bytes + file.bytes, 0), 0) > DELIVERY_ARTIFACT_MAX_TOTAL_BYTES) {
11424
11434
  throw new Error("Invalid executable delivery inventory.");
11425
11435
  }
11426
11436
  const objects = [];
@@ -21479,9 +21489,13 @@ function uniqueToolName(name, moduleName, used) {
21479
21489
  }
21480
21490
  function findRouteForCommand(manifest, commandClassName) {
21481
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;
21482
21495
  for (const controller of module.controllers) {
21483
21496
  for (const route of controller.routes) {
21484
- 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)
21485
21499
  continue;
21486
21500
  return {
21487
21501
  httpMethod: route.method,
@@ -21574,25 +21588,25 @@ var starter_metadata_default = {
21574
21588
  generatedBy: "supacloud/starter-metadata-v1",
21575
21589
  packages: {
21576
21590
  compiler: {
21577
- version: "0.47.3"
21591
+ version: "0.49.0"
21578
21592
  },
21579
21593
  app: {
21580
- version: "0.28.3",
21594
+ version: "0.30.0",
21581
21595
  dependencies: {
21582
21596
  rxjs: "7.8.2"
21583
21597
  }
21584
21598
  },
21585
21599
  elysia: {
21586
- version: "0.33.5"
21600
+ version: "0.35.0"
21587
21601
  },
21588
21602
  commands: {
21589
- version: "0.9.7"
21603
+ version: "0.11.0"
21590
21604
  },
21591
21605
  contracts: {
21592
- version: "0.11.5"
21606
+ version: "0.13.0"
21593
21607
  },
21594
21608
  db: {
21595
- version: "0.18.4",
21609
+ version: "0.20.0",
21596
21610
  peerDependencies: {
21597
21611
  "drizzle-orm": "1.0.0-rc.5-5935859"
21598
21612
  },
@@ -21601,7 +21615,7 @@ var starter_metadata_default = {
21601
21615
  }
21602
21616
  },
21603
21617
  sdk: {
21604
- version: "0.43.4",
21618
+ version: "0.45.0",
21605
21619
  peerDependencies: {
21606
21620
  "@supabase/supabase-js": "^2.117.3"
21607
21621
  }
@@ -26246,8 +26260,12 @@ function featureScaffold(name, className, governance) {
26246
26260
  export const ${className}Params = t.Object({
26247
26261
  id: t.String({ minLength: 1, maxLength: 128, pattern: "^[A-Za-z0-9_-]+$" }),
26248
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
+ });
26249
26267
  export const ${className}ListQuery = t.Object({
26250
- state: t.Optional(t.String({ minLength: 1, maxLength: 64 })),
26268
+ state: t.Optional(t.Union([t.Literal("draft"), t.Literal("approved")])),
26251
26269
  });
26252
26270
  export const ${className}ApproveBody = t.Object({
26253
26271
  expectedVersion: t.Integer({ minimum: 1 }),
@@ -26258,13 +26276,50 @@ export const ${className}Row = t.Object({
26258
26276
  state: t.String(),
26259
26277
  version: t.Integer({ minimum: 1 }),
26260
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;
26261
26306
  export const ${className}ListResponse = t.Array(${className}Row);
26307
+ export const ${className}RefreshInput = t.Object({
26308
+ id: t.String({ minLength: 1, maxLength: 128, pattern: "^[A-Za-z0-9_-]+$" }),
26309
+ version: t.Integer({ minimum: 1 }),
26310
+ });
26262
26311
  `,
26263
- [`${name}.model.ts`]: `import { InjectionToken } from "@supacloud/app/core";
26312
+ [`${name}.model.ts`]: `import {
26313
+ InjectionToken, type FeatureRequestContext,
26314
+ } from "@supacloud/app/core";
26264
26315
  import type {
26265
26316
  ${className}ApproveBody,
26266
26317
  ${className}ListQuery,
26318
+ ${className}Receipt,
26319
+ ${className}ReceiptParams,
26320
+ ${className}JobStatus,
26267
26321
  ${className}Row,
26322
+ ${className}RefreshInput,
26268
26323
  } from "./${name}.contracts";
26269
26324
  import type { StaticDecode } from "typebox";
26270
26325
 
@@ -26272,14 +26327,25 @@ export type ${className}ListInput = StaticDecode<typeof ${className}ListQuery>;
26272
26327
  export type ${className}ApproveInput = StaticDecode<typeof ${className}ApproveBody>;
26273
26328
  export type ${className}ApproveCommand = ${className}ApproveInput & { id: string };
26274
26329
  export type ${className}Result = StaticDecode<typeof ${className}Row>;
26330
+ export type ${className}ReceiptResult = StaticDecode<typeof ${className}Receipt>;
26331
+ export type ${className}RefreshJobInput = StaticDecode<typeof ${className}RefreshInput>;
26332
+ export type ${className}JobStatusResult = StaticDecode<typeof ${className}JobStatus>;
26275
26333
 
26276
26334
  export interface ${className}ReadPort {
26277
- list(input: ${className}ListInput): Promise<${className}Result[]>;
26278
- detail(id: string): Promise<${className}Result>;
26335
+ list(context: FeatureRequestContext, input: ${className}ListInput): Promise<${className}Result[]>;
26336
+ detail(context: FeatureRequestContext, id: string): Promise<${className}Result>;
26279
26337
  }
26280
26338
 
26281
26339
  export interface ${className}WritePort {
26282
- approve(input: ${className}ApproveCommand): Promise<${className}Result>;
26340
+ approve(context: FeatureRequestContext, input: ${className}ApproveCommand): Promise<${className}Result>;
26341
+ }
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>;
26283
26349
  }
26284
26350
 
26285
26351
  export const ${className}Reader = new InjectionToken<${className}ReadPort>(
@@ -26288,63 +26354,117 @@ export const ${className}Reader = new InjectionToken<${className}ReadPort>(
26288
26354
  export const ${className}Writer = new InjectionToken<${className}WritePort>(
26289
26355
  "${name}.writer", { scope: "request" },
26290
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
+ );
26291
26363
  `,
26292
- [`${name}.service.ts`]: `import { Inject, Injectable, Optional } from "@supacloud/app/core";
26364
+ [`${name}.service.ts`]: `import {
26365
+ Inject, Injectable, Optional, REQUEST_CONTEXT, requireFeatureRequestContext,
26366
+ type FeatureRequestContext,
26367
+ } from "@supacloud/app/core";
26293
26368
  import {
26294
26369
  ${className}Reader,
26370
+ ${className}JobStatuses,
26371
+ ${className}Receipts,
26295
26372
  ${className}Writer,
26296
26373
  type ${className}ApproveInput,
26297
26374
  type ${className}ReadPort,
26298
26375
  type ${className}Result,
26376
+ type ${className}ReceiptPort,
26377
+ type ${className}JobStatusPort,
26378
+ type ${className}JobStatusResult,
26299
26379
  type ${className}WritePort,
26300
26380
  } from "./${name}.model";
26301
26381
 
26302
26382
  @Injectable({ scope: "request" })
26303
26383
  export class ${className}Service {
26304
26384
  constructor(
26385
+ @Inject(REQUEST_CONTEXT) private readonly requestContext: unknown,
26305
26386
  @Inject(${className}Reader) @Optional() private readonly reader?: ${className}ReadPort,
26306
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,
26307
26390
  ) {}
26308
26391
 
26309
- async list(input: Parameters<${className}ReadPort["list"]>[0]): Promise<${className}Result[]> {
26392
+ private context(): FeatureRequestContext {
26393
+ return requireFeatureRequestContext(this.requestContext);
26394
+ }
26395
+
26396
+ async list(input: Parameters<${className}ReadPort["list"]>[1]): Promise<${className}Result[]> {
26310
26397
  if (!this.reader) throw new Error("Bind ${className}Reader with verified identity and tenant scope");
26311
- return this.reader.list(input);
26398
+ return this.reader.list(this.context(), input);
26312
26399
  }
26313
26400
 
26314
26401
  async detail(id: string): Promise<${className}Result> {
26315
26402
  if (!this.reader) throw new Error("Bind ${className}Reader with verified identity and tenant scope");
26316
- return this.reader.detail(id);
26403
+ return this.reader.detail(this.context(), id);
26317
26404
  }
26318
26405
 
26319
26406
  async approve(id: string, input: ${className}ApproveInput): Promise<${className}Result> {
26320
26407
  if (!this.writer) throw new Error("Bind ${className}Writer with durable command governance");
26321
- return this.writer.approve({ ...input, id });
26408
+ return this.writer.approve(this.context(), { ...input, id });
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 });
26322
26419
  }
26323
26420
  }
26324
26421
  `,
26325
26422
  [`${name}.feature.ts`]: `import { Effect } from "effect";
26326
26423
  import {
26327
- Body, Command, Controller, defineFeatureSlice, defineFeatureSpec, Get, Inject,
26328
- Injectable, Job, Param, Post, Query, type FeatureState,
26424
+ Body, Command, Controller, defineFeatureSlice, defineFeatureSpec, Get, Inject, JOB_CONTEXT,
26425
+ Injectable, Job, Param, Post, Query, requireFeatureRequestContext, type FeatureRequestContext,
26426
+ type FeatureState,
26329
26427
  } from "@supacloud/app/core";
26330
26428
  import {
26331
26429
  ${className}ApproveBody,
26332
26430
  ${className}ListQuery,
26431
+ ${className}Receipt,
26432
+ ${className}ReceiptParams,
26433
+ ${className}JobStatus,
26333
26434
  ${className}ListResponse,
26334
26435
  ${className}Params,
26436
+ ${className}RefreshInput,
26335
26437
  ${className}Row,
26336
26438
  } from "./${name}.contracts";
26337
26439
  import { ${className}Service } from "./${name}.service";
26338
- import type { ${className}ApproveInput, ${className}ListInput, ${className}Result } from "./${name}.model";
26440
+ import type {
26441
+ ${className}ApproveInput,
26442
+ ${className}ListInput,
26443
+ ${className}ReceiptResult,
26444
+ ${className}JobStatusResult,
26445
+ ${className}RefreshJobInput,
26446
+ ${className}Result,
26447
+ } from "./${name}.model";
26339
26448
 
26340
26449
  export const featureSpec = defineFeatureSpec({
26341
26450
  name: ${JSON.stringify(name)},
26342
26451
  states: ["draft", "approved"],
26452
+ tenantField: "tenant_id",
26343
26453
  transitions: {
26344
26454
  approve: {
26345
26455
  from: "draft", to: "approved", command: "Approve${className}Command",
26346
26456
  permission: ${JSON.stringify(permission)}, transaction: "required",
26347
26457
  idempotency: "required", audit: ${JSON.stringify(`${name}.approved`)},
26458
+ job: ${JSON.stringify(`${name}.refresh`)},
26459
+ invalidateTags: [
26460
+ ${JSON.stringify(`${name}.list`)},
26461
+ ${JSON.stringify(`${name}.detail`)},
26462
+ ${JSON.stringify(`${name}.receipt`)},
26463
+ ],
26464
+ persistence: {
26465
+ boundary: "database", optimisticLock: "required", versionField: "version", tenantField: "tenant_id", stateField: "state",
26466
+ receipt: "required", reconciliation: "none",
26467
+ },
26348
26468
  route: "POST /${name}/:id/approve",
26349
26469
  },
26350
26470
  },
@@ -26352,7 +26472,7 @@ export const featureSpec = defineFeatureSpec({
26352
26472
  export type ${className}State = FeatureState<typeof featureSpec>;
26353
26473
 
26354
26474
  @Injectable({ scope: "request" })
26355
- @Query({ name: "${name}.list" })
26475
+ @Query({ name: "${name}.list", tenantScope: "required" })
26356
26476
  export class List${className}Query {
26357
26477
  constructor(@Inject(${className}Service) private readonly service: ${className}Service) {}
26358
26478
  execute(input: ${className}ListInput): Promise<${className}Result[]> {
@@ -26367,6 +26487,7 @@ export class List${className}Query {
26367
26487
  transaction: "required",
26368
26488
  audit: ${JSON.stringify(`${name}.approved`)},
26369
26489
  idempotency: "required",
26490
+ tenantScope: "required",
26370
26491
  rpc: ${JSON.stringify(rpc)},
26371
26492
  })
26372
26493
  export class Approve${className}Command {
@@ -26378,11 +26499,17 @@ export class Approve${className}Command {
26378
26499
 
26379
26500
  @Job({
26380
26501
  name: ${JSON.stringify(`${name}.refresh`)},
26502
+ input: ${className}RefreshInput,
26381
26503
  mode: "task",
26382
26504
  idempotency: "required",
26505
+ tenantScope: "required",
26383
26506
  })
26384
26507
  export class Refresh${className}Job {
26385
- run(_input: { id: string; version: number }): Promise<void> {
26508
+ constructor(@Inject(JOB_CONTEXT) private readonly jobContext: unknown) {}
26509
+
26510
+ async run(_input: ${className}RefreshJobInput): Promise<void> {
26511
+ const context: FeatureRequestContext = requireFeatureRequestContext(this.jobContext);
26512
+ void context;
26386
26513
  throw new Error("Bind the existing Workflow/Queue adapter before dispatching ${name}.refresh");
26387
26514
  }
26388
26515
  }
@@ -26397,15 +26524,17 @@ export class ${className}Controller {
26397
26524
 
26398
26525
  @Get("/", {
26399
26526
  query: ${className}ListQuery,
26527
+ tags: [${JSON.stringify(`${name}.list`)}],
26400
26528
  responses: { 200: ${className}ListResponse },
26401
26529
  effect: { required: true, dependencies: [], errors: [], retry: "none" },
26402
26530
  })
26403
- list(@Query("state") state?: string): Effect.Effect<${className}Result[], never, never> {
26531
+ list(@Query("state") state?: ${className}State): Effect.Effect<${className}Result[], never, never> {
26404
26532
  return Effect.promise(() => this.listQuery.execute(state === undefined ? {} : { state }));
26405
26533
  }
26406
26534
 
26407
26535
  @Get("/:id", {
26408
26536
  params: ${className}Params,
26537
+ tags: [${JSON.stringify(`${name}.detail`)}],
26409
26538
  responses: { 200: ${className}Row },
26410
26539
  effect: { required: true, dependencies: [], errors: [], retry: "none" },
26411
26540
  })
@@ -26418,6 +26547,7 @@ export class ${className}Controller {
26418
26547
  body: ${className}ApproveBody,
26419
26548
  responses: { 200: ${className}Row },
26420
26549
  command: Approve${className}Command,
26550
+ idempotency: "required",
26421
26551
  effect: { required: true, dependencies: [], errors: [], retry: "none" },
26422
26552
  })
26423
26553
  approve(
@@ -26426,6 +26556,32 @@ export class ${className}Controller {
26426
26556
  ): Effect.Effect<${className}Result, never, never> {
26427
26557
  return Effect.promise(() => this.approveCommand.execute(id, body));
26428
26558
  }
26559
+
26560
+ @Get("/:id/operations/:operationId", {
26561
+ params: ${className}ReceiptParams,
26562
+ tags: [${JSON.stringify(`${name}.receipt`)}],
26563
+ responses: { 200: ${className}Receipt },
26564
+ effect: { required: true, dependencies: [], errors: [], retry: "none" },
26565
+ })
26566
+ receipt(
26567
+ @Param("id") id: string,
26568
+ @Param("operationId") operationId: string,
26569
+ ): Effect.Effect<${className}ReceiptResult, never, never> {
26570
+ return Effect.promise(() => this.service.receipt(id, operationId));
26571
+ }
26572
+
26573
+ @Get("/:id/operations/:operationId/status", {
26574
+ params: ${className}ReceiptParams,
26575
+ tags: [${JSON.stringify(`${name}.job-status`)}],
26576
+ responses: { 200: ${className}JobStatus },
26577
+ effect: { required: true, dependencies: [], errors: [], retry: "none" },
26578
+ })
26579
+ jobStatus(
26580
+ @Param("id") id: string,
26581
+ @Param("operationId") operationId: string,
26582
+ ): Effect.Effect<${className}JobStatusResult, never, never> {
26583
+ return Effect.promise(() => this.service.jobStatus(id, operationId));
26584
+ }
26429
26585
  }
26430
26586
 
26431
26587
  export const ${className}Feature = defineFeatureSlice({
@@ -26438,19 +26594,37 @@ export const ${className}Feature = defineFeatureSlice({
26438
26594
  });
26439
26595
  `,
26440
26596
  [`${name}.feature.test.ts`]: `import { expect, test } from "bun:test";
26597
+ import { Refresh${className}Job } from "./${name}.feature";
26441
26598
  import { ${className}Service } from "./${name}.service";
26599
+ import { ${className}Fixture, ${className}RequestContext, ${className}RequestEnvelope } from "./${name}.fixture";
26600
+
26601
+ test("${name} rejects missing trusted tenant context before adapter execution", async () => {
26602
+ let calls = 0;
26603
+ const service = new ${className}Service(undefined, {
26604
+ list: async () => { calls++; return []; },
26605
+ detail: async () => { calls++; return { id: "example", state: "draft", version: 1 }; },
26606
+ });
26607
+ await expect(service.list({})).rejects.toThrow("FEATURE_CONTEXT_REQUIRED");
26608
+ expect(calls).toBe(0);
26609
+ });
26610
+
26611
+ test("${name} rejects missing trusted job context before dispatch", async () => {
26612
+ const job = new Refresh${className}Job({});
26613
+ await expect(job.run({ id: "example", version: 1 })).rejects.toThrow("FEATURE_CONTEXT_REQUIRED");
26614
+ });
26442
26615
 
26443
26616
  test("${name} fails closed until verified read and durable write ports are bound", async () => {
26444
- const service = new ${className}Service();
26617
+ const service = new ${className}Service(${className}RequestEnvelope);
26445
26618
  await expect(service.list({})).rejects.toThrow("Bind ${className}Reader");
26446
26619
  await expect(service.detail("example")).rejects.toThrow("Bind ${className}Reader");
26447
26620
  await expect(service.approve("example", { expectedVersion: 1 })).rejects.toThrow("Bind ${className}Writer");
26621
+ await expect(service.jobStatus("example", "operation-example")).rejects.toThrow("Bind ${className}JobStatuses");
26448
26622
  });
26449
26623
 
26450
26624
  test("${name} preserves denied reads and rejected writes without fallback or retry", async () => {
26451
26625
  const denied = new Error("denied");
26452
26626
  let attempts = 0;
26453
- const service = new ${className}Service({
26627
+ const service = new ${className}Service(${className}RequestEnvelope, {
26454
26628
  list: async () => { throw denied; },
26455
26629
  detail: async () => { throw denied; },
26456
26630
  }, {
@@ -26463,14 +26637,166 @@ test("${name} preserves denied reads and rejected writes without fallback or ret
26463
26637
  });
26464
26638
 
26465
26639
  test("${name} forwards the authoritative version and result without inventing persistence", async () => {
26466
- const row = { id: "example", state: "approved", version: 2 };
26467
- const service = new ${className}Service(undefined, {
26468
- async approve(input) {
26469
- expect(input).toEqual({ id: "example", expectedVersion: 1, reason: "reviewed" });
26640
+ const row = { id: ${className}Fixture.id, state: "approved", version: 2 };
26641
+ const service = new ${className}Service(${className}RequestEnvelope, undefined, {
26642
+ async approve(requestContext, input) {
26643
+ expect(requestContext).toMatchObject(${className}RequestContext);
26644
+ expect(input).toEqual({ id: ${className}Fixture.id, ...${className}Fixture.approve });
26470
26645
  return row;
26471
26646
  },
26472
26647
  });
26473
- expect(await service.approve("example", { expectedVersion: 1, reason: "reviewed" })).toBe(row);
26648
+ expect(await service.approve(${className}Fixture.id, ${className}Fixture.approve)).toBe(row);
26649
+ });
26650
+ `,
26651
+ [`${name}.fixture.ts`]: `import type { ${className}ApproveInput, ${className}Result } from "./${name}.model";
26652
+
26653
+ /**
26654
+ * Test data only. Production adapters must load authoritative rows and
26655
+ * identity from the database/authentication boundary.
26656
+ */
26657
+ export const ${className}Fixture = {
26658
+ id: "example",
26659
+ approve: { expectedVersion: 1, reason: "reviewed" } satisfies ${className}ApproveInput,
26660
+ row: { id: "example", state: "draft", version: 1 } satisfies ${className}Result,
26661
+ } as const;
26662
+
26663
+ export const ${className}RequestContext = {
26664
+ requestId: "request-example",
26665
+ tenantId: "tenant-example",
26666
+ actorId: "reviewer-example",
26667
+ permissions: [${JSON.stringify(permission)}],
26668
+ } as const;
26669
+
26670
+ export const ${className}RequestEnvelope = {
26671
+ ...${className}RequestContext,
26672
+ identity: { authenticated: true, subject: "reviewer-example" },
26673
+ access: { tenantId: "tenant-example", permissions: [${JSON.stringify(permission)}] },
26674
+ } as const satisfies Record<string, unknown>;
26675
+ `,
26676
+ [`${name}.e2e.test.ts`]: `import { expect, test } from "bun:test";
26677
+ import { ${className}Fixture } from "./${name}.fixture";
26678
+
26679
+ const baseUrl = process.env["SUPACLOUD_FEATURE_E2E_URL"];
26680
+ const authorization = process.env["SUPACLOUD_FEATURE_E2E_AUTHORIZATION"];
26681
+ const otherAuthorization = process.env["SUPACLOUD_FEATURE_E2E_OTHER_AUTHORIZATION"];
26682
+ const fixtureId = process.env["SUPACLOUD_FEATURE_E2E_ID"] ?? ${className}Fixture.id;
26683
+ const idempotencyKey = process.env["SUPACLOUD_FEATURE_E2E_IDEMPOTENCY_KEY"];
26684
+ const enabled = Boolean(baseUrl && authorization && idempotencyKey);
26685
+ const tenantIsolationEnabled = Boolean(enabled && otherAuthorization);
26686
+
26687
+ function requireConfig(): { baseUrl: string; authorization: string; idempotencyKey: string } {
26688
+ if (!baseUrl || !authorization || !idempotencyKey) {
26689
+ throw new Error(
26690
+ "Set SUPACLOUD_FEATURE_E2E_URL, SUPACLOUD_FEATURE_E2E_AUTHORIZATION and " +
26691
+ "SUPACLOUD_FEATURE_E2E_IDEMPOTENCY_KEY before running this generated E2E test.",
26692
+ );
26693
+ }
26694
+ return { baseUrl, authorization, idempotencyKey };
26695
+ }
26696
+
26697
+ async function json(response: Response): Promise<unknown> {
26698
+ const text = await response.text();
26699
+ if (!text) return undefined;
26700
+ try {
26701
+ return JSON.parse(text) as unknown;
26702
+ } catch {
26703
+ return text;
26704
+ }
26705
+ }
26706
+
26707
+ function containsFixture(value: unknown): boolean {
26708
+ return JSON.stringify(value).includes(fixtureId);
26709
+ }
26710
+
26711
+ test.skipIf(!enabled)("${name} authenticated list/detail/approval E2E", async () => {
26712
+ const config = requireConfig();
26713
+ const headers = { authorization: config.authorization, "content-type": "application/json" };
26714
+ const list = await fetch(new URL("/${name}", config.baseUrl), { headers });
26715
+ expect(list.ok).toBe(true);
26716
+ const detail = await fetch(
26717
+ new URL("/${name}/" + encodeURIComponent(fixtureId), config.baseUrl),
26718
+ { headers },
26719
+ );
26720
+ expect(detail.ok).toBe(true);
26721
+ const approve = (key: string, body = ${className}Fixture.approve) => fetch(
26722
+ new URL("/${name}/" + encodeURIComponent(fixtureId) + "/approve", config.baseUrl),
26723
+ {
26724
+ method: "POST",
26725
+ headers: { ...headers, "idempotency-key": key },
26726
+ body: JSON.stringify(body),
26727
+ },
26728
+ );
26729
+ const first = await approve(config.idempotencyKey);
26730
+ const firstBody = await json(first);
26731
+ expect([200, 409, 422]).toContain(first.status);
26732
+
26733
+ // A lost response is recovered through the durable receipt, never by
26734
+ // submitting the approval again.
26735
+ const recovered = await fetch(
26736
+ new URL("/${name}/" + encodeURIComponent(fixtureId) + "/operations/" + encodeURIComponent(config.idempotencyKey), config.baseUrl),
26737
+ { headers },
26738
+ );
26739
+ expect(recovered.ok).toBe(true);
26740
+ const recoveredBody = await json(recovered);
26741
+ expect(recoveredBody).toMatchObject({ operationId: config.idempotencyKey });
26742
+ const jobStatus = await fetch(
26743
+ new URL("/${name}/" + encodeURIComponent(fixtureId) + "/operations/" + encodeURIComponent(config.idempotencyKey) + "/status", config.baseUrl),
26744
+ { headers },
26745
+ );
26746
+ expect(jobStatus.ok).toBe(true);
26747
+ expect(await json(jobStatus)).toMatchObject({ operationId: config.idempotencyKey });
26748
+
26749
+ // Replaying an identical command may return the original success or a
26750
+ // domain conflict, but it must never create a second successful transition.
26751
+ const replay = await approve(config.idempotencyKey);
26752
+ const replayBody = await json(replay);
26753
+ expect(replay.status).toBe(first.status);
26754
+ expect(replayBody).toEqual(firstBody);
26755
+
26756
+ // Reusing a key with a different payload must be rejected by the receipt
26757
+ // boundary instead of being treated as a new approval.
26758
+ const conflicting = await approve(
26759
+ config.idempotencyKey,
26760
+ { ...${className}Fixture.approve, reason: "conflicting replay" },
26761
+ );
26762
+ expect([400, 409, 422]).toContain(conflicting.status);
26763
+
26764
+ // Two distinct keys racing on one version may commit at most one state
26765
+ // transition. A fixture that is already terminal may reject both.
26766
+ const [raceA, raceB] = await Promise.all([
26767
+ approve(config.idempotencyKey + "-race-a"),
26768
+ approve(config.idempotencyKey + "-race-b"),
26769
+ ]);
26770
+ expect([raceA.status, raceB.status].filter((status) => status >= 200 && status < 300).length)
26771
+ .toBeLessThanOrEqual(1);
26772
+ });
26773
+
26774
+ test.skipIf(!tenantIsolationEnabled)("${name} rejects cross-tenant access", async () => {
26775
+ const config = requireConfig();
26776
+ if (!otherAuthorization) throw new Error("Missing SUPACLOUD_FEATURE_E2E_OTHER_AUTHORIZATION");
26777
+ const headers = {
26778
+ authorization: otherAuthorization,
26779
+ "content-type": "application/json",
26780
+ };
26781
+ const list = await fetch(new URL("/${name}", config.baseUrl), { headers });
26782
+ expect(list.ok).toBe(true);
26783
+ expect(containsFixture(await json(list))).toBe(false);
26784
+
26785
+ const detail = await fetch(
26786
+ new URL("/${name}/" + encodeURIComponent(fixtureId), config.baseUrl),
26787
+ { headers },
26788
+ );
26789
+ expect([403, 404]).toContain(detail.status);
26790
+
26791
+ const approval = await fetch(
26792
+ new URL("/${name}/" + encodeURIComponent(fixtureId) + "/approve", config.baseUrl),
26793
+ {
26794
+ method: "POST",
26795
+ headers: { ...headers, "idempotency-key": config.idempotencyKey + "-other-tenant" },
26796
+ body: JSON.stringify(${className}Fixture.approve),
26797
+ },
26798
+ );
26799
+ expect([403, 404]).toContain(approval.status);
26474
26800
  });
26475
26801
  `,
26476
26802
  [`${name}.deployment.md`]: `# ${className} feature delivery checklist
@@ -26479,12 +26805,19 @@ test("${name} forwards the authoritative version and result without inventing pe
26479
26805
  - [ ] Bind ${className}Writer to a transactional RPC or persistent Command adapter.
26480
26806
  - [ ] Register the \`${rpc}\` compiler and runtime RPC capability.
26481
26807
  - [ ] Verify RLS, permission denial, stale version, identical replay and conflicting replay.
26808
+ - [ ] Verify receipt lookup recovers a lost response without resubmitting the Command.
26809
+ - [ ] Pass \`${className}ApproveInvalidateTags\` to the generated mutation's \`invalidateTags\` option.
26810
+ - [ ] Bind \`${className}JobStatuses\` to the existing Workflow/Queue status read path.
26811
+ - [ ] Verify status lookup is tenant-scoped and distinguishes queued, running, completed, failed and cancelled.
26482
26812
  - [ ] Verify the refresh job uses the existing Workflow/Queue delivery path.
26483
26813
  - [ ] Run the generated client contract and authenticated E2E test.
26484
26814
  - [ ] Run \`supacloud app check --format json\`, package tests and the selected deployment receipt.
26485
26815
 
26486
26816
  The scaffold does not create migrations, infer permissions, or provide an in-memory
26487
26817
  production fallback. Database migrations remain the database source of truth.
26818
+ Every adapter receives a trusted framework context whose tenant and actor values
26819
+ come from verified identity/access data. Do not add tenant, actor, or permission
26820
+ fields to the request body as a substitute for this context.
26488
26821
  The HTTP schemas describe the boundary, not a second database model. Bind their
26489
26822
  fields to the existing database-source contract checker before delivery.
26490
26823
  The Writer input is derived from the approval body contract plus the route ID.
@@ -26492,15 +26825,24 @@ Add request fields in the contract, not a second handwritten Writer interface.
26492
26825
  Typecheck application adapters after changing a field type; the derived Port
26493
26826
  must expose incompatible assumptions in business code.
26494
26827
 
26828
+ For authenticated tenant-isolation acceptance, also provide
26829
+ \`SUPACLOUD_FEATURE_E2E_OTHER_AUTHORIZATION\` for a principal from another tenant.
26830
+ The generated E2E then verifies that the fixture is absent from list results and that
26831
+ cross-tenant detail and approval are rejected.
26832
+
26495
26833
  The named RPC adapter must invoke the continuation in the same database transaction
26496
26834
  used by the request-scoped Writer, or own the complete command instead; never execute
26497
26835
  both paths. Lock/check the version and state, reauthorize replays, and commit the
26498
26836
  business write, receipt, audit and outbox together. Do not put a network side effect
26499
26837
  inside this transaction. The refresh Job is an explicit unimplemented integration
26500
- point, not an automatically scheduled workflow.
26838
+ point, not an automatically scheduled workflow. The generated status route is also
26839
+ an explicit read adapter boundary; it does not treat a queue message ID or current
26840
+ business row as proof of task completion.
26501
26841
 
26502
26842
  Compile the routes with the existing client generator and use @supacloud/query for
26503
- identity-scoped caching/invalidation. There is deliberately no second hand-maintained
26843
+ identity-scoped caching/invalidation. Use the generated \`${className}ApproveInvalidateTags\`
26844
+ constant for the approval mutation; tags are explicit and never inferred from route names.
26845
+ There is deliberately no second hand-maintained
26504
26846
  HTTP client. A committed approval followed by a failed refresh is still committed;
26505
26847
  recover its receipt instead of resending. Use the host's standard error translation
26506
26848
  and requestId propagation; the scaffold does not claim to implement those adapters.
@@ -26683,7 +27025,7 @@ async function runLocalDevelopment(options) {
26683
27025
  }
26684
27026
 
26685
27027
  // src/shared/tools/app-verification-plan.ts
26686
- import { readdir as readdir4, realpath as realpath3 } from "node:fs/promises";
27028
+ import { readdir as readdir4, realpath as realpath3, stat } from "node:fs/promises";
26687
27029
  import { dirname as dirname7, isAbsolute as isAbsolute4, relative as relative5, resolve as resolve16 } from "node:path";
26688
27030
  function inside2(root, file) {
26689
27031
  const path = relative5(root, file).replaceAll("\\", "/");
@@ -26708,37 +27050,50 @@ async function createVerificationPlan(root, graph, target, sourceRoot = root) {
26708
27050
  ...(owner.jobs ?? []).flatMap((item) => (item.aspects ?? []).flatMap((aspect) => aspect.file ? [aspect.file] : [])),
26709
27051
  ...owner.controllers.flatMap((item) => item.routes.flatMap((route) => (route.aspects ?? []).flatMap((aspect) => aspect.file ? [aspect.file] : [])))
26710
27052
  ].filter((file) => typeof file === "string");
26711
- const directories = [...new Set(sources.map((file) => dirname7(resolve16(sourceRoot, file))))].sort();
26712
- const tests = new Set;
26713
27053
  const manual = [];
26714
- const coverage = [];
26715
- for (const directory of directories) {
26716
- if (!inside2(requestedRoot, directory)) {
26717
- manual.push("A source directory is outside the project; select its owning project explicitly");
27054
+ const validatedSources = [];
27055
+ for (const source of new Set(sources)) {
27056
+ const requested = resolve16(sourceRoot, source);
27057
+ if (!inside2(requestedRoot, requested)) {
27058
+ manual.push("A source file is outside the project; select its owning project explicitly");
26718
27059
  continue;
26719
27060
  }
26720
- const actual = await realpath3(directory).catch((error) => {
27061
+ const actual = await realpath3(requested).catch((error) => {
26721
27062
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
26722
27063
  return null;
26723
27064
  throw error;
26724
27065
  });
26725
27066
  if (actual === null) {
26726
- manual.push(`Source directory is missing: ${relative5(requestedRoot, directory).replaceAll("\\", "/")}`);
27067
+ manual.push(`Source file is missing: ${relative5(requestedRoot, requested).replaceAll("\\", "/")}`);
26727
27068
  continue;
26728
27069
  }
26729
27070
  if (!inside2(project, actual)) {
27071
+ manual.push("A source file is outside the project; select its owning project explicitly");
27072
+ continue;
27073
+ }
27074
+ if (!(await stat(actual)).isFile()) {
27075
+ manual.push(`Source is not a regular file: ${relative5(requestedRoot, requested).replaceAll("\\", "/")}`);
27076
+ continue;
27077
+ }
27078
+ validatedSources.push(actual);
27079
+ }
27080
+ const directories = [...new Set(validatedSources.map((file) => dirname7(file)))].sort();
27081
+ const tests = new Set;
27082
+ const coverage = [];
27083
+ for (const directory of directories) {
27084
+ if (!inside2(project, await realpath3(directory))) {
26730
27085
  manual.push("A source directory is outside the project; select its owning project explicitly");
26731
27086
  continue;
26732
27087
  }
26733
27088
  const local = [];
26734
- for (const entry of await readdir4(actual, { withFileTypes: true })) {
27089
+ for (const entry of await readdir4(directory, { withFileTypes: true })) {
26735
27090
  if (entry.isFile() && /\.(test|spec)\.[cm]?[jt]sx?$/.test(entry.name)) {
26736
- const file = relative5(project, resolve16(actual, entry.name)).replaceAll("\\", "/");
27091
+ const file = relative5(project, resolve16(directory, entry.name)).replaceAll("\\", "/");
26737
27092
  tests.add(file);
26738
27093
  local.push(file);
26739
27094
  }
26740
27095
  }
26741
- const path = relative5(project, actual).replaceAll("\\", "/") || ".";
27096
+ const path = relative5(project, directory).replaceAll("\\", "/") || ".";
26742
27097
  coverage.push({ directory: path, tests: local.sort(), basis: "colocated-not-proven" });
26743
27098
  if (!local.length)
26744
27099
  manual.push(`No colocated tests for source directory: ${path}`);
@@ -26753,7 +27108,7 @@ async function createVerificationPlan(root, graph, target, sourceRoot = root) {
26753
27108
  source: "declaration",
26754
27109
  target: owner.name,
26755
27110
  ready: manual.length === 0,
26756
- files: [...new Set(sources.map((file) => relative5(requestedRoot, resolve16(sourceRoot, file)).replaceAll("\\", "/")))].sort(),
27111
+ files: [...new Set(validatedSources.map((file) => relative5(project, file).replaceAll("\\", "/")))].sort(),
26757
27112
  tests: selected.slice(0, 32),
26758
27113
  commands: selected.length <= 32 ? [
26759
27114
  ...selected.map((file) => ["bun", "test", `./${file}`]),
@@ -27289,16 +27644,19 @@ async function projectCompileConfig(args) {
27289
27644
  }
27290
27645
  async function readAllowedDependencies(root) {
27291
27646
  const path = join13(root, "package.json");
27292
- if (!existsSync11(path))
27293
- return { dependencies: [], devDependencies: [] };
27647
+ if (!existsSync11(path)) {
27648
+ return { dependencies: [], devDependencies: [], optionalDependencies: [], peerDependencies: [] };
27649
+ }
27294
27650
  try {
27295
27651
  const parsed = JSON.parse(await readFile4(path, "utf8"));
27296
27652
  return {
27297
27653
  dependencies: Object.keys(parsed.dependencies ?? {}).sort(),
27298
- devDependencies: Object.keys(parsed.devDependencies ?? {}).sort()
27654
+ devDependencies: Object.keys(parsed.devDependencies ?? {}).sort(),
27655
+ optionalDependencies: Object.keys(parsed.optionalDependencies ?? {}).sort(),
27656
+ peerDependencies: Object.keys(parsed.peerDependencies ?? {}).sort()
27299
27657
  };
27300
27658
  } catch {
27301
- return { dependencies: [], devDependencies: [] };
27659
+ return { dependencies: [], devDependencies: [], optionalDependencies: [], peerDependencies: [] };
27302
27660
  }
27303
27661
  }
27304
27662
  function formatModuleContextPack(pack) {
@@ -27399,6 +27757,9 @@ async function runContext(args) {
27399
27757
  ` modules: ${project.modules.map((module) => module.name).join(", ") || "-"}`,
27400
27758
  ` external tokens: ${project.externalTokens.join(", ") || "-"}`,
27401
27759
  ` dependencies: ${project.allowedDependencies.dependencies.join(", ") || "-"}`,
27760
+ ` dev dependencies: ${project.allowedDependencies.devDependencies.join(", ") || "-"}`,
27761
+ ` optional dependencies: ${project.allowedDependencies.optionalDependencies.join(", ") || "-"}`,
27762
+ ` peer dependencies: ${project.allowedDependencies.peerDependencies.join(", ") || "-"}`,
27402
27763
  ...project.diagnostics.map(formatDiagnostic)
27403
27764
  ].join(`
27404
27765
  `));
@@ -32365,7 +32726,7 @@ async function runProjectDeployment(http, args) {
32365
32726
  // package.json
32366
32727
  var package_default = {
32367
32728
  name: "@supacloud/cli",
32368
- version: "0.103.7",
32729
+ version: "0.105.0",
32369
32730
  description: "Project-scoped CLI for SupaCloud users",
32370
32731
  type: "module",
32371
32732
  main: "./dist/index.js",
@@ -32417,19 +32778,19 @@ var package_default = {
32417
32778
  directory: "packages/cli"
32418
32779
  },
32419
32780
  dependencies: {
32420
- "@supacloud/compiler": "^0.47.3",
32421
- "@supacloud/delivery": "0.19.4",
32422
- "@supacloud/db": "0.18.4",
32781
+ "@supacloud/compiler": "^0.49.0",
32782
+ "@supacloud/delivery": "0.21.0",
32783
+ "@supacloud/db": "0.20.0",
32423
32784
  typebox: "1.3.37"
32424
32785
  },
32425
32786
  overrides: {
32426
- "@supacloud/contracts": "0.11.5",
32427
- "@supacloud/delivery": "0.19.4",
32787
+ "@supacloud/contracts": "0.13.0",
32788
+ "@supacloud/delivery": "0.21.0",
32428
32789
  "@graphql-tools/utils": "^12.0.4"
32429
32790
  },
32430
32791
  devDependencies: {
32431
32792
  "@supabase/supabase-js": "2.117.3",
32432
- "@supacloud/elysia": "0.33.5",
32793
+ "@supacloud/elysia": "0.35.0",
32433
32794
  "@types/bun": "^1.4.3",
32434
32795
  effect: "4.0.5",
32435
32796
  elysia: "2.0.0-beta.32",
@@ -1,7 +1,6 @@
1
1
  /**
2
- * An editable vertical feature slice. The generated ports are deliberately
3
- * fail-closed: database access, authorization and durable command adapters
4
- * remain application-owned and cannot be fabricated by the scaffold.
2
+ * An editable vertical feature slice. Persistence remains application-owned,
3
+ * while every generated port receives the trusted framework request context.
5
4
  */
6
5
  export declare function featureScaffold(name: string, className: string, governance: {
7
6
  permission: string;
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.103.7",
3
+ "version": "0.105.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.47.3",
56
- "@supacloud/delivery": "0.19.4",
57
- "@supacloud/db": "0.18.4",
55
+ "@supacloud/compiler": "^0.49.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.11.5",
62
- "@supacloud/delivery": "0.19.4",
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
66
  "@supabase/supabase-js": "2.117.3",
67
- "@supacloud/elysia": "0.33.5",
67
+ "@supacloud/elysia": "0.35.0",
68
68
  "@types/bun": "^1.4.3",
69
69
  "effect": "4.0.5",
70
70
  "elysia": "2.0.0-beta.32",