@supacloud/cli 0.106.3 → 0.107.1

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
@@ -21588,16 +21588,16 @@ var starter_metadata_default = {
21588
21588
  generatedBy: "supacloud/starter-metadata-v1",
21589
21589
  packages: {
21590
21590
  compiler: {
21591
- version: "0.50.1"
21591
+ version: "0.51.0"
21592
21592
  },
21593
21593
  app: {
21594
- version: "0.31.1",
21594
+ version: "0.32.0",
21595
21595
  dependencies: {
21596
21596
  rxjs: "7.8.2"
21597
21597
  }
21598
21598
  },
21599
21599
  elysia: {
21600
- version: "0.35.2"
21600
+ version: "0.36.0"
21601
21601
  },
21602
21602
  commands: {
21603
21603
  version: "0.11.0"
@@ -25364,7 +25364,7 @@ export function createCompiledModules(): never {
25364
25364
  `,
25365
25365
  "src/review/review.ts": `import { Effect } from "effect";
25366
25366
  import {
25367
- Body, Command, Controller, DB_CLIENT, Get, Inject, Param, Post,
25367
+ Body, Command, Controller, DB_CLIENT, Get, Inject, Injectable, Param, Post,
25368
25368
  defineFeatureSlice, defineFeatureSpec, type Aspect,
25369
25369
  } from "@supacloud/app";
25370
25370
  import { ApplicationError, assertFeatureTransition, createSchemaDecoder } from "@supacloud/elysia";
@@ -25408,6 +25408,7 @@ function readReview(value: unknown): Review {
25408
25408
  }
25409
25409
  }
25410
25410
 
25411
+ @Injectable({ scope: "request" })
25411
25412
  @Command({
25412
25413
  name: "review.approve", permission: "review.approve", transaction: "required",
25413
25414
  idempotency: "required", audit: "review.approved", aspects: [requireRequest],
@@ -26253,7 +26254,7 @@ test("${name} controller preserves an asynchronous read failure", async () => {
26253
26254
  // src/shared/tools/app-feature.ts
26254
26255
  function featureScaffold(name, className, governance) {
26255
26256
  const commandName = `${name}.approve`;
26256
- const { permission, rpc, database } = governance;
26257
+ const { permission, readPermission, rpc, database } = governance;
26257
26258
  const databaseImportPath = governance.databaseImportPath ?? `../../../${database.source.replace(/\.(?:[cm]?tsx?|mts|cts)$/, "")}`;
26258
26259
  const rowTypeRoot = database.rowType.split(".")[0];
26259
26260
  return {
@@ -26375,6 +26376,7 @@ export const ${className}Refresher = new InjectionToken<${className}RefreshPort>
26375
26376
  `,
26376
26377
  [`${name}.service.ts`]: `import {
26377
26378
  FeatureAdapterError, Inject, Injectable, Optional, REQUEST_CONTEXT, requireFeatureOperationResult,
26379
+ requireFeaturePermission,
26378
26380
  requireFeatureRequestContext, requireFeatureTransitionResult,
26379
26381
  type FeatureRequestContext,
26380
26382
  } from "@supacloud/app/core";
@@ -26407,13 +26409,17 @@ export class ${className}Service {
26407
26409
  }
26408
26410
 
26409
26411
  async list(input: Parameters<${className}ReadPort["list"]>[1]): Promise<${className}Result[]> {
26412
+ const context = this.context();
26413
+ requireFeaturePermission(context, ${JSON.stringify(readPermission)});
26410
26414
  if (!this.reader) throw new FeatureAdapterError("${name}.reader");
26411
- return this.reader.list(this.context(), input);
26415
+ return this.reader.list(context, input);
26412
26416
  }
26413
26417
 
26414
26418
  async detail(id: string): Promise<${className}Result> {
26419
+ const context = this.context();
26420
+ requireFeaturePermission(context, ${JSON.stringify(readPermission)});
26415
26421
  if (!this.reader) throw new FeatureAdapterError("${name}.reader");
26416
- return this.reader.detail(this.context(), id);
26422
+ return this.reader.detail(context, id);
26417
26423
  }
26418
26424
 
26419
26425
  async approve(id: string, input: ${className}ApproveInput): Promise<${className}Result> {
@@ -26427,17 +26433,21 @@ export class ${className}Service {
26427
26433
  }
26428
26434
 
26429
26435
  async receipt(id: string, operationId: string): Promise<Awaited<ReturnType<${className}ReceiptPort["lookup"]>>> {
26436
+ const context = this.context();
26437
+ requireFeaturePermission(context, ${JSON.stringify(readPermission)});
26430
26438
  if (!this.receipts) throw new FeatureAdapterError("${name}.receipts");
26431
26439
  const input = { id, operationId };
26432
26440
  // Keep the comparison identity separate from mutable adapter arguments.
26433
- return requireFeatureOperationResult(input, await this.receipts.lookup(this.context(), { ...input }));
26441
+ return requireFeatureOperationResult(input, await this.receipts.lookup(context, { ...input }));
26434
26442
  }
26435
26443
 
26436
26444
  async jobStatus(id: string, operationId: string): Promise<${className}JobStatusResult> {
26445
+ const context = this.context();
26446
+ requireFeaturePermission(context, ${JSON.stringify(readPermission)});
26437
26447
  if (!this.jobStatuses) throw new FeatureAdapterError("${name}.job-statuses");
26438
26448
  const input = { id, operationId };
26439
26449
  // Keep the comparison identity separate from mutable adapter arguments.
26440
- return requireFeatureOperationResult(input, await this.jobStatuses.lookup(this.context(), { ...input }));
26450
+ return requireFeatureOperationResult(input, await this.jobStatuses.lookup(context, { ...input }));
26441
26451
  }
26442
26452
  }
26443
26453
  `,
@@ -26475,6 +26485,21 @@ export const featureSpec = defineFeatureSpec({
26475
26485
  states: ["draft", "approved"],
26476
26486
  database: ${JSON.stringify(database)},
26477
26487
  tenantField: "tenant_id",
26488
+ readPermission: ${JSON.stringify(readPermission)},
26489
+ requiredAdapters: [
26490
+ "${className}Reader",
26491
+ "${className}Writer",
26492
+ "${className}Receipts",
26493
+ "${className}JobStatuses",
26494
+ "${className}Refresher",
26495
+ ],
26496
+ requiredAdapterScopes: {
26497
+ ${className}Reader: "request",
26498
+ ${className}Writer: "request",
26499
+ ${className}Receipts: "request",
26500
+ ${className}JobStatuses: "request",
26501
+ ${className}Refresher: "job",
26502
+ },
26478
26503
  transitions: {
26479
26504
  approve: {
26480
26505
  from: "draft", to: "approved", command: "Approve${className}Command",
@@ -26531,7 +26556,7 @@ type ${className}JobStatusError =
26531
26556
  | { readonly _tag: "FEATURE_ADAPTER_UNAVAILABLE" };
26532
26557
 
26533
26558
  @Injectable({ scope: "request" })
26534
- @Query({ name: "${name}.list", tenantScope: "required" })
26559
+ @Query({ name: "${name}.list", permission: ${JSON.stringify(readPermission)}, tenantScope: "required" })
26535
26560
  export class List${className}Query {
26536
26561
  constructor(@Inject(${className}Service) private readonly service: ${className}Service) {}
26537
26562
  execute(input: ${className}ListInput): Promise<${className}Result[]> {
@@ -26539,6 +26564,33 @@ export class List${className}Query {
26539
26564
  }
26540
26565
  }
26541
26566
 
26567
+ @Injectable({ scope: "request" })
26568
+ @Query({ name: "${name}.detail", permission: ${JSON.stringify(readPermission)}, tenantScope: "required" })
26569
+ export class Detail${className}Query {
26570
+ constructor(@Inject(${className}Service) private readonly service: ${className}Service) {}
26571
+ execute(id: string): Promise<${className}Result> {
26572
+ return this.service.detail(id);
26573
+ }
26574
+ }
26575
+
26576
+ @Injectable({ scope: "request" })
26577
+ @Query({ name: "${name}.receipt", permission: ${JSON.stringify(readPermission)}, tenantScope: "required" })
26578
+ export class Receipt${className}Query {
26579
+ constructor(@Inject(${className}Service) private readonly service: ${className}Service) {}
26580
+ execute(id: string, operationId: string): Promise<${className}ReceiptResult> {
26581
+ return this.service.receipt(id, operationId);
26582
+ }
26583
+ }
26584
+
26585
+ @Injectable({ scope: "request" })
26586
+ @Query({ name: "${name}.job-status", permission: ${JSON.stringify(readPermission)}, tenantScope: "required" })
26587
+ export class JobStatus${className}Query {
26588
+ constructor(@Inject(${className}Service) private readonly service: ${className}Service) {}
26589
+ execute(id: string, operationId: string): Promise<${className}JobStatusResult> {
26590
+ return this.service.jobStatus(id, operationId);
26591
+ }
26592
+ }
26593
+
26542
26594
  @Injectable({ scope: "request" })
26543
26595
  @Command({
26544
26596
  name: ${JSON.stringify(commandName)},
@@ -26580,11 +26632,14 @@ export class Refresh${className}Job {
26580
26632
  export class ${className}Controller {
26581
26633
  constructor(
26582
26634
  @Inject(List${className}Query) private readonly listQuery: List${className}Query,
26635
+ @Inject(Detail${className}Query) private readonly detailQuery: Detail${className}Query,
26636
+ @Inject(Receipt${className}Query) private readonly receiptQuery: Receipt${className}Query,
26637
+ @Inject(JobStatus${className}Query) private readonly jobStatusQuery: JobStatus${className}Query,
26583
26638
  @Inject(Approve${className}Command) private readonly approveCommand: Approve${className}Command,
26584
- @Inject(${className}Service) private readonly service: ${className}Service,
26585
26639
  ) {}
26586
26640
 
26587
26641
  @Get("/", {
26642
+ permission: ${JSON.stringify(readPermission)},
26588
26643
  query: ${className}ListQuery,
26589
26644
  tags: [${JSON.stringify(`${name}.list`)}],
26590
26645
  responses: { 200: ${className}ListResponse },
@@ -26604,6 +26659,7 @@ export class ${className}Controller {
26604
26659
  }
26605
26660
 
26606
26661
  @Get("/:id", {
26662
+ permission: ${JSON.stringify(readPermission)},
26607
26663
  params: ${className}Params,
26608
26664
  tags: [${JSON.stringify(`${name}.detail`)}],
26609
26665
  responses: { 200: ${className}Row },
@@ -26619,7 +26675,7 @@ export class ${className}Controller {
26619
26675
  },
26620
26676
  })
26621
26677
  detail(@Param("id") id: string): Effect.Effect<${className}Result, ${className}ListError, never> {
26622
- return Effect.promise(() => this.service.detail(id));
26678
+ return Effect.promise(() => this.detailQuery.execute(id));
26623
26679
  }
26624
26680
 
26625
26681
  @Post("/:id/approve", {
@@ -26651,6 +26707,7 @@ export class ${className}Controller {
26651
26707
  }
26652
26708
 
26653
26709
  @Get("/:id/operations/:operationId", {
26710
+ permission: ${JSON.stringify(readPermission)},
26654
26711
  params: ${className}ReceiptParams,
26655
26712
  tags: [${JSON.stringify(`${name}.receipt`)}],
26656
26713
  responses: { 200: ${className}Receipt },
@@ -26672,10 +26729,11 @@ export class ${className}Controller {
26672
26729
  @Param("id") id: string,
26673
26730
  @Param("operationId") operationId: string,
26674
26731
  ): Effect.Effect<${className}ReceiptResult, ${className}ReceiptError, never> {
26675
- return Effect.promise(() => this.service.receipt(id, operationId));
26732
+ return Effect.promise(() => this.receiptQuery.execute(id, operationId));
26676
26733
  }
26677
26734
 
26678
26735
  @Get("/:id/operations/:operationId/status", {
26736
+ permission: ${JSON.stringify(readPermission)},
26679
26737
  params: ${className}ReceiptParams,
26680
26738
  tags: [${JSON.stringify(`${name}.job-status`)}],
26681
26739
  responses: { 200: ${className}JobStatus },
@@ -26696,7 +26754,7 @@ export class ${className}Controller {
26696
26754
  @Param("id") id: string,
26697
26755
  @Param("operationId") operationId: string,
26698
26756
  ): Effect.Effect<${className}JobStatusResult, ${className}JobStatusError, never> {
26699
- return Effect.promise(() => this.service.jobStatus(id, operationId));
26757
+ return Effect.promise(() => this.jobStatusQuery.execute(id, operationId));
26700
26758
  }
26701
26759
  }
26702
26760
 
@@ -26704,7 +26762,14 @@ export const ${className}Feature = defineFeatureSlice({
26704
26762
  name: ${JSON.stringify(name)},
26705
26763
  tags: ["type:feature"],
26706
26764
  spec: featureSpec,
26707
- providers: [${className}Service, List${className}Query, Approve${className}Command],
26765
+ providers: [
26766
+ ${className}Service,
26767
+ List${className}Query,
26768
+ Detail${className}Query,
26769
+ Receipt${className}Query,
26770
+ JobStatus${className}Query,
26771
+ Approve${className}Command,
26772
+ ],
26708
26773
  controllers: [${className}Controller],
26709
26774
  jobs: [Refresh${className}Job],
26710
26775
  });
@@ -26880,13 +26945,13 @@ export const ${className}RequestContext = {
26880
26945
  requestId: "request-example",
26881
26946
  tenantId: "tenant-example",
26882
26947
  actorId: "reviewer-example",
26883
- permissions: [${JSON.stringify(permission)}],
26948
+ permissions: [${JSON.stringify(permission)}, ${JSON.stringify(readPermission)}],
26884
26949
  } as const;
26885
26950
 
26886
26951
  export const ${className}RequestEnvelope = {
26887
26952
  ...${className}RequestContext,
26888
26953
  identity: { authenticated: true, subject: "reviewer-example" },
26889
- access: { tenantId: "tenant-example", permissions: [${JSON.stringify(permission)}] },
26954
+ access: { tenantId: "tenant-example", permissions: [${JSON.stringify(permission)}, ${JSON.stringify(readPermission)}] },
26890
26955
  } as const satisfies Record<string, unknown>;
26891
26956
 
26892
26957
  export const ${className}JobRequestEnvelope = {
@@ -27739,11 +27804,12 @@ async function generateScaffold(args) {
27739
27804
  if ((kind === "resource" || kind === "feature") && (args.force || args.module !== undefined)) {
27740
27805
  throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", `${kind} creates its own module; use --name and optionally --register-in, never --force or --module`);
27741
27806
  }
27742
- if (kind !== "feature" && (args.permission !== undefined || args.rpc !== undefined)) {
27743
- throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "--permission and --rpc are only supported for feature generation");
27807
+ if (kind !== "feature" && (args.permission !== undefined || args.read_permission !== undefined || args.rpc !== undefined)) {
27808
+ throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "--permission, --read-permission and --rpc are only supported for feature generation");
27744
27809
  }
27745
27810
  const featureGovernance = kind === "feature" ? {
27746
27811
  permission: requireFeatureCapability(args.permission, "permission"),
27812
+ readPermission: requireFeatureCapability(args.read_permission, "read-permission"),
27747
27813
  rpc: requireFeatureCapability(args.rpc, "rpc"),
27748
27814
  database: {
27749
27815
  source: requireFeatureDatabase(args.database_source, "database-source", /^(?!\/)(?!.*\\)(?!.*(?:^|\/)\.\.(?:\/|$))[A-Za-z0-9._/-]+$/),
@@ -29033,7 +29099,7 @@ Execution: skipped`);
29033
29099
  return format === "json" || json ? result : formatPromotionExecution(result);
29034
29100
  }
29035
29101
  async function runAppTool(request, options = {}) {
29036
- 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) {
29102
+ 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.read_permission !== undefined && request["read-permission"] !== undefined || request.request_id !== undefined && request["request-id"] !== undefined) {
29037
29103
  throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "Do not combine hyphenated and underscored aliases of the same flag");
29038
29104
  }
29039
29105
  request = {
@@ -29044,6 +29110,7 @@ async function runAppTool(request, options = {}) {
29044
29110
  ...request["database-source"] === undefined ? {} : { database_source: request["database-source"] },
29045
29111
  ...request["database-table"] === undefined ? {} : { database_table: request["database-table"] },
29046
29112
  ...request["database-type"] === undefined ? {} : { database_type: request["database-type"] },
29113
+ ...request["read-permission"] === undefined ? {} : { read_permission: request["read-permission"] },
29047
29114
  ...request["request-id"] === undefined ? {} : { request_id: request["request-id"] }
29048
29115
  };
29049
29116
  if (request.events !== undefined || request.request_id !== undefined) {
@@ -29218,6 +29285,8 @@ function registerAppTools(server, options = {}) {
29218
29285
  name: optional(String2(), "[init/generate] Project or object name"),
29219
29286
  module: optional(String2(), "[generate] Target feature module (required for command/query/controller)"),
29220
29287
  permission: optional(String2(), "[generate feature] Explicit approval permission; not inferred from name"),
29288
+ read_permission: optional(String2(), "[generate feature] Explicit read permission; not inferred from name"),
29289
+ "read-permission": optional(String2(), "[generate feature] CLI alias of read_permission"),
29221
29290
  rpc: optional(String2(), "[generate feature] Explicit durable RPC capability; must be registered by the host"),
29222
29291
  database_source: optional(String2(), "[generate feature] Project-relative database source path"),
29223
29292
  database_table: optional(String2(), "[generate feature] Schema-qualified table owned by the feature"),
@@ -29258,6 +29327,8 @@ function registerAppAliases(server) {
29258
29327
  name: optional(String2(), "[generate] Object name"),
29259
29328
  module: optional(String2(), "[generate] Target feature module"),
29260
29329
  permission: optional(String2(), "[generate feature] Explicit approval permission; not inferred from name"),
29330
+ read_permission: optional(String2(), "[generate feature] Explicit read permission; not inferred from name"),
29331
+ "read-permission": optional(String2(), "[generate feature] CLI alias of read_permission"),
29261
29332
  rpc: optional(String2(), "[generate feature] Explicit durable RPC capability; must be registered by the host"),
29262
29333
  database_source: optional(String2(), "[generate feature] Project-relative database source path"),
29263
29334
  database_table: optional(String2(), "[generate feature] Schema-qualified table owned by the feature"),
@@ -32983,7 +33054,7 @@ async function runProjectDeployment(http, args) {
32983
33054
  // package.json
32984
33055
  var package_default = {
32985
33056
  name: "@supacloud/cli",
32986
- version: "0.106.3",
33057
+ version: "0.107.1",
32987
33058
  description: "Project-scoped CLI for SupaCloud users",
32988
33059
  type: "module",
32989
33060
  main: "./dist/index.js",
@@ -33035,7 +33106,7 @@ var package_default = {
33035
33106
  directory: "packages/cli"
33036
33107
  },
33037
33108
  dependencies: {
33038
- "@supacloud/compiler": "^0.50.1",
33109
+ "@supacloud/compiler": "^0.51.0",
33039
33110
  "@supacloud/delivery": "0.21.0",
33040
33111
  "@supacloud/db": "0.20.0",
33041
33112
  typebox: "1.3.37"
@@ -33047,7 +33118,7 @@ var package_default = {
33047
33118
  },
33048
33119
  devDependencies: {
33049
33120
  "@supabase/supabase-js": "2.118.0",
33050
- "@supacloud/elysia": "0.35.2",
33121
+ "@supacloud/elysia": "0.36.0",
33051
33122
  "@types/bun": "^1.4.3",
33052
33123
  effect: "4.0.5",
33053
33124
  elysia: "2.0.0-beta.32",
@@ -4,6 +4,7 @@
4
4
  */
5
5
  export declare function featureScaffold(name: string, className: string, governance: {
6
6
  permission: string;
7
+ readPermission: string;
7
8
  rpc: string;
8
9
  database: {
9
10
  source: string;
@@ -71,6 +71,8 @@ export interface AppToolArguments {
71
71
  name?: string;
72
72
  module?: string;
73
73
  permission?: string;
74
+ read_permission?: string;
75
+ "read-permission"?: string;
74
76
  rpc?: string;
75
77
  database_source?: string;
76
78
  database_table?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.106.3",
3
+ "version": "0.107.1",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -52,7 +52,7 @@
52
52
  "directory": "packages/cli"
53
53
  },
54
54
  "dependencies": {
55
- "@supacloud/compiler": "^0.50.1",
55
+ "@supacloud/compiler": "^0.51.0",
56
56
  "@supacloud/delivery": "0.21.0",
57
57
  "@supacloud/db": "0.20.0",
58
58
  "typebox": "1.3.37"
@@ -64,7 +64,7 @@
64
64
  },
65
65
  "devDependencies": {
66
66
  "@supabase/supabase-js": "2.118.0",
67
- "@supacloud/elysia": "0.35.2",
67
+ "@supacloud/elysia": "0.36.0",
68
68
  "@types/bun": "^1.4.3",
69
69
  "effect": "4.0.5",
70
70
  "elysia": "2.0.0-beta.32",