@supacloud/compiler 0.50.1 → 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 +96 -9
- package/dist/contract-manifest.d.ts +1 -0
- package/dist/index.js +96 -9
- package/dist/migration-app-version.d.ts +1 -1
- package/dist/migration-elysia-version.d.ts +1 -1
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
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
|
|
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
|
}
|
|
@@ -1448,6 +1492,11 @@ var init_validate = __esm(() => {
|
|
|
1448
1492
|
"duplicate-feature-transition": { code: "SC6102", docsUrl: "https://supacloud.dev/errors/SC6102" },
|
|
1449
1493
|
"invalid-feature-transition": { code: "SC6103", docsUrl: "https://supacloud.dev/errors/SC6103" },
|
|
1450
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" },
|
|
1451
1500
|
"feature-governance-drift": { code: "SC6105", docsUrl: "https://supacloud.dev/errors/SC6105" },
|
|
1452
1501
|
"feature-persistence-invalid": { code: "SC6110", docsUrl: "https://supacloud.dev/errors/SC6110" },
|
|
1453
1502
|
"feature-persistence-drift": { code: "SC6111", docsUrl: "https://supacloud.dev/errors/SC6111" },
|
|
@@ -1455,9 +1504,14 @@ var init_validate = __esm(() => {
|
|
|
1455
1504
|
"feature-tenant-boundary-invalid": { code: "SC6112", docsUrl: "https://supacloud.dev/errors/SC6112" },
|
|
1456
1505
|
"feature-tenant-boundary-drift": { code: "SC6127", docsUrl: "https://supacloud.dev/errors/SC6127" },
|
|
1457
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" },
|
|
1458
1510
|
"feature-job-tenant-boundary": { code: "SC6115", docsUrl: "https://supacloud.dev/errors/SC6115" },
|
|
1459
1511
|
"feature-command-tenant-boundary": { code: "SC6116", docsUrl: "https://supacloud.dev/errors/SC6116" },
|
|
1460
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" },
|
|
1461
1515
|
"feature-job-idempotency": { code: "SC6118", docsUrl: "https://supacloud.dev/errors/SC6118" },
|
|
1462
1516
|
"feature-job-mode": { code: "SC6119", docsUrl: "https://supacloud.dev/errors/SC6119" },
|
|
1463
1517
|
"feature-job-contract": { code: "SC6120", docsUrl: "https://supacloud.dev/errors/SC6120" },
|
|
@@ -1522,10 +1576,13 @@ function buildContractManifest(graph, artifacts) {
|
|
|
1522
1576
|
});
|
|
1523
1577
|
}
|
|
1524
1578
|
for (const query of module.queries) {
|
|
1579
|
+
if (query.permission)
|
|
1580
|
+
permissions.add(query.permission);
|
|
1525
1581
|
queries.push({
|
|
1526
1582
|
name: query.name,
|
|
1527
1583
|
className: query.className,
|
|
1528
1584
|
module: module.name,
|
|
1585
|
+
...query.permission === undefined ? {} : { permission: query.permission },
|
|
1529
1586
|
...query.tenantScope === undefined ? {} : { tenantScope: query.tenantScope }
|
|
1530
1587
|
});
|
|
1531
1588
|
}
|
|
@@ -1549,6 +1606,7 @@ function buildContractManifest(graph, artifacts) {
|
|
|
1549
1606
|
states: [...module.featureSpec.states],
|
|
1550
1607
|
...module.featureSpec.database === undefined ? {} : { database: module.featureSpec.database },
|
|
1551
1608
|
...module.featureSpec.tenantField === undefined ? {} : { tenantField: module.featureSpec.tenantField },
|
|
1609
|
+
...module.featureSpec.readPermission === undefined ? {} : { readPermission: module.featureSpec.readPermission },
|
|
1552
1610
|
transitions: module.featureSpec.transitions.map((transition) => {
|
|
1553
1611
|
const job = transition.job === undefined ? undefined : (module.jobs ?? []).find((candidate) => candidate.className === transition.job || candidate.name === transition.job);
|
|
1554
1612
|
return {
|
|
@@ -1590,8 +1648,9 @@ function buildContractManifest(graph, artifacts) {
|
|
|
1590
1648
|
const governedCommand = command ?? featureCommand;
|
|
1591
1649
|
const effectiveCommand = route.command ?? featureTransition?.command;
|
|
1592
1650
|
const idempotency = route.idempotency ?? command?.idempotency ?? featureTransition?.idempotency;
|
|
1593
|
-
|
|
1594
|
-
|
|
1651
|
+
const permission = route.permission ?? governedCommand?.permission ?? featureTransition?.permission;
|
|
1652
|
+
if (permission)
|
|
1653
|
+
permissions.add(permission);
|
|
1595
1654
|
if (route.contract?.evidence)
|
|
1596
1655
|
fixtures.add(route.contract.evidence);
|
|
1597
1656
|
routes.push({
|
|
@@ -1601,7 +1660,7 @@ function buildContractManifest(graph, artifacts) {
|
|
|
1601
1660
|
controller: controller.className,
|
|
1602
1661
|
handler: route.handler,
|
|
1603
1662
|
...effectiveCommand === undefined ? {} : { command: effectiveCommand },
|
|
1604
|
-
...
|
|
1663
|
+
...permission === undefined ? {} : { permission },
|
|
1605
1664
|
...idempotency === undefined ? {} : { idempotency },
|
|
1606
1665
|
...route.tags === undefined ? {} : { tags: [...route.tags] },
|
|
1607
1666
|
requestSchemas: Object.fromEntries(["body", "params", "query", "headers", "cookie"].flatMap((key) => route[key] === undefined ? [] : [[key, route[key]]])),
|
|
@@ -2663,6 +2722,7 @@ function renderClient(graph, options) {
|
|
|
2663
2722
|
const featureTransition = featureTransitionForRoute(module, controller, route);
|
|
2664
2723
|
const featureCommand = featureTransition?.command === undefined ? undefined : commandByClassName.get(featureTransition.command);
|
|
2665
2724
|
const governedCommand = command ?? featureCommand;
|
|
2725
|
+
const routePermission = route.permission ?? governedCommand?.permission ?? featureTransition?.permission;
|
|
2666
2726
|
const idempotencyMode = route.idempotency ?? command?.idempotency ?? featureTransition?.idempotency ?? "none";
|
|
2667
2727
|
const transitionRoute = `${route.method} ${fullPath}`;
|
|
2668
2728
|
const invalidationTags = featureInvalidationTags.get(`${module.name}:${transitionRoute}`);
|
|
@@ -2689,6 +2749,7 @@ function renderClient(graph, options) {
|
|
|
2689
2749
|
["parse", route.parse],
|
|
2690
2750
|
["allowDeleteBody", route.allowDeleteBody],
|
|
2691
2751
|
["idempotency", idempotencyMode === "none" ? undefined : idempotencyMode],
|
|
2752
|
+
["permission", routePermission],
|
|
2692
2753
|
["tags", route.tags],
|
|
2693
2754
|
["invalidateTags", invalidationTags]
|
|
2694
2755
|
]) {
|
|
@@ -2730,6 +2791,7 @@ function renderClient(graph, options) {
|
|
|
2730
2791
|
path: fullPath,
|
|
2731
2792
|
controller: controller.className,
|
|
2732
2793
|
handler: route.handler,
|
|
2794
|
+
...routePermission === undefined ? {} : { permission: routePermission },
|
|
2733
2795
|
kind: procedureKind,
|
|
2734
2796
|
idempotency: idempotencyMode,
|
|
2735
2797
|
...route.tags === undefined ? {} : { tags: route.tags },
|
|
@@ -2742,7 +2804,7 @@ function renderClient(graph, options) {
|
|
|
2742
2804
|
handler: route.handler,
|
|
2743
2805
|
kind: procedureKind,
|
|
2744
2806
|
...governedCommand?.name === undefined ? {} : { command: governedCommand.name },
|
|
2745
|
-
...
|
|
2807
|
+
...routePermission === undefined ? {} : { permission: routePermission },
|
|
2746
2808
|
idempotency: idempotencyMode,
|
|
2747
2809
|
...route.tags === undefined ? {} : { tags: [...route.tags] },
|
|
2748
2810
|
...invalidationTags === undefined ? {} : { invalidateTags: [...invalidationTags] }
|
|
@@ -2798,6 +2860,7 @@ ${procedureMethods.join(`
|
|
|
2798
2860
|
" controller: string;",
|
|
2799
2861
|
" handler: string;",
|
|
2800
2862
|
" kind: Kind;",
|
|
2863
|
+
" permission?: string;",
|
|
2801
2864
|
" idempotency: Idempotency;",
|
|
2802
2865
|
" tags?: readonly string[];",
|
|
2803
2866
|
" invalidateTags?: readonly string[];",
|
|
@@ -3436,7 +3499,7 @@ function renderOpenApi(graph, options) {
|
|
|
3436
3499
|
const featureCommand = featureTransition?.command === undefined ? undefined : commandByClass.get(featureTransition.command);
|
|
3437
3500
|
const governedCommand = command ?? featureCommand;
|
|
3438
3501
|
const idempotencyMode = route.idempotency ?? command?.idempotency ?? featureTransition?.idempotency ?? "none";
|
|
3439
|
-
const permission = governedCommand?.permission ?? featureTransition?.permission;
|
|
3502
|
+
const permission = route.permission ?? governedCommand?.permission ?? featureTransition?.permission;
|
|
3440
3503
|
const requiresAuth = (route.guards?.length ?? 0) > 0 || permission !== undefined || governedCommand !== undefined;
|
|
3441
3504
|
const responseKind = route.contract?.response;
|
|
3442
3505
|
const responseContentType = responseKind === "binary" || responseKind === "stream" ? "application/octet-stream" : "application/json";
|
|
@@ -3889,6 +3952,10 @@ function renderPermissions(graph) {
|
|
|
3889
3952
|
if (command.permission)
|
|
3890
3953
|
permissions.add(command.permission);
|
|
3891
3954
|
}
|
|
3955
|
+
for (const query of module.queries) {
|
|
3956
|
+
if (query.permission)
|
|
3957
|
+
permissions.add(query.permission);
|
|
3958
|
+
}
|
|
3892
3959
|
for (const controller of module.controllers) {
|
|
3893
3960
|
for (const route of controller.routes) {
|
|
3894
3961
|
const featureTransition = featureTransitionForRoute(module, controller, route);
|
|
@@ -3896,7 +3963,7 @@ function renderPermissions(graph) {
|
|
|
3896
3963
|
const featureCommand = featureTransition?.command === undefined ? undefined : module.commands.find((candidate) => candidate.className === featureTransition.command || candidate.name === featureTransition.command);
|
|
3897
3964
|
const governedCommand = command ?? featureCommand;
|
|
3898
3965
|
const effectiveCommand = route.command ?? featureTransition?.command;
|
|
3899
|
-
const perm = governedCommand?.permission ?? featureTransition?.permission;
|
|
3966
|
+
const perm = route.permission ?? governedCommand?.permission ?? featureTransition?.permission;
|
|
3900
3967
|
const idempotency = route.idempotency ?? command?.idempotency ?? featureTransition?.idempotency;
|
|
3901
3968
|
if (perm)
|
|
3902
3969
|
permissions.add(perm);
|
|
@@ -19588,10 +19655,12 @@ function parseModule(candidate, nameByNode, ctx) {
|
|
|
19588
19655
|
if (queryDec) {
|
|
19589
19656
|
const meta = decoratorObjectArg(queryDec);
|
|
19590
19657
|
if (meta) {
|
|
19658
|
+
const permission = stringLiteralProp(meta, "permission");
|
|
19591
19659
|
const tenantScope = commandModeProp(meta, "tenantScope");
|
|
19592
19660
|
queries.push({
|
|
19593
19661
|
className: cls.name?.text ?? "<anonymous>",
|
|
19594
19662
|
name: stringLiteralProp(meta, "name") ?? cls.name?.text ?? "<anonymous>",
|
|
19663
|
+
...permission === undefined ? {} : { permission },
|
|
19595
19664
|
...tenantScope === undefined ? {} : { tenantScope }
|
|
19596
19665
|
});
|
|
19597
19666
|
}
|
|
@@ -19652,11 +19721,23 @@ function parseFeatureSpec(input, ctx, seen = new Set) {
|
|
|
19652
19721
|
if (databaseExpr !== undefined && database === undefined)
|
|
19653
19722
|
return invalid();
|
|
19654
19723
|
const tenantField = stringLiteralProp(input, "tenantField");
|
|
19724
|
+
const readPermission = stringLiteralProp(input, "readPermission");
|
|
19725
|
+
const requiredAdaptersExpr = getProp(input, "requiredAdapters");
|
|
19726
|
+
const requiredAdapterScopesExpr = getProp(input, "requiredAdapterScopes");
|
|
19655
19727
|
const statesExpr = getProp(input, "states");
|
|
19656
19728
|
const transitionObject = getProp(input, "transitions");
|
|
19657
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))) {
|
|
19658
19730
|
return invalid();
|
|
19659
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();
|
|
19660
19741
|
const states = [];
|
|
19661
19742
|
for (const state of statesExpr.elements) {
|
|
19662
19743
|
if (!ts7.isStringLiteral(state))
|
|
@@ -19712,6 +19793,9 @@ function parseFeatureSpec(input, ctx, seen = new Set) {
|
|
|
19712
19793
|
states,
|
|
19713
19794
|
...database === undefined ? {} : { database },
|
|
19714
19795
|
...tenantField === undefined ? {} : { tenantField },
|
|
19796
|
+
...readPermission === undefined ? {} : { readPermission },
|
|
19797
|
+
...requiredAdapters === undefined ? {} : { requiredAdapters },
|
|
19798
|
+
...requiredAdapterScopes === undefined ? {} : { requiredAdapterScopes },
|
|
19715
19799
|
transitions,
|
|
19716
19800
|
file: sourcePath(ctx.rootDir, input.getSourceFile().fileName),
|
|
19717
19801
|
line: lineOf(input)
|
|
@@ -20648,6 +20732,9 @@ function parseController(input, ctx) {
|
|
|
20648
20732
|
const optionsArg = args[1];
|
|
20649
20733
|
const optionsObject = resolveStaticObjectLiteral(optionsArg, ctx);
|
|
20650
20734
|
if (optionsObject) {
|
|
20735
|
+
const permission = stringLiteralProp(optionsObject, "permission");
|
|
20736
|
+
if (permission !== undefined)
|
|
20737
|
+
route.permission = permission;
|
|
20651
20738
|
const parsePolicy = routePolicyProperty(optionsObject, "parse", ctx);
|
|
20652
20739
|
const deletePolicy = routePolicyProperty(optionsObject, "allowDeleteBody", ctx);
|
|
20653
20740
|
const idempotency = checkedRouteIdempotency(optionsObject, ctx, route.handler);
|
|
@@ -25006,10 +25093,10 @@ import { readFileSync as readFileSync4 } from "node:fs";
|
|
|
25006
25093
|
import { resolve as resolve18 } from "node:path";
|
|
25007
25094
|
|
|
25008
25095
|
// src/migration-app-version.ts
|
|
25009
|
-
var MIGRATION_APP_VERSION = "0.
|
|
25096
|
+
var MIGRATION_APP_VERSION = "0.32.0";
|
|
25010
25097
|
|
|
25011
25098
|
// src/migration-elysia-version.ts
|
|
25012
|
-
var MIGRATION_ELYSIA_VERSION = "0.
|
|
25099
|
+
var MIGRATION_ELYSIA_VERSION = "0.36.0";
|
|
25013
25100
|
|
|
25014
25101
|
// src/migration-policy.ts
|
|
25015
25102
|
var cachedCompilerVersion;
|
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
|
|
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
|
}
|
|
@@ -1447,6 +1491,11 @@ var init_validate = __esm(() => {
|
|
|
1447
1491
|
"duplicate-feature-transition": { code: "SC6102", docsUrl: "https://supacloud.dev/errors/SC6102" },
|
|
1448
1492
|
"invalid-feature-transition": { code: "SC6103", docsUrl: "https://supacloud.dev/errors/SC6103" },
|
|
1449
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" },
|
|
1450
1499
|
"feature-governance-drift": { code: "SC6105", docsUrl: "https://supacloud.dev/errors/SC6105" },
|
|
1451
1500
|
"feature-persistence-invalid": { code: "SC6110", docsUrl: "https://supacloud.dev/errors/SC6110" },
|
|
1452
1501
|
"feature-persistence-drift": { code: "SC6111", docsUrl: "https://supacloud.dev/errors/SC6111" },
|
|
@@ -1454,9 +1503,14 @@ var init_validate = __esm(() => {
|
|
|
1454
1503
|
"feature-tenant-boundary-invalid": { code: "SC6112", docsUrl: "https://supacloud.dev/errors/SC6112" },
|
|
1455
1504
|
"feature-tenant-boundary-drift": { code: "SC6127", docsUrl: "https://supacloud.dev/errors/SC6127" },
|
|
1456
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" },
|
|
1457
1509
|
"feature-job-tenant-boundary": { code: "SC6115", docsUrl: "https://supacloud.dev/errors/SC6115" },
|
|
1458
1510
|
"feature-command-tenant-boundary": { code: "SC6116", docsUrl: "https://supacloud.dev/errors/SC6116" },
|
|
1459
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" },
|
|
1460
1514
|
"feature-job-idempotency": { code: "SC6118", docsUrl: "https://supacloud.dev/errors/SC6118" },
|
|
1461
1515
|
"feature-job-mode": { code: "SC6119", docsUrl: "https://supacloud.dev/errors/SC6119" },
|
|
1462
1516
|
"feature-job-contract": { code: "SC6120", docsUrl: "https://supacloud.dev/errors/SC6120" },
|
|
@@ -1521,10 +1575,13 @@ function buildContractManifest(graph, artifacts) {
|
|
|
1521
1575
|
});
|
|
1522
1576
|
}
|
|
1523
1577
|
for (const query of module.queries) {
|
|
1578
|
+
if (query.permission)
|
|
1579
|
+
permissions.add(query.permission);
|
|
1524
1580
|
queries.push({
|
|
1525
1581
|
name: query.name,
|
|
1526
1582
|
className: query.className,
|
|
1527
1583
|
module: module.name,
|
|
1584
|
+
...query.permission === undefined ? {} : { permission: query.permission },
|
|
1528
1585
|
...query.tenantScope === undefined ? {} : { tenantScope: query.tenantScope }
|
|
1529
1586
|
});
|
|
1530
1587
|
}
|
|
@@ -1548,6 +1605,7 @@ function buildContractManifest(graph, artifacts) {
|
|
|
1548
1605
|
states: [...module.featureSpec.states],
|
|
1549
1606
|
...module.featureSpec.database === undefined ? {} : { database: module.featureSpec.database },
|
|
1550
1607
|
...module.featureSpec.tenantField === undefined ? {} : { tenantField: module.featureSpec.tenantField },
|
|
1608
|
+
...module.featureSpec.readPermission === undefined ? {} : { readPermission: module.featureSpec.readPermission },
|
|
1551
1609
|
transitions: module.featureSpec.transitions.map((transition) => {
|
|
1552
1610
|
const job = transition.job === undefined ? undefined : (module.jobs ?? []).find((candidate) => candidate.className === transition.job || candidate.name === transition.job);
|
|
1553
1611
|
return {
|
|
@@ -1589,8 +1647,9 @@ function buildContractManifest(graph, artifacts) {
|
|
|
1589
1647
|
const governedCommand = command ?? featureCommand;
|
|
1590
1648
|
const effectiveCommand = route.command ?? featureTransition?.command;
|
|
1591
1649
|
const idempotency = route.idempotency ?? command?.idempotency ?? featureTransition?.idempotency;
|
|
1592
|
-
|
|
1593
|
-
|
|
1650
|
+
const permission = route.permission ?? governedCommand?.permission ?? featureTransition?.permission;
|
|
1651
|
+
if (permission)
|
|
1652
|
+
permissions.add(permission);
|
|
1594
1653
|
if (route.contract?.evidence)
|
|
1595
1654
|
fixtures.add(route.contract.evidence);
|
|
1596
1655
|
routes.push({
|
|
@@ -1600,7 +1659,7 @@ function buildContractManifest(graph, artifacts) {
|
|
|
1600
1659
|
controller: controller.className,
|
|
1601
1660
|
handler: route.handler,
|
|
1602
1661
|
...effectiveCommand === undefined ? {} : { command: effectiveCommand },
|
|
1603
|
-
...
|
|
1662
|
+
...permission === undefined ? {} : { permission },
|
|
1604
1663
|
...idempotency === undefined ? {} : { idempotency },
|
|
1605
1664
|
...route.tags === undefined ? {} : { tags: [...route.tags] },
|
|
1606
1665
|
requestSchemas: Object.fromEntries(["body", "params", "query", "headers", "cookie"].flatMap((key) => route[key] === undefined ? [] : [[key, route[key]]])),
|
|
@@ -2662,6 +2721,7 @@ function renderClient(graph, options) {
|
|
|
2662
2721
|
const featureTransition = featureTransitionForRoute(module, controller, route);
|
|
2663
2722
|
const featureCommand = featureTransition?.command === undefined ? undefined : commandByClassName.get(featureTransition.command);
|
|
2664
2723
|
const governedCommand = command ?? featureCommand;
|
|
2724
|
+
const routePermission = route.permission ?? governedCommand?.permission ?? featureTransition?.permission;
|
|
2665
2725
|
const idempotencyMode = route.idempotency ?? command?.idempotency ?? featureTransition?.idempotency ?? "none";
|
|
2666
2726
|
const transitionRoute = `${route.method} ${fullPath}`;
|
|
2667
2727
|
const invalidationTags = featureInvalidationTags.get(`${module.name}:${transitionRoute}`);
|
|
@@ -2688,6 +2748,7 @@ function renderClient(graph, options) {
|
|
|
2688
2748
|
["parse", route.parse],
|
|
2689
2749
|
["allowDeleteBody", route.allowDeleteBody],
|
|
2690
2750
|
["idempotency", idempotencyMode === "none" ? undefined : idempotencyMode],
|
|
2751
|
+
["permission", routePermission],
|
|
2691
2752
|
["tags", route.tags],
|
|
2692
2753
|
["invalidateTags", invalidationTags]
|
|
2693
2754
|
]) {
|
|
@@ -2729,6 +2790,7 @@ function renderClient(graph, options) {
|
|
|
2729
2790
|
path: fullPath,
|
|
2730
2791
|
controller: controller.className,
|
|
2731
2792
|
handler: route.handler,
|
|
2793
|
+
...routePermission === undefined ? {} : { permission: routePermission },
|
|
2732
2794
|
kind: procedureKind,
|
|
2733
2795
|
idempotency: idempotencyMode,
|
|
2734
2796
|
...route.tags === undefined ? {} : { tags: route.tags },
|
|
@@ -2741,7 +2803,7 @@ function renderClient(graph, options) {
|
|
|
2741
2803
|
handler: route.handler,
|
|
2742
2804
|
kind: procedureKind,
|
|
2743
2805
|
...governedCommand?.name === undefined ? {} : { command: governedCommand.name },
|
|
2744
|
-
...
|
|
2806
|
+
...routePermission === undefined ? {} : { permission: routePermission },
|
|
2745
2807
|
idempotency: idempotencyMode,
|
|
2746
2808
|
...route.tags === undefined ? {} : { tags: [...route.tags] },
|
|
2747
2809
|
...invalidationTags === undefined ? {} : { invalidateTags: [...invalidationTags] }
|
|
@@ -2797,6 +2859,7 @@ ${procedureMethods.join(`
|
|
|
2797
2859
|
" controller: string;",
|
|
2798
2860
|
" handler: string;",
|
|
2799
2861
|
" kind: Kind;",
|
|
2862
|
+
" permission?: string;",
|
|
2800
2863
|
" idempotency: Idempotency;",
|
|
2801
2864
|
" tags?: readonly string[];",
|
|
2802
2865
|
" invalidateTags?: readonly string[];",
|
|
@@ -3435,7 +3498,7 @@ function renderOpenApi(graph, options) {
|
|
|
3435
3498
|
const featureCommand = featureTransition?.command === undefined ? undefined : commandByClass.get(featureTransition.command);
|
|
3436
3499
|
const governedCommand = command ?? featureCommand;
|
|
3437
3500
|
const idempotencyMode = route.idempotency ?? command?.idempotency ?? featureTransition?.idempotency ?? "none";
|
|
3438
|
-
const permission = governedCommand?.permission ?? featureTransition?.permission;
|
|
3501
|
+
const permission = route.permission ?? governedCommand?.permission ?? featureTransition?.permission;
|
|
3439
3502
|
const requiresAuth = (route.guards?.length ?? 0) > 0 || permission !== undefined || governedCommand !== undefined;
|
|
3440
3503
|
const responseKind = route.contract?.response;
|
|
3441
3504
|
const responseContentType = responseKind === "binary" || responseKind === "stream" ? "application/octet-stream" : "application/json";
|
|
@@ -3888,6 +3951,10 @@ function renderPermissions(graph) {
|
|
|
3888
3951
|
if (command.permission)
|
|
3889
3952
|
permissions.add(command.permission);
|
|
3890
3953
|
}
|
|
3954
|
+
for (const query of module.queries) {
|
|
3955
|
+
if (query.permission)
|
|
3956
|
+
permissions.add(query.permission);
|
|
3957
|
+
}
|
|
3891
3958
|
for (const controller of module.controllers) {
|
|
3892
3959
|
for (const route of controller.routes) {
|
|
3893
3960
|
const featureTransition = featureTransitionForRoute(module, controller, route);
|
|
@@ -3895,7 +3962,7 @@ function renderPermissions(graph) {
|
|
|
3895
3962
|
const featureCommand = featureTransition?.command === undefined ? undefined : module.commands.find((candidate) => candidate.className === featureTransition.command || candidate.name === featureTransition.command);
|
|
3896
3963
|
const governedCommand = command ?? featureCommand;
|
|
3897
3964
|
const effectiveCommand = route.command ?? featureTransition?.command;
|
|
3898
|
-
const perm = governedCommand?.permission ?? featureTransition?.permission;
|
|
3965
|
+
const perm = route.permission ?? governedCommand?.permission ?? featureTransition?.permission;
|
|
3899
3966
|
const idempotency = route.idempotency ?? command?.idempotency ?? featureTransition?.idempotency;
|
|
3900
3967
|
if (perm)
|
|
3901
3968
|
permissions.add(perm);
|
|
@@ -19547,10 +19614,12 @@ function parseModule(candidate, nameByNode, ctx) {
|
|
|
19547
19614
|
if (queryDec) {
|
|
19548
19615
|
const meta = decoratorObjectArg(queryDec);
|
|
19549
19616
|
if (meta) {
|
|
19617
|
+
const permission = stringLiteralProp(meta, "permission");
|
|
19550
19618
|
const tenantScope = commandModeProp(meta, "tenantScope");
|
|
19551
19619
|
queries.push({
|
|
19552
19620
|
className: cls.name?.text ?? "<anonymous>",
|
|
19553
19621
|
name: stringLiteralProp(meta, "name") ?? cls.name?.text ?? "<anonymous>",
|
|
19622
|
+
...permission === undefined ? {} : { permission },
|
|
19554
19623
|
...tenantScope === undefined ? {} : { tenantScope }
|
|
19555
19624
|
});
|
|
19556
19625
|
}
|
|
@@ -19611,11 +19680,23 @@ function parseFeatureSpec(input, ctx, seen = new Set) {
|
|
|
19611
19680
|
if (databaseExpr !== undefined && database === undefined)
|
|
19612
19681
|
return invalid();
|
|
19613
19682
|
const tenantField = stringLiteralProp(input, "tenantField");
|
|
19683
|
+
const readPermission = stringLiteralProp(input, "readPermission");
|
|
19684
|
+
const requiredAdaptersExpr = getProp(input, "requiredAdapters");
|
|
19685
|
+
const requiredAdapterScopesExpr = getProp(input, "requiredAdapterScopes");
|
|
19614
19686
|
const statesExpr = getProp(input, "states");
|
|
19615
19687
|
const transitionObject = getProp(input, "transitions");
|
|
19616
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))) {
|
|
19617
19689
|
return invalid();
|
|
19618
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();
|
|
19619
19700
|
const states = [];
|
|
19620
19701
|
for (const state of statesExpr.elements) {
|
|
19621
19702
|
if (!ts7.isStringLiteral(state))
|
|
@@ -19671,6 +19752,9 @@ function parseFeatureSpec(input, ctx, seen = new Set) {
|
|
|
19671
19752
|
states,
|
|
19672
19753
|
...database === undefined ? {} : { database },
|
|
19673
19754
|
...tenantField === undefined ? {} : { tenantField },
|
|
19755
|
+
...readPermission === undefined ? {} : { readPermission },
|
|
19756
|
+
...requiredAdapters === undefined ? {} : { requiredAdapters },
|
|
19757
|
+
...requiredAdapterScopes === undefined ? {} : { requiredAdapterScopes },
|
|
19674
19758
|
transitions,
|
|
19675
19759
|
file: sourcePath(ctx.rootDir, input.getSourceFile().fileName),
|
|
19676
19760
|
line: lineOf(input)
|
|
@@ -20607,6 +20691,9 @@ function parseController(input, ctx) {
|
|
|
20607
20691
|
const optionsArg = args[1];
|
|
20608
20692
|
const optionsObject = resolveStaticObjectLiteral(optionsArg, ctx);
|
|
20609
20693
|
if (optionsObject) {
|
|
20694
|
+
const permission = stringLiteralProp(optionsObject, "permission");
|
|
20695
|
+
if (permission !== undefined)
|
|
20696
|
+
route.permission = permission;
|
|
20610
20697
|
const parsePolicy = routePolicyProperty(optionsObject, "parse", ctx);
|
|
20611
20698
|
const deletePolicy = routePolicyProperty(optionsObject, "allowDeleteBody", ctx);
|
|
20612
20699
|
const idempotency = checkedRouteIdempotency(optionsObject, ctx, route.handler);
|
|
@@ -24688,10 +24775,10 @@ import { readFileSync as readFileSync4 } from "node:fs";
|
|
|
24688
24775
|
import { resolve as resolve17 } from "node:path";
|
|
24689
24776
|
|
|
24690
24777
|
// src/migration-app-version.ts
|
|
24691
|
-
var MIGRATION_APP_VERSION = "0.
|
|
24778
|
+
var MIGRATION_APP_VERSION = "0.32.0";
|
|
24692
24779
|
|
|
24693
24780
|
// src/migration-elysia-version.ts
|
|
24694
|
-
var MIGRATION_ELYSIA_VERSION = "0.
|
|
24781
|
+
var MIGRATION_ELYSIA_VERSION = "0.36.0";
|
|
24695
24782
|
|
|
24696
24783
|
// src/migration-policy.ts
|
|
24697
24784
|
var cachedCompilerVersion;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MIGRATION_APP_VERSION = "0.
|
|
1
|
+
export declare const MIGRATION_APP_VERSION = "0.32.0";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MIGRATION_ELYSIA_VERSION = "0.
|
|
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.
|
|
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",
|