@supacloud/compiler 0.50.0 → 0.51.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/cli.js CHANGED
@@ -270,6 +270,16 @@ function validateFeatureSpec(spec, module) {
270
270
  if (spec.tenantField !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(spec.tenantField)) {
271
271
  error("feature-tenant-boundary-invalid", `Feature ${spec.name} declares an unsafe tenant field.`);
272
272
  }
273
+ if (spec.requiredAdapters !== undefined) {
274
+ if (spec.requiredAdapters.length === 0 || spec.requiredAdapters.some((token) => !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(token)) || new Set(spec.requiredAdapters).size !== spec.requiredAdapters.length) {
275
+ error("feature-adapter-requirements-invalid", `Feature ${spec.name} must declare unique, non-empty adapter token identifiers.`);
276
+ }
277
+ }
278
+ if (spec.requiredAdapterScopes !== undefined) {
279
+ if (spec.requiredAdapters === undefined || Object.keys(spec.requiredAdapterScopes).some((token) => !spec.requiredAdapters?.includes(token))) {
280
+ error("feature-adapter-requirements-invalid", `Feature ${spec.name} must scope only declared required adapters.`);
281
+ }
282
+ }
273
283
  const databaseTransition = spec.transitions.some((transition) => transition.persistence?.boundary === "database");
274
284
  if (databaseTransition && spec.database === undefined && spec.file !== undefined) {
275
285
  error("feature-database-source-required", `Database Feature ${spec.name} must declare its database source, table and rowType.`);
@@ -286,6 +296,19 @@ function validateFeatureSpec(spec, module) {
286
296
  }
287
297
  }
288
298
  const names = new Set;
299
+ if (module && spec.requiredAdapters !== undefined) {
300
+ for (const token of spec.requiredAdapters) {
301
+ const provider = module.providers.find((candidate) => candidate.token === token || candidate.useClass === token);
302
+ if (!provider) {
303
+ error("feature-adapter-unresolved", `Feature ${spec.name} requires adapter ${token}, but module ${module.name} does not register a provider for it.`);
304
+ } else {
305
+ const expectedScope = spec.requiredAdapterScopes?.[token];
306
+ if (expectedScope !== undefined && provider.scope !== expectedScope) {
307
+ error("feature-adapter-provider-scope", `Feature ${spec.name} requires adapter ${token} to use ${expectedScope} scope, but module ${module.name} registers it with ${provider.scope} scope.`);
308
+ }
309
+ }
310
+ }
311
+ }
289
312
  for (const transition of spec.transitions) {
290
313
  if (!transition.name.trim() || names.has(transition.name)) {
291
314
  error("duplicate-feature-transition", `Feature ${spec.name} has duplicate/empty transition '${transition.name}'.`);
@@ -325,11 +348,20 @@ function validateFeatureSpec(spec, module) {
325
348
  }
326
349
  if (!module)
327
350
  continue;
328
- if (transition.persistence?.boundary === "database") {
351
+ if (transition.persistence !== undefined) {
329
352
  for (const query of module.queries) {
353
+ const queryProvider = module.providers.find((provider) => provider.token === query.className || provider.useClass === query.className);
354
+ if (!queryProvider) {
355
+ error("feature-query-provider-unresolved", `Feature query ${query.className} is not registered as a runtime provider in module ${module.name}.`);
356
+ } else if (queryProvider.scope !== "request") {
357
+ error("feature-query-provider-scope", `Feature query ${query.className} must use request scope, but module ${module.name} registers it with ${queryProvider.scope} scope.`);
358
+ }
330
359
  if (query.tenantScope !== "required") {
331
360
  error("feature-query-tenant-boundary", `Query ${query.className} in Feature ${spec.name} must declare tenantScope: "required".`);
332
361
  }
362
+ if (spec.readPermission !== undefined && query.permission !== spec.readPermission) {
363
+ error("feature-query-permission", `Query ${query.className} in Feature ${spec.name} must declare read permission '${spec.readPermission}'.`);
364
+ }
333
365
  }
334
366
  for (const job of module.jobs ?? []) {
335
367
  if (job.tenantScope !== "required") {
@@ -346,6 +378,12 @@ function validateFeatureSpec(spec, module) {
346
378
  error("feature-command-unresolved", `Transition ${transition.name} declares governance without a command binding.`);
347
379
  }
348
380
  if (command) {
381
+ const commandProvider = module.providers.find((provider) => provider.token === command.className || provider.useClass === command.className);
382
+ if (!commandProvider) {
383
+ error("feature-command-provider-unresolved", `Feature transition ${transition.name} references command ${command.className}, but the command is not registered as a provider in module ${module.name}.`);
384
+ } else if (commandProvider.scope !== "request") {
385
+ error("feature-command-provider-scope", `Feature command ${command.className} must use request scope, but module ${module.name} registers it with ${commandProvider.scope} scope.`);
386
+ }
349
387
  for (const key of ["permission", "transaction", "idempotency", "audit"]) {
350
388
  if (transition[key] !== undefined && transition[key] !== command[key]) {
351
389
  error("feature-governance-drift", `Transition ${transition.name} ${key} differs from command ${command.className}.`);
@@ -372,6 +410,12 @@ function validateFeatureSpec(spec, module) {
372
410
  error("feature-job-unresolved", `Transition ${transition.name} must reference exactly one Job in module ${module.name}.`);
373
411
  } else {
374
412
  const job = jobs[0];
413
+ const jobProvider = module.providers.find((provider) => provider.token === job.className || provider.useClass === job.className);
414
+ if (!jobProvider) {
415
+ error("feature-job-provider-unresolved", `Feature transition ${transition.name} references Job ${job.className}, but the Job is not registered as a runtime provider in module ${module.name}.`);
416
+ } else if (jobProvider.scope !== "job") {
417
+ error("feature-job-provider-scope", `Feature Job ${job.className} must use job scope, but module ${module.name} registers it with ${jobProvider.scope} scope.`);
418
+ }
375
419
  if (job.tenantScope !== "required") {
376
420
  error("feature-job-tenant-boundary", `Job ${job.className} in Feature ${spec.name} must declare tenantScope: "required".`);
377
421
  }
@@ -390,6 +434,17 @@ function validateFeatureSpec(spec, module) {
390
434
  }
391
435
  }
392
436
  }
437
+ const declaredTags = module ? new Set(module.controllers.flatMap((controller) => controller.routes.flatMap((route) => route.tags ?? []))) : new Set;
438
+ if (transition.persistence !== undefined && declaredTags.size > 0 && transition.invalidateTags === undefined) {
439
+ error("feature-invalidation-tags-required", `Persistent transition ${transition.name} must declare invalidateTags when Feature ${spec.name} exposes query tags.`);
440
+ }
441
+ if (transition.invalidateTags !== undefined && declaredTags.size > 0) {
442
+ for (const tag of transition.invalidateTags) {
443
+ if (!declaredTags.has(tag)) {
444
+ error("feature-invalidation-tag-unresolved", `Transition ${transition.name} invalidates '${tag}', but Feature ${spec.name} does not declare that query tag.`);
445
+ }
446
+ }
447
+ }
393
448
  if (transition.route) {
394
449
  const routes = module.controllers.flatMap((controller) => controller.routes.filter((route) => `${route.method} ${joinRoutePaths(controller.path, route.path)}` === transition.route));
395
450
  if (routes.length !== 1) {
@@ -1437,15 +1492,26 @@ var init_validate = __esm(() => {
1437
1492
  "duplicate-feature-transition": { code: "SC6102", docsUrl: "https://supacloud.dev/errors/SC6102" },
1438
1493
  "invalid-feature-transition": { code: "SC6103", docsUrl: "https://supacloud.dev/errors/SC6103" },
1439
1494
  "feature-command-unresolved": { code: "SC6104", docsUrl: "https://supacloud.dev/errors/SC6104" },
1495
+ "feature-command-provider-unresolved": { code: "SC6128", docsUrl: "https://supacloud.dev/errors/SC6128" },
1496
+ "feature-command-provider-scope": { code: "SC6134", docsUrl: "https://supacloud.dev/errors/SC6134" },
1497
+ "feature-adapter-unresolved": { code: "SC6135", docsUrl: "https://supacloud.dev/errors/SC6135" },
1498
+ "feature-adapter-requirements-invalid": { code: "SC6136", docsUrl: "https://supacloud.dev/errors/SC6136" },
1499
+ "feature-adapter-provider-scope": { code: "SC6137", docsUrl: "https://supacloud.dev/errors/SC6137" },
1440
1500
  "feature-governance-drift": { code: "SC6105", docsUrl: "https://supacloud.dev/errors/SC6105" },
1441
1501
  "feature-persistence-invalid": { code: "SC6110", docsUrl: "https://supacloud.dev/errors/SC6110" },
1442
1502
  "feature-persistence-drift": { code: "SC6111", docsUrl: "https://supacloud.dev/errors/SC6111" },
1503
+ "feature-invalidation-tag-unresolved": { code: "SC6113", docsUrl: "https://supacloud.dev/errors/SC6113" },
1443
1504
  "feature-tenant-boundary-invalid": { code: "SC6112", docsUrl: "https://supacloud.dev/errors/SC6112" },
1444
- "feature-tenant-boundary-drift": { code: "SC6113", docsUrl: "https://supacloud.dev/errors/SC6113" },
1505
+ "feature-tenant-boundary-drift": { code: "SC6127", docsUrl: "https://supacloud.dev/errors/SC6127" },
1445
1506
  "feature-query-tenant-boundary": { code: "SC6114", docsUrl: "https://supacloud.dev/errors/SC6114" },
1507
+ "feature-query-provider-unresolved": { code: "SC6130", docsUrl: "https://supacloud.dev/errors/SC6130" },
1508
+ "feature-query-provider-scope": { code: "SC6131", docsUrl: "https://supacloud.dev/errors/SC6131" },
1509
+ "feature-query-permission": { code: "SC6138", docsUrl: "https://supacloud.dev/errors/SC6138" },
1446
1510
  "feature-job-tenant-boundary": { code: "SC6115", docsUrl: "https://supacloud.dev/errors/SC6115" },
1447
1511
  "feature-command-tenant-boundary": { code: "SC6116", docsUrl: "https://supacloud.dev/errors/SC6116" },
1448
1512
  "feature-job-unresolved": { code: "SC6117", docsUrl: "https://supacloud.dev/errors/SC6117" },
1513
+ "feature-job-provider-unresolved": { code: "SC6129", docsUrl: "https://supacloud.dev/errors/SC6129" },
1514
+ "feature-job-provider-scope": { code: "SC6132", docsUrl: "https://supacloud.dev/errors/SC6132" },
1449
1515
  "feature-job-idempotency": { code: "SC6118", docsUrl: "https://supacloud.dev/errors/SC6118" },
1450
1516
  "feature-job-mode": { code: "SC6119", docsUrl: "https://supacloud.dev/errors/SC6119" },
1451
1517
  "feature-job-contract": { code: "SC6120", docsUrl: "https://supacloud.dev/errors/SC6120" },
@@ -1454,6 +1520,7 @@ var init_validate = __esm(() => {
1454
1520
  "feature-database-source-invalid": { code: "SC6123", docsUrl: "https://supacloud.dev/errors/SC6123" },
1455
1521
  "feature-database-table-invalid": { code: "SC6124", docsUrl: "https://supacloud.dev/errors/SC6124" },
1456
1522
  "feature-database-row-type-invalid": { code: "SC6125", docsUrl: "https://supacloud.dev/errors/SC6125" },
1523
+ "feature-invalidation-tags-required": { code: "SC6126", docsUrl: "https://supacloud.dev/errors/SC6126" },
1457
1524
  "feature-route-unresolved": { code: "SC6106", docsUrl: "https://supacloud.dev/errors/SC6106" },
1458
1525
  "feature-route-drift": { code: "SC6107", docsUrl: "https://supacloud.dev/errors/SC6107" },
1459
1526
  "invalid-feature-spec": { code: "SC6108", docsUrl: "https://supacloud.dev/errors/SC6108" }
@@ -1509,10 +1576,13 @@ function buildContractManifest(graph, artifacts) {
1509
1576
  });
1510
1577
  }
1511
1578
  for (const query of module.queries) {
1579
+ if (query.permission)
1580
+ permissions.add(query.permission);
1512
1581
  queries.push({
1513
1582
  name: query.name,
1514
1583
  className: query.className,
1515
1584
  module: module.name,
1585
+ ...query.permission === undefined ? {} : { permission: query.permission },
1516
1586
  ...query.tenantScope === undefined ? {} : { tenantScope: query.tenantScope }
1517
1587
  });
1518
1588
  }
@@ -1536,6 +1606,7 @@ function buildContractManifest(graph, artifacts) {
1536
1606
  states: [...module.featureSpec.states],
1537
1607
  ...module.featureSpec.database === undefined ? {} : { database: module.featureSpec.database },
1538
1608
  ...module.featureSpec.tenantField === undefined ? {} : { tenantField: module.featureSpec.tenantField },
1609
+ ...module.featureSpec.readPermission === undefined ? {} : { readPermission: module.featureSpec.readPermission },
1539
1610
  transitions: module.featureSpec.transitions.map((transition) => {
1540
1611
  const job = transition.job === undefined ? undefined : (module.jobs ?? []).find((candidate) => candidate.className === transition.job || candidate.name === transition.job);
1541
1612
  return {
@@ -1577,8 +1648,9 @@ function buildContractManifest(graph, artifacts) {
1577
1648
  const governedCommand = command ?? featureCommand;
1578
1649
  const effectiveCommand = route.command ?? featureTransition?.command;
1579
1650
  const idempotency = route.idempotency ?? command?.idempotency ?? featureTransition?.idempotency;
1580
- if (governedCommand?.permission)
1581
- permissions.add(governedCommand.permission);
1651
+ const permission = route.permission ?? governedCommand?.permission ?? featureTransition?.permission;
1652
+ if (permission)
1653
+ permissions.add(permission);
1582
1654
  if (route.contract?.evidence)
1583
1655
  fixtures.add(route.contract.evidence);
1584
1656
  routes.push({
@@ -1588,7 +1660,7 @@ function buildContractManifest(graph, artifacts) {
1588
1660
  controller: controller.className,
1589
1661
  handler: route.handler,
1590
1662
  ...effectiveCommand === undefined ? {} : { command: effectiveCommand },
1591
- ...governedCommand?.permission === undefined ? {} : { permission: governedCommand.permission },
1663
+ ...permission === undefined ? {} : { permission },
1592
1664
  ...idempotency === undefined ? {} : { idempotency },
1593
1665
  ...route.tags === undefined ? {} : { tags: [...route.tags] },
1594
1666
  requestSchemas: Object.fromEntries(["body", "params", "query", "headers", "cookie"].flatMap((key) => route[key] === undefined ? [] : [[key, route[key]]])),
@@ -2650,6 +2722,7 @@ function renderClient(graph, options) {
2650
2722
  const featureTransition = featureTransitionForRoute(module, controller, route);
2651
2723
  const featureCommand = featureTransition?.command === undefined ? undefined : commandByClassName.get(featureTransition.command);
2652
2724
  const governedCommand = command ?? featureCommand;
2725
+ const routePermission = route.permission ?? governedCommand?.permission ?? featureTransition?.permission;
2653
2726
  const idempotencyMode = route.idempotency ?? command?.idempotency ?? featureTransition?.idempotency ?? "none";
2654
2727
  const transitionRoute = `${route.method} ${fullPath}`;
2655
2728
  const invalidationTags = featureInvalidationTags.get(`${module.name}:${transitionRoute}`);
@@ -2676,6 +2749,7 @@ function renderClient(graph, options) {
2676
2749
  ["parse", route.parse],
2677
2750
  ["allowDeleteBody", route.allowDeleteBody],
2678
2751
  ["idempotency", idempotencyMode === "none" ? undefined : idempotencyMode],
2752
+ ["permission", routePermission],
2679
2753
  ["tags", route.tags],
2680
2754
  ["invalidateTags", invalidationTags]
2681
2755
  ]) {
@@ -2717,6 +2791,7 @@ function renderClient(graph, options) {
2717
2791
  path: fullPath,
2718
2792
  controller: controller.className,
2719
2793
  handler: route.handler,
2794
+ ...routePermission === undefined ? {} : { permission: routePermission },
2720
2795
  kind: procedureKind,
2721
2796
  idempotency: idempotencyMode,
2722
2797
  ...route.tags === undefined ? {} : { tags: route.tags },
@@ -2729,7 +2804,7 @@ function renderClient(graph, options) {
2729
2804
  handler: route.handler,
2730
2805
  kind: procedureKind,
2731
2806
  ...governedCommand?.name === undefined ? {} : { command: governedCommand.name },
2732
- ...governedCommand?.permission === undefined ? {} : { permission: governedCommand.permission },
2807
+ ...routePermission === undefined ? {} : { permission: routePermission },
2733
2808
  idempotency: idempotencyMode,
2734
2809
  ...route.tags === undefined ? {} : { tags: [...route.tags] },
2735
2810
  ...invalidationTags === undefined ? {} : { invalidateTags: [...invalidationTags] }
@@ -2785,6 +2860,7 @@ ${procedureMethods.join(`
2785
2860
  " controller: string;",
2786
2861
  " handler: string;",
2787
2862
  " kind: Kind;",
2863
+ " permission?: string;",
2788
2864
  " idempotency: Idempotency;",
2789
2865
  " tags?: readonly string[];",
2790
2866
  " invalidateTags?: readonly string[];",
@@ -3423,7 +3499,7 @@ function renderOpenApi(graph, options) {
3423
3499
  const featureCommand = featureTransition?.command === undefined ? undefined : commandByClass.get(featureTransition.command);
3424
3500
  const governedCommand = command ?? featureCommand;
3425
3501
  const idempotencyMode = route.idempotency ?? command?.idempotency ?? featureTransition?.idempotency ?? "none";
3426
- const permission = governedCommand?.permission ?? featureTransition?.permission;
3502
+ const permission = route.permission ?? governedCommand?.permission ?? featureTransition?.permission;
3427
3503
  const requiresAuth = (route.guards?.length ?? 0) > 0 || permission !== undefined || governedCommand !== undefined;
3428
3504
  const responseKind = route.contract?.response;
3429
3505
  const responseContentType = responseKind === "binary" || responseKind === "stream" ? "application/octet-stream" : "application/json";
@@ -3876,6 +3952,10 @@ function renderPermissions(graph) {
3876
3952
  if (command.permission)
3877
3953
  permissions.add(command.permission);
3878
3954
  }
3955
+ for (const query of module.queries) {
3956
+ if (query.permission)
3957
+ permissions.add(query.permission);
3958
+ }
3879
3959
  for (const controller of module.controllers) {
3880
3960
  for (const route of controller.routes) {
3881
3961
  const featureTransition = featureTransitionForRoute(module, controller, route);
@@ -3883,7 +3963,7 @@ function renderPermissions(graph) {
3883
3963
  const featureCommand = featureTransition?.command === undefined ? undefined : module.commands.find((candidate) => candidate.className === featureTransition.command || candidate.name === featureTransition.command);
3884
3964
  const governedCommand = command ?? featureCommand;
3885
3965
  const effectiveCommand = route.command ?? featureTransition?.command;
3886
- const perm = governedCommand?.permission ?? featureTransition?.permission;
3966
+ const perm = route.permission ?? governedCommand?.permission ?? featureTransition?.permission;
3887
3967
  const idempotency = route.idempotency ?? command?.idempotency ?? featureTransition?.idempotency;
3888
3968
  if (perm)
3889
3969
  permissions.add(perm);
@@ -19575,10 +19655,12 @@ function parseModule(candidate, nameByNode, ctx) {
19575
19655
  if (queryDec) {
19576
19656
  const meta = decoratorObjectArg(queryDec);
19577
19657
  if (meta) {
19658
+ const permission = stringLiteralProp(meta, "permission");
19578
19659
  const tenantScope = commandModeProp(meta, "tenantScope");
19579
19660
  queries.push({
19580
19661
  className: cls.name?.text ?? "<anonymous>",
19581
19662
  name: stringLiteralProp(meta, "name") ?? cls.name?.text ?? "<anonymous>",
19663
+ ...permission === undefined ? {} : { permission },
19582
19664
  ...tenantScope === undefined ? {} : { tenantScope }
19583
19665
  });
19584
19666
  }
@@ -19639,11 +19721,23 @@ function parseFeatureSpec(input, ctx, seen = new Set) {
19639
19721
  if (databaseExpr !== undefined && database === undefined)
19640
19722
  return invalid();
19641
19723
  const tenantField = stringLiteralProp(input, "tenantField");
19724
+ const readPermission = stringLiteralProp(input, "readPermission");
19725
+ const requiredAdaptersExpr = getProp(input, "requiredAdapters");
19726
+ const requiredAdapterScopesExpr = getProp(input, "requiredAdapterScopes");
19642
19727
  const statesExpr = getProp(input, "states");
19643
19728
  const transitionObject = getProp(input, "transitions");
19644
19729
  if (!name || !statesExpr || !ts7.isArrayLiteralExpression(statesExpr) || statesExpr.elements.some((state) => !ts7.isStringLiteral(state)) || !transitionObject || !ts7.isObjectLiteralExpression(transitionObject) || input.properties.some((property) => !ts7.isPropertyAssignment(property))) {
19645
19730
  return invalid();
19646
19731
  }
19732
+ const requiredAdapters = requiredAdaptersExpr === undefined ? undefined : ts7.isArrayLiteralExpression(requiredAdaptersExpr) && requiredAdaptersExpr.elements.every((element) => ts7.isStringLiteral(element) && element.text.trim().length > 0) ? requiredAdaptersExpr.elements.map((element) => element.text) : undefined;
19733
+ if (requiredAdaptersExpr !== undefined && requiredAdapters === undefined)
19734
+ return invalid();
19735
+ const requiredAdapterScopes = requiredAdapterScopesExpr === undefined ? undefined : ts7.isObjectLiteralExpression(requiredAdapterScopesExpr) && requiredAdapterScopesExpr.properties.every((property) => ts7.isPropertyAssignment(property) && !ts7.isComputedPropertyName(property.name) && ts7.isStringLiteral(property.initializer) && isScope(property.initializer.text)) ? Object.fromEntries(requiredAdapterScopesExpr.properties.map((property) => [
19736
+ propertyName(property.name),
19737
+ property.initializer.getText().slice(1, -1)
19738
+ ])) : undefined;
19739
+ if (requiredAdapterScopesExpr !== undefined && requiredAdapterScopes === undefined)
19740
+ return invalid();
19647
19741
  const states = [];
19648
19742
  for (const state of statesExpr.elements) {
19649
19743
  if (!ts7.isStringLiteral(state))
@@ -19699,6 +19793,9 @@ function parseFeatureSpec(input, ctx, seen = new Set) {
19699
19793
  states,
19700
19794
  ...database === undefined ? {} : { database },
19701
19795
  ...tenantField === undefined ? {} : { tenantField },
19796
+ ...readPermission === undefined ? {} : { readPermission },
19797
+ ...requiredAdapters === undefined ? {} : { requiredAdapters },
19798
+ ...requiredAdapterScopes === undefined ? {} : { requiredAdapterScopes },
19702
19799
  transitions,
19703
19800
  file: sourcePath(ctx.rootDir, input.getSourceFile().fileName),
19704
19801
  line: lineOf(input)
@@ -20635,6 +20732,9 @@ function parseController(input, ctx) {
20635
20732
  const optionsArg = args[1];
20636
20733
  const optionsObject = resolveStaticObjectLiteral(optionsArg, ctx);
20637
20734
  if (optionsObject) {
20735
+ const permission = stringLiteralProp(optionsObject, "permission");
20736
+ if (permission !== undefined)
20737
+ route.permission = permission;
20638
20738
  const parsePolicy = routePolicyProperty(optionsObject, "parse", ctx);
20639
20739
  const deletePolicy = routePolicyProperty(optionsObject, "allowDeleteBody", ctx);
20640
20740
  const idempotency = checkedRouteIdempotency(optionsObject, ctx, route.handler);
@@ -24993,10 +25093,10 @@ import { readFileSync as readFileSync4 } from "node:fs";
24993
25093
  import { resolve as resolve18 } from "node:path";
24994
25094
 
24995
25095
  // src/migration-app-version.ts
24996
- var MIGRATION_APP_VERSION = "0.31.0";
25096
+ var MIGRATION_APP_VERSION = "0.32.0";
24997
25097
 
24998
25098
  // src/migration-elysia-version.ts
24999
- var MIGRATION_ELYSIA_VERSION = "0.35.1";
25099
+ var MIGRATION_ELYSIA_VERSION = "0.36.0";
25000
25100
 
25001
25101
  // src/migration-policy.ts
25002
25102
  var cachedCompilerVersion;
@@ -24,6 +24,7 @@ export interface ContractManifest {
24
24
  name: string;
25
25
  className: string;
26
26
  module: string;
27
+ permission?: string;
27
28
  tenantScope?: "required" | "none";
28
29
  }>;
29
30
  jobs: Array<{
package/dist/index.js CHANGED
@@ -269,6 +269,16 @@ function validateFeatureSpec(spec, module) {
269
269
  if (spec.tenantField !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(spec.tenantField)) {
270
270
  error("feature-tenant-boundary-invalid", `Feature ${spec.name} declares an unsafe tenant field.`);
271
271
  }
272
+ if (spec.requiredAdapters !== undefined) {
273
+ if (spec.requiredAdapters.length === 0 || spec.requiredAdapters.some((token) => !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(token)) || new Set(spec.requiredAdapters).size !== spec.requiredAdapters.length) {
274
+ error("feature-adapter-requirements-invalid", `Feature ${spec.name} must declare unique, non-empty adapter token identifiers.`);
275
+ }
276
+ }
277
+ if (spec.requiredAdapterScopes !== undefined) {
278
+ if (spec.requiredAdapters === undefined || Object.keys(spec.requiredAdapterScopes).some((token) => !spec.requiredAdapters?.includes(token))) {
279
+ error("feature-adapter-requirements-invalid", `Feature ${spec.name} must scope only declared required adapters.`);
280
+ }
281
+ }
272
282
  const databaseTransition = spec.transitions.some((transition) => transition.persistence?.boundary === "database");
273
283
  if (databaseTransition && spec.database === undefined && spec.file !== undefined) {
274
284
  error("feature-database-source-required", `Database Feature ${spec.name} must declare its database source, table and rowType.`);
@@ -285,6 +295,19 @@ function validateFeatureSpec(spec, module) {
285
295
  }
286
296
  }
287
297
  const names = new Set;
298
+ if (module && spec.requiredAdapters !== undefined) {
299
+ for (const token of spec.requiredAdapters) {
300
+ const provider = module.providers.find((candidate) => candidate.token === token || candidate.useClass === token);
301
+ if (!provider) {
302
+ error("feature-adapter-unresolved", `Feature ${spec.name} requires adapter ${token}, but module ${module.name} does not register a provider for it.`);
303
+ } else {
304
+ const expectedScope = spec.requiredAdapterScopes?.[token];
305
+ if (expectedScope !== undefined && provider.scope !== expectedScope) {
306
+ error("feature-adapter-provider-scope", `Feature ${spec.name} requires adapter ${token} to use ${expectedScope} scope, but module ${module.name} registers it with ${provider.scope} scope.`);
307
+ }
308
+ }
309
+ }
310
+ }
288
311
  for (const transition of spec.transitions) {
289
312
  if (!transition.name.trim() || names.has(transition.name)) {
290
313
  error("duplicate-feature-transition", `Feature ${spec.name} has duplicate/empty transition '${transition.name}'.`);
@@ -324,11 +347,20 @@ function validateFeatureSpec(spec, module) {
324
347
  }
325
348
  if (!module)
326
349
  continue;
327
- if (transition.persistence?.boundary === "database") {
350
+ if (transition.persistence !== undefined) {
328
351
  for (const query of module.queries) {
352
+ const queryProvider = module.providers.find((provider) => provider.token === query.className || provider.useClass === query.className);
353
+ if (!queryProvider) {
354
+ error("feature-query-provider-unresolved", `Feature query ${query.className} is not registered as a runtime provider in module ${module.name}.`);
355
+ } else if (queryProvider.scope !== "request") {
356
+ error("feature-query-provider-scope", `Feature query ${query.className} must use request scope, but module ${module.name} registers it with ${queryProvider.scope} scope.`);
357
+ }
329
358
  if (query.tenantScope !== "required") {
330
359
  error("feature-query-tenant-boundary", `Query ${query.className} in Feature ${spec.name} must declare tenantScope: "required".`);
331
360
  }
361
+ if (spec.readPermission !== undefined && query.permission !== spec.readPermission) {
362
+ error("feature-query-permission", `Query ${query.className} in Feature ${spec.name} must declare read permission '${spec.readPermission}'.`);
363
+ }
332
364
  }
333
365
  for (const job of module.jobs ?? []) {
334
366
  if (job.tenantScope !== "required") {
@@ -345,6 +377,12 @@ function validateFeatureSpec(spec, module) {
345
377
  error("feature-command-unresolved", `Transition ${transition.name} declares governance without a command binding.`);
346
378
  }
347
379
  if (command) {
380
+ const commandProvider = module.providers.find((provider) => provider.token === command.className || provider.useClass === command.className);
381
+ if (!commandProvider) {
382
+ error("feature-command-provider-unresolved", `Feature transition ${transition.name} references command ${command.className}, but the command is not registered as a provider in module ${module.name}.`);
383
+ } else if (commandProvider.scope !== "request") {
384
+ error("feature-command-provider-scope", `Feature command ${command.className} must use request scope, but module ${module.name} registers it with ${commandProvider.scope} scope.`);
385
+ }
348
386
  for (const key of ["permission", "transaction", "idempotency", "audit"]) {
349
387
  if (transition[key] !== undefined && transition[key] !== command[key]) {
350
388
  error("feature-governance-drift", `Transition ${transition.name} ${key} differs from command ${command.className}.`);
@@ -371,6 +409,12 @@ function validateFeatureSpec(spec, module) {
371
409
  error("feature-job-unresolved", `Transition ${transition.name} must reference exactly one Job in module ${module.name}.`);
372
410
  } else {
373
411
  const job = jobs[0];
412
+ const jobProvider = module.providers.find((provider) => provider.token === job.className || provider.useClass === job.className);
413
+ if (!jobProvider) {
414
+ error("feature-job-provider-unresolved", `Feature transition ${transition.name} references Job ${job.className}, but the Job is not registered as a runtime provider in module ${module.name}.`);
415
+ } else if (jobProvider.scope !== "job") {
416
+ error("feature-job-provider-scope", `Feature Job ${job.className} must use job scope, but module ${module.name} registers it with ${jobProvider.scope} scope.`);
417
+ }
374
418
  if (job.tenantScope !== "required") {
375
419
  error("feature-job-tenant-boundary", `Job ${job.className} in Feature ${spec.name} must declare tenantScope: "required".`);
376
420
  }
@@ -389,6 +433,17 @@ function validateFeatureSpec(spec, module) {
389
433
  }
390
434
  }
391
435
  }
436
+ const declaredTags = module ? new Set(module.controllers.flatMap((controller) => controller.routes.flatMap((route) => route.tags ?? []))) : new Set;
437
+ if (transition.persistence !== undefined && declaredTags.size > 0 && transition.invalidateTags === undefined) {
438
+ error("feature-invalidation-tags-required", `Persistent transition ${transition.name} must declare invalidateTags when Feature ${spec.name} exposes query tags.`);
439
+ }
440
+ if (transition.invalidateTags !== undefined && declaredTags.size > 0) {
441
+ for (const tag of transition.invalidateTags) {
442
+ if (!declaredTags.has(tag)) {
443
+ error("feature-invalidation-tag-unresolved", `Transition ${transition.name} invalidates '${tag}', but Feature ${spec.name} does not declare that query tag.`);
444
+ }
445
+ }
446
+ }
392
447
  if (transition.route) {
393
448
  const routes = module.controllers.flatMap((controller) => controller.routes.filter((route) => `${route.method} ${joinRoutePaths(controller.path, route.path)}` === transition.route));
394
449
  if (routes.length !== 1) {
@@ -1436,15 +1491,26 @@ var init_validate = __esm(() => {
1436
1491
  "duplicate-feature-transition": { code: "SC6102", docsUrl: "https://supacloud.dev/errors/SC6102" },
1437
1492
  "invalid-feature-transition": { code: "SC6103", docsUrl: "https://supacloud.dev/errors/SC6103" },
1438
1493
  "feature-command-unresolved": { code: "SC6104", docsUrl: "https://supacloud.dev/errors/SC6104" },
1494
+ "feature-command-provider-unresolved": { code: "SC6128", docsUrl: "https://supacloud.dev/errors/SC6128" },
1495
+ "feature-command-provider-scope": { code: "SC6134", docsUrl: "https://supacloud.dev/errors/SC6134" },
1496
+ "feature-adapter-unresolved": { code: "SC6135", docsUrl: "https://supacloud.dev/errors/SC6135" },
1497
+ "feature-adapter-requirements-invalid": { code: "SC6136", docsUrl: "https://supacloud.dev/errors/SC6136" },
1498
+ "feature-adapter-provider-scope": { code: "SC6137", docsUrl: "https://supacloud.dev/errors/SC6137" },
1439
1499
  "feature-governance-drift": { code: "SC6105", docsUrl: "https://supacloud.dev/errors/SC6105" },
1440
1500
  "feature-persistence-invalid": { code: "SC6110", docsUrl: "https://supacloud.dev/errors/SC6110" },
1441
1501
  "feature-persistence-drift": { code: "SC6111", docsUrl: "https://supacloud.dev/errors/SC6111" },
1502
+ "feature-invalidation-tag-unresolved": { code: "SC6113", docsUrl: "https://supacloud.dev/errors/SC6113" },
1442
1503
  "feature-tenant-boundary-invalid": { code: "SC6112", docsUrl: "https://supacloud.dev/errors/SC6112" },
1443
- "feature-tenant-boundary-drift": { code: "SC6113", docsUrl: "https://supacloud.dev/errors/SC6113" },
1504
+ "feature-tenant-boundary-drift": { code: "SC6127", docsUrl: "https://supacloud.dev/errors/SC6127" },
1444
1505
  "feature-query-tenant-boundary": { code: "SC6114", docsUrl: "https://supacloud.dev/errors/SC6114" },
1506
+ "feature-query-provider-unresolved": { code: "SC6130", docsUrl: "https://supacloud.dev/errors/SC6130" },
1507
+ "feature-query-provider-scope": { code: "SC6131", docsUrl: "https://supacloud.dev/errors/SC6131" },
1508
+ "feature-query-permission": { code: "SC6138", docsUrl: "https://supacloud.dev/errors/SC6138" },
1445
1509
  "feature-job-tenant-boundary": { code: "SC6115", docsUrl: "https://supacloud.dev/errors/SC6115" },
1446
1510
  "feature-command-tenant-boundary": { code: "SC6116", docsUrl: "https://supacloud.dev/errors/SC6116" },
1447
1511
  "feature-job-unresolved": { code: "SC6117", docsUrl: "https://supacloud.dev/errors/SC6117" },
1512
+ "feature-job-provider-unresolved": { code: "SC6129", docsUrl: "https://supacloud.dev/errors/SC6129" },
1513
+ "feature-job-provider-scope": { code: "SC6132", docsUrl: "https://supacloud.dev/errors/SC6132" },
1448
1514
  "feature-job-idempotency": { code: "SC6118", docsUrl: "https://supacloud.dev/errors/SC6118" },
1449
1515
  "feature-job-mode": { code: "SC6119", docsUrl: "https://supacloud.dev/errors/SC6119" },
1450
1516
  "feature-job-contract": { code: "SC6120", docsUrl: "https://supacloud.dev/errors/SC6120" },
@@ -1453,6 +1519,7 @@ var init_validate = __esm(() => {
1453
1519
  "feature-database-source-invalid": { code: "SC6123", docsUrl: "https://supacloud.dev/errors/SC6123" },
1454
1520
  "feature-database-table-invalid": { code: "SC6124", docsUrl: "https://supacloud.dev/errors/SC6124" },
1455
1521
  "feature-database-row-type-invalid": { code: "SC6125", docsUrl: "https://supacloud.dev/errors/SC6125" },
1522
+ "feature-invalidation-tags-required": { code: "SC6126", docsUrl: "https://supacloud.dev/errors/SC6126" },
1456
1523
  "feature-route-unresolved": { code: "SC6106", docsUrl: "https://supacloud.dev/errors/SC6106" },
1457
1524
  "feature-route-drift": { code: "SC6107", docsUrl: "https://supacloud.dev/errors/SC6107" },
1458
1525
  "invalid-feature-spec": { code: "SC6108", docsUrl: "https://supacloud.dev/errors/SC6108" }
@@ -1508,10 +1575,13 @@ function buildContractManifest(graph, artifacts) {
1508
1575
  });
1509
1576
  }
1510
1577
  for (const query of module.queries) {
1578
+ if (query.permission)
1579
+ permissions.add(query.permission);
1511
1580
  queries.push({
1512
1581
  name: query.name,
1513
1582
  className: query.className,
1514
1583
  module: module.name,
1584
+ ...query.permission === undefined ? {} : { permission: query.permission },
1515
1585
  ...query.tenantScope === undefined ? {} : { tenantScope: query.tenantScope }
1516
1586
  });
1517
1587
  }
@@ -1535,6 +1605,7 @@ function buildContractManifest(graph, artifacts) {
1535
1605
  states: [...module.featureSpec.states],
1536
1606
  ...module.featureSpec.database === undefined ? {} : { database: module.featureSpec.database },
1537
1607
  ...module.featureSpec.tenantField === undefined ? {} : { tenantField: module.featureSpec.tenantField },
1608
+ ...module.featureSpec.readPermission === undefined ? {} : { readPermission: module.featureSpec.readPermission },
1538
1609
  transitions: module.featureSpec.transitions.map((transition) => {
1539
1610
  const job = transition.job === undefined ? undefined : (module.jobs ?? []).find((candidate) => candidate.className === transition.job || candidate.name === transition.job);
1540
1611
  return {
@@ -1576,8 +1647,9 @@ function buildContractManifest(graph, artifacts) {
1576
1647
  const governedCommand = command ?? featureCommand;
1577
1648
  const effectiveCommand = route.command ?? featureTransition?.command;
1578
1649
  const idempotency = route.idempotency ?? command?.idempotency ?? featureTransition?.idempotency;
1579
- if (governedCommand?.permission)
1580
- permissions.add(governedCommand.permission);
1650
+ const permission = route.permission ?? governedCommand?.permission ?? featureTransition?.permission;
1651
+ if (permission)
1652
+ permissions.add(permission);
1581
1653
  if (route.contract?.evidence)
1582
1654
  fixtures.add(route.contract.evidence);
1583
1655
  routes.push({
@@ -1587,7 +1659,7 @@ function buildContractManifest(graph, artifacts) {
1587
1659
  controller: controller.className,
1588
1660
  handler: route.handler,
1589
1661
  ...effectiveCommand === undefined ? {} : { command: effectiveCommand },
1590
- ...governedCommand?.permission === undefined ? {} : { permission: governedCommand.permission },
1662
+ ...permission === undefined ? {} : { permission },
1591
1663
  ...idempotency === undefined ? {} : { idempotency },
1592
1664
  ...route.tags === undefined ? {} : { tags: [...route.tags] },
1593
1665
  requestSchemas: Object.fromEntries(["body", "params", "query", "headers", "cookie"].flatMap((key) => route[key] === undefined ? [] : [[key, route[key]]])),
@@ -2649,6 +2721,7 @@ function renderClient(graph, options) {
2649
2721
  const featureTransition = featureTransitionForRoute(module, controller, route);
2650
2722
  const featureCommand = featureTransition?.command === undefined ? undefined : commandByClassName.get(featureTransition.command);
2651
2723
  const governedCommand = command ?? featureCommand;
2724
+ const routePermission = route.permission ?? governedCommand?.permission ?? featureTransition?.permission;
2652
2725
  const idempotencyMode = route.idempotency ?? command?.idempotency ?? featureTransition?.idempotency ?? "none";
2653
2726
  const transitionRoute = `${route.method} ${fullPath}`;
2654
2727
  const invalidationTags = featureInvalidationTags.get(`${module.name}:${transitionRoute}`);
@@ -2675,6 +2748,7 @@ function renderClient(graph, options) {
2675
2748
  ["parse", route.parse],
2676
2749
  ["allowDeleteBody", route.allowDeleteBody],
2677
2750
  ["idempotency", idempotencyMode === "none" ? undefined : idempotencyMode],
2751
+ ["permission", routePermission],
2678
2752
  ["tags", route.tags],
2679
2753
  ["invalidateTags", invalidationTags]
2680
2754
  ]) {
@@ -2716,6 +2790,7 @@ function renderClient(graph, options) {
2716
2790
  path: fullPath,
2717
2791
  controller: controller.className,
2718
2792
  handler: route.handler,
2793
+ ...routePermission === undefined ? {} : { permission: routePermission },
2719
2794
  kind: procedureKind,
2720
2795
  idempotency: idempotencyMode,
2721
2796
  ...route.tags === undefined ? {} : { tags: route.tags },
@@ -2728,7 +2803,7 @@ function renderClient(graph, options) {
2728
2803
  handler: route.handler,
2729
2804
  kind: procedureKind,
2730
2805
  ...governedCommand?.name === undefined ? {} : { command: governedCommand.name },
2731
- ...governedCommand?.permission === undefined ? {} : { permission: governedCommand.permission },
2806
+ ...routePermission === undefined ? {} : { permission: routePermission },
2732
2807
  idempotency: idempotencyMode,
2733
2808
  ...route.tags === undefined ? {} : { tags: [...route.tags] },
2734
2809
  ...invalidationTags === undefined ? {} : { invalidateTags: [...invalidationTags] }
@@ -2784,6 +2859,7 @@ ${procedureMethods.join(`
2784
2859
  " controller: string;",
2785
2860
  " handler: string;",
2786
2861
  " kind: Kind;",
2862
+ " permission?: string;",
2787
2863
  " idempotency: Idempotency;",
2788
2864
  " tags?: readonly string[];",
2789
2865
  " invalidateTags?: readonly string[];",
@@ -3422,7 +3498,7 @@ function renderOpenApi(graph, options) {
3422
3498
  const featureCommand = featureTransition?.command === undefined ? undefined : commandByClass.get(featureTransition.command);
3423
3499
  const governedCommand = command ?? featureCommand;
3424
3500
  const idempotencyMode = route.idempotency ?? command?.idempotency ?? featureTransition?.idempotency ?? "none";
3425
- const permission = governedCommand?.permission ?? featureTransition?.permission;
3501
+ const permission = route.permission ?? governedCommand?.permission ?? featureTransition?.permission;
3426
3502
  const requiresAuth = (route.guards?.length ?? 0) > 0 || permission !== undefined || governedCommand !== undefined;
3427
3503
  const responseKind = route.contract?.response;
3428
3504
  const responseContentType = responseKind === "binary" || responseKind === "stream" ? "application/octet-stream" : "application/json";
@@ -3875,6 +3951,10 @@ function renderPermissions(graph) {
3875
3951
  if (command.permission)
3876
3952
  permissions.add(command.permission);
3877
3953
  }
3954
+ for (const query of module.queries) {
3955
+ if (query.permission)
3956
+ permissions.add(query.permission);
3957
+ }
3878
3958
  for (const controller of module.controllers) {
3879
3959
  for (const route of controller.routes) {
3880
3960
  const featureTransition = featureTransitionForRoute(module, controller, route);
@@ -3882,7 +3962,7 @@ function renderPermissions(graph) {
3882
3962
  const featureCommand = featureTransition?.command === undefined ? undefined : module.commands.find((candidate) => candidate.className === featureTransition.command || candidate.name === featureTransition.command);
3883
3963
  const governedCommand = command ?? featureCommand;
3884
3964
  const effectiveCommand = route.command ?? featureTransition?.command;
3885
- const perm = governedCommand?.permission ?? featureTransition?.permission;
3965
+ const perm = route.permission ?? governedCommand?.permission ?? featureTransition?.permission;
3886
3966
  const idempotency = route.idempotency ?? command?.idempotency ?? featureTransition?.idempotency;
3887
3967
  if (perm)
3888
3968
  permissions.add(perm);
@@ -19534,10 +19614,12 @@ function parseModule(candidate, nameByNode, ctx) {
19534
19614
  if (queryDec) {
19535
19615
  const meta = decoratorObjectArg(queryDec);
19536
19616
  if (meta) {
19617
+ const permission = stringLiteralProp(meta, "permission");
19537
19618
  const tenantScope = commandModeProp(meta, "tenantScope");
19538
19619
  queries.push({
19539
19620
  className: cls.name?.text ?? "<anonymous>",
19540
19621
  name: stringLiteralProp(meta, "name") ?? cls.name?.text ?? "<anonymous>",
19622
+ ...permission === undefined ? {} : { permission },
19541
19623
  ...tenantScope === undefined ? {} : { tenantScope }
19542
19624
  });
19543
19625
  }
@@ -19598,11 +19680,23 @@ function parseFeatureSpec(input, ctx, seen = new Set) {
19598
19680
  if (databaseExpr !== undefined && database === undefined)
19599
19681
  return invalid();
19600
19682
  const tenantField = stringLiteralProp(input, "tenantField");
19683
+ const readPermission = stringLiteralProp(input, "readPermission");
19684
+ const requiredAdaptersExpr = getProp(input, "requiredAdapters");
19685
+ const requiredAdapterScopesExpr = getProp(input, "requiredAdapterScopes");
19601
19686
  const statesExpr = getProp(input, "states");
19602
19687
  const transitionObject = getProp(input, "transitions");
19603
19688
  if (!name || !statesExpr || !ts7.isArrayLiteralExpression(statesExpr) || statesExpr.elements.some((state) => !ts7.isStringLiteral(state)) || !transitionObject || !ts7.isObjectLiteralExpression(transitionObject) || input.properties.some((property) => !ts7.isPropertyAssignment(property))) {
19604
19689
  return invalid();
19605
19690
  }
19691
+ const requiredAdapters = requiredAdaptersExpr === undefined ? undefined : ts7.isArrayLiteralExpression(requiredAdaptersExpr) && requiredAdaptersExpr.elements.every((element) => ts7.isStringLiteral(element) && element.text.trim().length > 0) ? requiredAdaptersExpr.elements.map((element) => element.text) : undefined;
19692
+ if (requiredAdaptersExpr !== undefined && requiredAdapters === undefined)
19693
+ return invalid();
19694
+ const requiredAdapterScopes = requiredAdapterScopesExpr === undefined ? undefined : ts7.isObjectLiteralExpression(requiredAdapterScopesExpr) && requiredAdapterScopesExpr.properties.every((property) => ts7.isPropertyAssignment(property) && !ts7.isComputedPropertyName(property.name) && ts7.isStringLiteral(property.initializer) && isScope(property.initializer.text)) ? Object.fromEntries(requiredAdapterScopesExpr.properties.map((property) => [
19695
+ propertyName(property.name),
19696
+ property.initializer.getText().slice(1, -1)
19697
+ ])) : undefined;
19698
+ if (requiredAdapterScopesExpr !== undefined && requiredAdapterScopes === undefined)
19699
+ return invalid();
19606
19700
  const states = [];
19607
19701
  for (const state of statesExpr.elements) {
19608
19702
  if (!ts7.isStringLiteral(state))
@@ -19658,6 +19752,9 @@ function parseFeatureSpec(input, ctx, seen = new Set) {
19658
19752
  states,
19659
19753
  ...database === undefined ? {} : { database },
19660
19754
  ...tenantField === undefined ? {} : { tenantField },
19755
+ ...readPermission === undefined ? {} : { readPermission },
19756
+ ...requiredAdapters === undefined ? {} : { requiredAdapters },
19757
+ ...requiredAdapterScopes === undefined ? {} : { requiredAdapterScopes },
19661
19758
  transitions,
19662
19759
  file: sourcePath(ctx.rootDir, input.getSourceFile().fileName),
19663
19760
  line: lineOf(input)
@@ -20594,6 +20691,9 @@ function parseController(input, ctx) {
20594
20691
  const optionsArg = args[1];
20595
20692
  const optionsObject = resolveStaticObjectLiteral(optionsArg, ctx);
20596
20693
  if (optionsObject) {
20694
+ const permission = stringLiteralProp(optionsObject, "permission");
20695
+ if (permission !== undefined)
20696
+ route.permission = permission;
20597
20697
  const parsePolicy = routePolicyProperty(optionsObject, "parse", ctx);
20598
20698
  const deletePolicy = routePolicyProperty(optionsObject, "allowDeleteBody", ctx);
20599
20699
  const idempotency = checkedRouteIdempotency(optionsObject, ctx, route.handler);
@@ -24675,10 +24775,10 @@ import { readFileSync as readFileSync4 } from "node:fs";
24675
24775
  import { resolve as resolve17 } from "node:path";
24676
24776
 
24677
24777
  // src/migration-app-version.ts
24678
- var MIGRATION_APP_VERSION = "0.31.0";
24778
+ var MIGRATION_APP_VERSION = "0.32.0";
24679
24779
 
24680
24780
  // src/migration-elysia-version.ts
24681
- var MIGRATION_ELYSIA_VERSION = "0.35.1";
24781
+ var MIGRATION_ELYSIA_VERSION = "0.36.0";
24682
24782
 
24683
24783
  // src/migration-policy.ts
24684
24784
  var cachedCompilerVersion;
@@ -1 +1 @@
1
- export declare const MIGRATION_APP_VERSION = "0.31.0";
1
+ export declare const MIGRATION_APP_VERSION = "0.32.0";
@@ -1 +1 @@
1
- export declare const MIGRATION_ELYSIA_VERSION = "0.35.1";
1
+ export declare const MIGRATION_ELYSIA_VERSION = "0.36.0";
package/dist/types.d.ts CHANGED
@@ -201,6 +201,7 @@ export interface EffectRouteNode {
201
201
  timeoutMs?: number;
202
202
  }
203
203
  export interface RouteNode {
204
+ permission?: string;
204
205
  /** Transport-level idempotency contract, independent of route command binding. */
205
206
  idempotency?: "required" | "none";
206
207
  /** Explicit query cache tags exposed to generated clients. */
@@ -344,6 +345,7 @@ export interface JobNode {
344
345
  export interface QueryNode {
345
346
  className: string;
346
347
  name: string;
348
+ permission?: string;
347
349
  tenantScope?: "required" | "none";
348
350
  }
349
351
  export interface FeatureTransitionNode {
@@ -382,6 +384,9 @@ export interface FeatureSpecNode {
382
384
  states: string[];
383
385
  database?: FeatureDatabaseSourceNode;
384
386
  tenantField?: string;
387
+ readPermission?: string;
388
+ requiredAdapters?: string[];
389
+ requiredAdapterScopes?: Record<string, Scope>;
385
390
  transitions: FeatureTransitionNode[];
386
391
  file?: string;
387
392
  line?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/compiler",
3
- "version": "0.50.0",
3
+ "version": "0.51.0",
4
4
  "description": "Static compiler for @supacloud/app metadata: builds the application graph from AST, validates it, and generates reflection-free factory code",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",