@supacloud/compiler 0.45.0 → 0.46.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/DELIVERY.md +20 -0
- package/README.md +16 -12
- package/dist/cli.js +165 -32
- package/dist/config.d.ts +3 -3
- package/dist/delivery-bundle-protocol.d.ts +7 -2
- package/dist/delivery-bundle-worker.js +16 -4
- package/dist/delivery-plan.d.ts +2 -2
- package/dist/effect-governance.d.ts +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +166 -32
- package/dist/inspect.d.ts +5 -0
- package/dist/source-semantics.d.ts +7 -1
- package/dist/type-safety.d.ts +4 -0
- package/dist/types.d.ts +3 -0
- package/package.json +4 -4
package/DELIVERY.md
CHANGED
|
@@ -62,6 +62,26 @@ their kind. HTTP and Job ownership are independent, so one module can contribute
|
|
|
62
62
|
HTTP to `api` and Jobs to `jobs`. Two HTTP targets cannot both own the same module.
|
|
63
63
|
`api` and `jobs` are reserved for their matching workload kinds.
|
|
64
64
|
|
|
65
|
+
`kind: "function"` declares an Edge Function target instead of an app-module
|
|
66
|
+
target. It carries a `function` descriptor (`slug`, `entrypoint`, `verifyJwt`)
|
|
67
|
+
and must not declare `modules`. Function targets own no HTTP routes or Jobs, so
|
|
68
|
+
the plan surfaces them alongside the module-owned targets:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
{
|
|
72
|
+
version: 1,
|
|
73
|
+
targets: [
|
|
74
|
+
{ name: "intake-hooks", kind: "function",
|
|
75
|
+
function: { slug: "fa-intake-hooks", entrypoint: "functions/intake/index.ts", verifyJwt: false } },
|
|
76
|
+
],
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This version only **plans** function targets. The local delivery build still
|
|
81
|
+
rejects them with `delivery-function-build-unsupported`; rendering, upload and
|
|
82
|
+
activation belong to a later delivery phase (see the project deployment
|
|
83
|
+
Function/schedule work). Existing HTTP/Worker configuration is unaffected.
|
|
84
|
+
|
|
65
85
|
No unused explicit target is accepted. Unknown modules, duplicate ownership,
|
|
66
86
|
unresolved imports, cyclic dependencies, duplicate job names, and conflicting
|
|
67
87
|
method/path patterns reject the plan. Parameter-name aliases such as `/:id` and
|
package/README.md
CHANGED
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Enforced Database Governance
|
|
4
4
|
|
|
5
|
-
Existing compiler callers retain `off
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
Existing compiler callers retain `off` as a compatibility migration exception.
|
|
6
|
+
Newly generated `command` starters set
|
|
7
|
+
`databaseGovernance: { mode: "enforce", moduleFile: "db/modules.ts" }`:
|
|
8
|
+
compile/check/build run the existing `@supacloud/db` source-boundary and
|
|
9
|
+
contract-freshness gate, SQL lint and live Catalog reconciliation, including
|
|
10
|
+
incremental cache hits. Missing adoption files, stale contracts, SQL findings or
|
|
11
|
+
Catalog drift block generation even with `writeOnError`; checks never generate
|
|
12
|
+
contracts or migrations.
|
|
11
13
|
The minimal/http/edge starters remain database-free.
|
|
12
14
|
|
|
13
|
-
For
|
|
14
|
-
and explicitly
|
|
15
|
+
For an existing database project, adopt the governed path by declaring the real
|
|
16
|
+
database modules and explicitly setting the following in `supacloud.config.ts`:
|
|
15
17
|
|
|
16
18
|
```ts
|
|
17
19
|
export default {
|
|
@@ -26,10 +28,12 @@ export default {
|
|
|
26
28
|
```
|
|
27
29
|
|
|
28
30
|
`projectRoot` is relative to the configuration directory (or `rootDir` when
|
|
29
|
-
calling `compileProject` / `checkProject` directly).
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
calling `compileProject` / `checkProject` directly). Use a read-only PostgreSQL
|
|
32
|
+
credential in the named environment variable, targeting an explicitly selected
|
|
33
|
+
local or test database. The compiler does not load `.env` files for this worker,
|
|
34
|
+
generate migrations, repair contracts, or apply SQL. Setting `mode: "off"` is
|
|
35
|
+
reserved for an explicit migration exception and should not be used in a new
|
|
36
|
+
database starter.
|
|
33
37
|
|
|
34
38
|
In `enforce` mode, compile/check/dev must pass the existing
|
|
35
39
|
`@supacloud/db` source-boundary and contract-freshness check, SQL lint and live
|
package/dist/cli.js
CHANGED
|
@@ -808,12 +808,15 @@ function validateGraph(graph, options = false) {
|
|
|
808
808
|
if (typeof options === "object" && options.commandCapabilities) {
|
|
809
809
|
const hostCaps = options.commandCapabilities;
|
|
810
810
|
const rpcCaps = command.rpc && Object.hasOwn(hostCaps.rpc ?? {}, command.rpc) ? hostCaps.rpc?.[command.rpc] : undefined;
|
|
811
|
-
if (hostCaps.requirePersistentAdapters && (command.rpc !== undefined && (!rpcCaps?.boundary || rpcCaps.audit !== true || rpcCaps.idempotency !== true) || hostCaps.permission !== true || !command.permission || !command.audit || command.idempotency !== "required" || command.rpc !== undefined && rpcCaps?.boundary === "database" && (rpcCaps.transaction !== true || command.transaction !== "required") || command.rpc === undefined && (hostCaps.audit !== true || hostCaps.idempotency !== true || hostCaps.transaction !== true || command.transaction !== "required"))) {
|
|
811
|
+
if (hostCaps.requirePersistentAdapters && (command.rpc !== undefined && (!rpcCaps?.boundary || rpcCaps.audit !== true || rpcCaps.idempotency !== true) || hostCaps.permission !== true || !command.permission || !command.audit || command.idempotency !== "required" || command.rpc !== undefined && rpcCaps?.boundary === "database" && (rpcCaps.transaction !== true || command.transaction !== "required") || command.rpc === undefined && (hostCaps.audit !== true || hostCaps.idempotency !== true || hostCaps.transaction !== true || command.transaction !== "required") || command.rpc !== undefined && rpcCaps?.boundary === "external" && rpcCaps.reconcile !== true)) {
|
|
812
812
|
error("command-persistence-required", `Command ${command.name} requires an explicit persistent adapter, permission, audit and idempotency policy.`, module.file, module.line, "Register a named database/external adapter, enable permission checks and declare permission, audit and required idempotency on the command.");
|
|
813
813
|
}
|
|
814
814
|
if (rpcCaps?.boundary === "external" && (command.transaction === "required" || rpcCaps.transaction === true)) {
|
|
815
815
|
error("command-external-transaction", `Command ${command.name} calls an external service; use durable intent and reconciliation, not transaction: 'required'.`, module.file, module.line, "Use transaction: 'none' with createExternalCommand; recover with read-only reconciliation. Do not claim a database transaction covers a remote request.");
|
|
816
816
|
}
|
|
817
|
+
if (rpcCaps?.boundary === "external" && rpcCaps.reconcile !== true) {
|
|
818
|
+
error("command-reconcile-required", `Command ${command.name} uses an external adapter without a receipt reconciliation capability.`, module.file, module.line, "Declare reconcile: true only when the adapter can look up the same idempotency key and resolve unknown outcomes without redispatching.");
|
|
819
|
+
}
|
|
817
820
|
const caps = command.rpc ? {
|
|
818
821
|
permission: hostCaps.permission,
|
|
819
822
|
audit: rpcCaps?.audit === true,
|
|
@@ -1230,6 +1233,7 @@ var init_validate = __esm(() => {
|
|
|
1230
1233
|
"invalid-job-timeout": { code: "SC4015", docsUrl: "https://supacloud.dev/errors/SC4015" },
|
|
1231
1234
|
"invalid-job-attempts": { code: "SC4016", docsUrl: "https://supacloud.dev/errors/SC4016" },
|
|
1232
1235
|
"invalid-job-idempotency": { code: "SC4017", docsUrl: "https://supacloud.dev/errors/SC4017" },
|
|
1236
|
+
"job-retry-idempotency-required": { code: "SC4027", docsUrl: "https://supacloud.dev/errors/SC4027" },
|
|
1233
1237
|
"invalid-job-mode": { code: "SC4018", docsUrl: "https://supacloud.dev/errors/SC4018" },
|
|
1234
1238
|
"invalid-job-schema": { code: "SC4019", docsUrl: "https://supacloud.dev/errors/SC4019" },
|
|
1235
1239
|
"command-persistence-required": { code: "SC4020", docsUrl: "https://supacloud.dev/errors/SC4020" },
|
|
@@ -1239,6 +1243,7 @@ var init_validate = __esm(() => {
|
|
|
1239
1243
|
"command-idempotency-unsupported": { code: "SC4024", docsUrl: "https://supacloud.dev/errors/SC4024" },
|
|
1240
1244
|
"command-transaction-rpc-only": { code: "SC4025", docsUrl: "https://supacloud.dev/errors/SC4025" },
|
|
1241
1245
|
"command-transaction-unsupported": { code: "SC4026", docsUrl: "https://supacloud.dev/errors/SC4026" },
|
|
1246
|
+
"command-reconcile-required": { code: "SC4028", docsUrl: "https://supacloud.dev/errors/SC4028" },
|
|
1242
1247
|
"invalid-route-contract": { code: "SC3020", docsUrl: "https://supacloud.dev/errors/SC3020" },
|
|
1243
1248
|
"route-contract-required": { code: "SC3021", docsUrl: "https://supacloud.dev/errors/SC3021" },
|
|
1244
1249
|
"route-contract-unverified": { code: "SC3022", docsUrl: "https://supacloud.dev/errors/SC3022" },
|
|
@@ -4592,7 +4597,7 @@ function createContextPack(graph, subject) {
|
|
|
4592
4597
|
}
|
|
4593
4598
|
}
|
|
4594
4599
|
}
|
|
4595
|
-
const modules = graph.modules.filter((module) => selected.has(module.name));
|
|
4600
|
+
const modules = graph.modules.filter((module) => selected.has(module.name)).map(sanitizeContextModule);
|
|
4596
4601
|
const queryDocuments = graph.graphql?.documents.filter((file) => modules.some((module) => {
|
|
4597
4602
|
const path = relative5(dirname4(module.file), file);
|
|
4598
4603
|
return path !== ".." && !path.startsWith(`..${sep5}`);
|
|
@@ -4634,7 +4639,12 @@ function createContextPack(graph, subject) {
|
|
|
4634
4639
|
singleResult: "async-await",
|
|
4635
4640
|
eventComposition: "rxjs",
|
|
4636
4641
|
angularHost: "@supacloud/app/angular",
|
|
4637
|
-
cancellation: "explicit-owner-signal"
|
|
4642
|
+
cancellation: "explicit-owner-signal",
|
|
4643
|
+
readTransport: "postgrest-supabase-js",
|
|
4644
|
+
writeTransport: "command-rpc",
|
|
4645
|
+
writeRetry: "disabled",
|
|
4646
|
+
externalOutcome: "receipt-reconcile",
|
|
4647
|
+
databaseSource: "drizzle-append-only"
|
|
4638
4648
|
},
|
|
4639
4649
|
relatedModules: {
|
|
4640
4650
|
imports: subjectModule.imports.filter((name) => selected.has(name)),
|
|
@@ -4642,6 +4652,44 @@ function createContextPack(graph, subject) {
|
|
|
4642
4652
|
}
|
|
4643
4653
|
};
|
|
4644
4654
|
}
|
|
4655
|
+
function sanitizeContextModule(module) {
|
|
4656
|
+
const sanitizeAspect = (aspect) => ({
|
|
4657
|
+
...aspect,
|
|
4658
|
+
expression: "[omitted]"
|
|
4659
|
+
});
|
|
4660
|
+
return {
|
|
4661
|
+
...module,
|
|
4662
|
+
providers: module.providers.map((provider) => ({
|
|
4663
|
+
...provider,
|
|
4664
|
+
...provider.useValueExpr === undefined ? {} : { useValueExpr: "[omitted]" },
|
|
4665
|
+
...provider.useFactoryName === undefined ? {} : { useFactoryName: "[omitted]" },
|
|
4666
|
+
...provider.functionalInjects === undefined ? {} : {
|
|
4667
|
+
functionalInjects: provider.functionalInjects.map((inject) => ({ ...inject, expression: "[omitted]" }))
|
|
4668
|
+
}
|
|
4669
|
+
})),
|
|
4670
|
+
...module.aspects === undefined ? {} : { aspects: module.aspects.map(sanitizeAspect) },
|
|
4671
|
+
controllers: module.controllers.map((controller) => ({
|
|
4672
|
+
...controller,
|
|
4673
|
+
...controller.functionalInjects === undefined ? {} : {
|
|
4674
|
+
functionalInjects: controller.functionalInjects.map((inject) => ({ ...inject, expression: "[omitted]" }))
|
|
4675
|
+
},
|
|
4676
|
+
routes: controller.routes.map((route) => ({
|
|
4677
|
+
...route,
|
|
4678
|
+
...route.aspects === undefined ? {} : { aspects: route.aspects.map(sanitizeAspect) }
|
|
4679
|
+
}))
|
|
4680
|
+
})),
|
|
4681
|
+
commands: module.commands.map((command) => ({
|
|
4682
|
+
...command,
|
|
4683
|
+
...command.aspects === undefined ? {} : { aspects: command.aspects.map(sanitizeAspect) }
|
|
4684
|
+
})),
|
|
4685
|
+
...module.jobs === undefined ? {} : {
|
|
4686
|
+
jobs: module.jobs.map((job) => ({
|
|
4687
|
+
...job,
|
|
4688
|
+
...job.aspects === undefined ? {} : { aspects: job.aspects.map(sanitizeAspect) }
|
|
4689
|
+
}))
|
|
4690
|
+
}
|
|
4691
|
+
};
|
|
4692
|
+
}
|
|
4645
4693
|
function resolveContextModule(graph, subject) {
|
|
4646
4694
|
const exact = graph.modules.find((module) => module.name === subject);
|
|
4647
4695
|
if (exact)
|
|
@@ -15683,6 +15731,11 @@ function parseDeliveryOptions(value) {
|
|
|
15683
15731
|
if (value.targets?.some((target) => target.compute && target.isolation === "shared")) {
|
|
15684
15732
|
throw new DeliveryConfigurationError;
|
|
15685
15733
|
}
|
|
15734
|
+
for (const target of value.targets ?? []) {
|
|
15735
|
+
const invalidShape = target.kind === "function" ? target.function === undefined || target.modules !== undefined || target.compute !== undefined : target.modules === undefined || target.function !== undefined;
|
|
15736
|
+
if (invalidShape)
|
|
15737
|
+
throw new DeliveryConfigurationError;
|
|
15738
|
+
}
|
|
15686
15739
|
const migrations = value.build?.migrations ?? [];
|
|
15687
15740
|
try {
|
|
15688
15741
|
validateWorkerExecution(value.execution?.groups ?? []);
|
|
@@ -15699,7 +15752,7 @@ function parseDeliveryPlanResult(value) {
|
|
|
15699
15752
|
throw new Error("Invalid delivery plan result.");
|
|
15700
15753
|
return value;
|
|
15701
15754
|
}
|
|
15702
|
-
var objectOptions, reference2, references, kind2, isolation, DeliveryFilePathSchema, DeliveryOptionsSchema, DeliveryDiagnosticSchema, routeSchema, jobSchema, DeliveryTargetSchema, DeliveryPlanSchema, DeliveryPlanResultSchema, DeliveryConfigurationError;
|
|
15755
|
+
var objectOptions, reference2, references, kind2, isolation, DeliveryFilePathSchema, functionTarget, DeliveryOptionsSchema, DeliveryDiagnosticSchema, routeSchema, jobSchema, DeliveryTargetSchema, DeliveryPlanSchema, DeliveryPlanResultSchema, DeliveryConfigurationError;
|
|
15703
15756
|
var init_delivery_schema = __esm(() => {
|
|
15704
15757
|
init_build();
|
|
15705
15758
|
init_value2();
|
|
@@ -15707,22 +15760,28 @@ var init_delivery_schema = __esm(() => {
|
|
|
15707
15760
|
objectOptions = { additionalProperties: false };
|
|
15708
15761
|
reference2 = String2({ minLength: 1, maxLength: 128, pattern: "^[A-Za-z][A-Za-z0-9_.:/-]*$" });
|
|
15709
15762
|
references = _Array_(reference2, { uniqueItems: true });
|
|
15710
|
-
kind2 = Union([Literal("api"), Literal("jobs"), Literal("webhook")]);
|
|
15763
|
+
kind2 = Union([Literal("api"), Literal("jobs"), Literal("webhook"), Literal("function")]);
|
|
15711
15764
|
isolation = Union([Literal("shared"), Literal("process")]);
|
|
15712
15765
|
DeliveryFilePathSchema = String2({
|
|
15713
15766
|
maxLength: 512,
|
|
15714
15767
|
pattern: "^(?:[A-Za-z0-9_-][A-Za-z0-9_.-]*/)*[A-Za-z0-9_-][A-Za-z0-9_.-]*$"
|
|
15715
15768
|
});
|
|
15769
|
+
functionTarget = _Object_({
|
|
15770
|
+
slug: String2({ pattern: "^[a-z][a-z0-9-]{0,62}$" }),
|
|
15771
|
+
entrypoint: DeliveryFilePathSchema,
|
|
15772
|
+
verifyJwt: Boolean2()
|
|
15773
|
+
}, objectOptions);
|
|
15716
15774
|
DeliveryOptionsSchema = _Object_({
|
|
15717
15775
|
version: Literal(1),
|
|
15718
15776
|
execution: Optional(WorkerExecutionSchema),
|
|
15719
15777
|
targets: Optional(_Array_(_Object_({
|
|
15720
15778
|
name: String2({ pattern: "^[a-z][a-z0-9-]{0,62}$" }),
|
|
15721
15779
|
kind: kind2,
|
|
15722
|
-
modules: _Array_(reference2, { minItems: 1, uniqueItems: true }),
|
|
15780
|
+
modules: Optional(_Array_(reference2, { minItems: 1, uniqueItems: true })),
|
|
15723
15781
|
isolation: Optional(isolation),
|
|
15724
15782
|
capabilities: Optional(references),
|
|
15725
|
-
compute: Optional(ComputeResourcesSchema)
|
|
15783
|
+
compute: Optional(ComputeResourcesSchema),
|
|
15784
|
+
function: Optional(functionTarget)
|
|
15726
15785
|
}, objectOptions))),
|
|
15727
15786
|
runtime: Optional(_Object_({
|
|
15728
15787
|
processIsolation: Boolean2(),
|
|
@@ -15800,6 +15859,7 @@ var init_delivery_schema = __esm(() => {
|
|
|
15800
15859
|
routes: _Array_(routeSchema),
|
|
15801
15860
|
jobs: _Array_(jobSchema),
|
|
15802
15861
|
externalTokens: _Array_(String2()),
|
|
15862
|
+
function: Optional(functionTarget),
|
|
15803
15863
|
requirements: _Object_({
|
|
15804
15864
|
processIsolation: Boolean2(),
|
|
15805
15865
|
durableQueue: Boolean2(),
|
|
@@ -21143,9 +21203,25 @@ import * as ts9 from "@typescript/typescript6";
|
|
|
21143
21203
|
var SOURCE_SEMANTIC_DIAGNOSTIC_CODES = {
|
|
21144
21204
|
"source-unsafe-any": { errorCode: "SC6009", docsUrl: "https://supacloud.dev/errors/SC6009" },
|
|
21145
21205
|
"source-floating-promise": { errorCode: "SC6010", docsUrl: "https://supacloud.dev/errors/SC6010" },
|
|
21146
|
-
"source-non-exhaustive-switch": { errorCode: "SC6011", docsUrl: "https://supacloud.dev/errors/SC6011" }
|
|
21206
|
+
"source-non-exhaustive-switch": { errorCode: "SC6011", docsUrl: "https://supacloud.dev/errors/SC6011" },
|
|
21207
|
+
"source-alternative-library": { errorCode: "SC6012", docsUrl: "https://supacloud.dev/errors/SC6012" }
|
|
21147
21208
|
};
|
|
21148
|
-
|
|
21209
|
+
var DEFAULT_ALTERNATIVE_LIBRARIES = [
|
|
21210
|
+
"@apollo/client",
|
|
21211
|
+
"@urql/core",
|
|
21212
|
+
"graphql-request",
|
|
21213
|
+
"kefir",
|
|
21214
|
+
"most",
|
|
21215
|
+
"@pgflow",
|
|
21216
|
+
"pgflow",
|
|
21217
|
+
"pg_durable",
|
|
21218
|
+
"react-relay",
|
|
21219
|
+
"redux-observable",
|
|
21220
|
+
"urql",
|
|
21221
|
+
"wonka",
|
|
21222
|
+
"xstream"
|
|
21223
|
+
];
|
|
21224
|
+
function scanSourceSemantics(source, checker, file, rootDir, options = {}) {
|
|
21149
21225
|
const diagnostics = [];
|
|
21150
21226
|
const report = (code, node, message) => {
|
|
21151
21227
|
diagnostics.push({
|
|
@@ -21157,6 +21233,16 @@ function scanSourceSemantics(source, checker, file, rootDir) {
|
|
|
21157
21233
|
line: source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1
|
|
21158
21234
|
});
|
|
21159
21235
|
};
|
|
21236
|
+
const allowedLibraries = new Set(options.allowAlternativeLibraries ?? []);
|
|
21237
|
+
for (const statement of source.statements) {
|
|
21238
|
+
if (!ts9.isImportDeclaration(statement) || !ts9.isStringLiteral(statement.moduleSpecifier))
|
|
21239
|
+
continue;
|
|
21240
|
+
const moduleName = statement.moduleSpecifier.text;
|
|
21241
|
+
const alternative = DEFAULT_ALTERNATIVE_LIBRARIES.find((name) => moduleName === name || moduleName.startsWith(`${name}/`));
|
|
21242
|
+
if (alternative && !allowedLibraries.has(alternative) && !allowedLibraries.has(moduleName)) {
|
|
21243
|
+
report("source-alternative-library", statement.moduleSpecifier, `Production source imports alternative library "${moduleName}". Use the governed Supabase client/PostgREST transport and RxJS event composition by default, or allow this package explicitly in typeSafety.allowAlternativeLibraries for a documented boundary.`);
|
|
21244
|
+
}
|
|
21245
|
+
}
|
|
21160
21246
|
const unsafe = (value, target) => {
|
|
21161
21247
|
if (isUnknownBoundary(target, checker) || containsAny(target, checker, new Set, rootDir))
|
|
21162
21248
|
return;
|
|
@@ -21437,7 +21523,7 @@ function scanProductionSource(options) {
|
|
|
21437
21523
|
}
|
|
21438
21524
|
for (const sourceFile of sourceFiles) {
|
|
21439
21525
|
scanSourceFile(sourceFile, checker, rootDir, diagnostics, options.strict ?? false);
|
|
21440
|
-
diagnostics.push(...scanSourceSemantics(sourceFile, checker, normalizeRelative(rootDir, sourceFile.fileName), rootDir));
|
|
21526
|
+
diagnostics.push(...scanSourceSemantics(sourceFile, checker, normalizeRelative(rootDir, sourceFile.fileName), rootDir, options.allowAlternativeLibraries === undefined ? {} : { allowAlternativeLibraries: options.allowAlternativeLibraries }));
|
|
21441
21527
|
diagnostics.push(...scanDrizzleSql(sourceFile, checker, normalizeRelative(rootDir, sourceFile.fileName), options.strict ?? false));
|
|
21442
21528
|
const scanner = ts10.createScanner(ts10.ScriptTarget.Latest, false, sourceFile.languageVariant, sourceFile.text);
|
|
21443
21529
|
for (let kind = scanner.scan();kind !== ts10.SyntaxKind.EndOfFileToken; kind = scanner.scan()) {
|
|
@@ -21699,6 +21785,17 @@ function validateEffectPolicies(graph, options = {}) {
|
|
|
21699
21785
|
}
|
|
21700
21786
|
return diagnostics;
|
|
21701
21787
|
}
|
|
21788
|
+
function validateJobPolicies(graph) {
|
|
21789
|
+
const diagnostics = [];
|
|
21790
|
+
for (const module of graph.modules) {
|
|
21791
|
+
for (const job of module.jobs ?? []) {
|
|
21792
|
+
if (job.maxAttempts !== undefined && job.maxAttempts > 1 && job.idempotency !== "required") {
|
|
21793
|
+
diagnostics.push(diagnostic("job-retry-idempotency-required", `Job ${job.name} (${job.className}) enables ${job.maxAttempts} attempts without an idempotency contract.`, module.file, 'Use idempotency: "required" with a durable receipt, or set maxAttempts: 1.'));
|
|
21794
|
+
}
|
|
21795
|
+
}
|
|
21796
|
+
}
|
|
21797
|
+
return diagnostics;
|
|
21798
|
+
}
|
|
21702
21799
|
|
|
21703
21800
|
// src/database-governance.ts
|
|
21704
21801
|
import { execFile } from "node:child_process";
|
|
@@ -21748,13 +21845,19 @@ async function checkDatabaseGovernance(rootDir, value) {
|
|
|
21748
21845
|
|
|
21749
21846
|
// src/compile.ts
|
|
21750
21847
|
function withDefaultGovernance(options) {
|
|
21751
|
-
return
|
|
21752
|
-
|
|
21753
|
-
|
|
21754
|
-
|
|
21755
|
-
|
|
21756
|
-
|
|
21757
|
-
|
|
21848
|
+
return {
|
|
21849
|
+
...options,
|
|
21850
|
+
requireRouteContracts: options.requireRouteContracts ?? true,
|
|
21851
|
+
disallowControllerDirectDb: options.disallowControllerDirectDb ?? true,
|
|
21852
|
+
detectOrphanModules: options.detectOrphanModules ?? true,
|
|
21853
|
+
...options.commandCapabilities === undefined ? { commandCapabilities: {
|
|
21854
|
+
requirePersistentAdapters: true,
|
|
21855
|
+
permission: true,
|
|
21856
|
+
audit: true,
|
|
21857
|
+
idempotency: true,
|
|
21858
|
+
transaction: true
|
|
21859
|
+
} } : {}
|
|
21860
|
+
};
|
|
21758
21861
|
}
|
|
21759
21862
|
async function renderOptionalGraphql(options) {
|
|
21760
21863
|
return options.graphql ? (await Promise.resolve().then(() => (init_graphql(), exports_graphql))).renderGraphql(options) : { diagnostics: [], files: {} };
|
|
@@ -21766,7 +21869,8 @@ async function compileProject(options) {
|
|
|
21766
21869
|
...graph.diagnostics ?? [],
|
|
21767
21870
|
...validateGraph(graph, options),
|
|
21768
21871
|
...validateClientSchemaBoundaries(graph, options),
|
|
21769
|
-
...validateEffectPolicies(graph, options.effect)
|
|
21872
|
+
...validateEffectPolicies(graph, options.effect),
|
|
21873
|
+
...validateJobPolicies(graph)
|
|
21770
21874
|
];
|
|
21771
21875
|
const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance);
|
|
21772
21876
|
diagnostics.push(...databaseDiagnostics);
|
|
@@ -21836,7 +21940,8 @@ async function checkProject(options) {
|
|
|
21836
21940
|
...graph.diagnostics ?? [],
|
|
21837
21941
|
...validateGraph(graph, options),
|
|
21838
21942
|
...validateClientSchemaBoundaries(graph, options),
|
|
21839
|
-
...validateEffectPolicies(graph, options.effect)
|
|
21943
|
+
...validateEffectPolicies(graph, options.effect),
|
|
21944
|
+
...validateJobPolicies(graph)
|
|
21840
21945
|
];
|
|
21841
21946
|
const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance);
|
|
21842
21947
|
diagnostics.push(...databaseDiagnostics);
|
|
@@ -21916,7 +22021,8 @@ function resolveTypeSafety(options) {
|
|
|
21916
22021
|
return {
|
|
21917
22022
|
noAnyInGenerated: true,
|
|
21918
22023
|
scanProductionSource: true,
|
|
21919
|
-
...options.typeSafety?.exclude ? { exclude: options.typeSafety.exclude } : {}
|
|
22024
|
+
...options.typeSafety?.exclude ? { exclude: options.typeSafety.exclude } : {},
|
|
22025
|
+
...options.typeSafety?.allowAlternativeLibraries ? { allowAlternativeLibraries: options.typeSafety.allowAlternativeLibraries } : {}
|
|
21920
22026
|
};
|
|
21921
22027
|
}
|
|
21922
22028
|
|
|
@@ -22650,11 +22756,13 @@ var DEFAULT_SUPACLOUD_CONFIG = {
|
|
|
22650
22756
|
outDir: "generated",
|
|
22651
22757
|
include: ["**/*.module.ts", "**/*.ts"],
|
|
22652
22758
|
strict: true,
|
|
22653
|
-
requireRouteContracts:
|
|
22759
|
+
requireRouteContracts: true,
|
|
22654
22760
|
generateClient: true,
|
|
22655
22761
|
generateOpenApi: true,
|
|
22656
22762
|
generatePermissions: true,
|
|
22657
22763
|
treeShakeUnusedProviders: true,
|
|
22764
|
+
disallowControllerDirectDb: true,
|
|
22765
|
+
detectOrphanModules: true,
|
|
22658
22766
|
commandCapabilities: {
|
|
22659
22767
|
requirePersistentAdapters: true,
|
|
22660
22768
|
permission: true,
|
|
@@ -22709,9 +22817,9 @@ function validateGovernanceConfig(config) {
|
|
|
22709
22817
|
if (!isRecord(adapters))
|
|
22710
22818
|
throw new Error("commandCapabilities.rpc must contain named adapters.");
|
|
22711
22819
|
for (const [name, adapter] of Object.entries(adapters)) {
|
|
22712
|
-
if (!name.trim() || !isRecord(adapter) || Object.keys(adapter).some((key) => !["audit", "idempotency", "transaction", "boundary"].includes(key)))
|
|
22820
|
+
if (!name.trim() || !isRecord(adapter) || Object.keys(adapter).some((key) => !["audit", "idempotency", "reconcile", "transaction", "boundary"].includes(key)))
|
|
22713
22821
|
throw new Error("Invalid command adapter.");
|
|
22714
|
-
for (const key of ["audit", "idempotency", "transaction"]) {
|
|
22822
|
+
for (const key of ["audit", "idempotency", "reconcile", "transaction"]) {
|
|
22715
22823
|
if (adapter[key] !== undefined && typeof adapter[key] !== "boolean")
|
|
22716
22824
|
throw new Error("Invalid command adapter capability.");
|
|
22717
22825
|
}
|
|
@@ -22736,8 +22844,8 @@ function validateGovernanceConfig(config) {
|
|
|
22736
22844
|
}
|
|
22737
22845
|
if (config.typeSafety !== undefined) {
|
|
22738
22846
|
const rules = config.typeSafety;
|
|
22739
|
-
if (!isRecord(rules) || Object.keys(rules).some((key) => !["scanProductionSource", "noAnyInGenerated", "exclude"].includes(key)) || rules["scanProductionSource"] !== undefined && typeof rules["scanProductionSource"] !== "boolean" || rules["noAnyInGenerated"] !== undefined && typeof rules["noAnyInGenerated"] !== "boolean" || rules["exclude"] !== undefined && !isStrings(rules["exclude"])) {
|
|
22740
|
-
throw new Error("typeSafety accepts boolean scanProductionSource/noAnyInGenerated and
|
|
22847
|
+
if (!isRecord(rules) || Object.keys(rules).some((key) => !["scanProductionSource", "noAnyInGenerated", "exclude", "allowAlternativeLibraries"].includes(key)) || rules["scanProductionSource"] !== undefined && typeof rules["scanProductionSource"] !== "boolean" || rules["noAnyInGenerated"] !== undefined && typeof rules["noAnyInGenerated"] !== "boolean" || rules["exclude"] !== undefined && !isStrings(rules["exclude"]) || rules["allowAlternativeLibraries"] !== undefined && !isStrings(rules["allowAlternativeLibraries"])) {
|
|
22848
|
+
throw new Error("typeSafety accepts boolean scanProductionSource/noAnyInGenerated and string arrays exclude/allowAlternativeLibraries.");
|
|
22741
22849
|
}
|
|
22742
22850
|
}
|
|
22743
22851
|
for (const key of ["allowRouteCommandBindings", "disallowControllerDirectDb", "detectOrphanModules"]) {
|
|
@@ -22796,8 +22904,8 @@ function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
|
|
|
22796
22904
|
}
|
|
22797
22905
|
},
|
|
22798
22906
|
...resolved.allowRouteCommandBindings === undefined ? {} : { allowRouteCommandBindings: resolved.allowRouteCommandBindings },
|
|
22799
|
-
|
|
22800
|
-
|
|
22907
|
+
disallowControllerDirectDb: resolved.disallowControllerDirectDb ?? DEFAULT_SUPACLOUD_CONFIG.disallowControllerDirectDb,
|
|
22908
|
+
detectOrphanModules: resolved.detectOrphanModules ?? DEFAULT_SUPACLOUD_CONFIG.detectOrphanModules,
|
|
22801
22909
|
effect: resolved.effect === undefined ? { ...DEFAULT_SUPACLOUD_CONFIG.effect } : { ...DEFAULT_SUPACLOUD_CONFIG.effect, ...resolved.effect },
|
|
22802
22910
|
treeShakeUnusedProviders: resolved.treeShakeUnusedProviders ?? DEFAULT_SUPACLOUD_CONFIG.treeShakeUnusedProviders,
|
|
22803
22911
|
...resolved.graphql === undefined || resolved.graphql === false ? {} : { graphql: {
|
|
@@ -22897,7 +23005,7 @@ function diagnostic2(error) {
|
|
|
22897
23005
|
...error.suggestion === undefined ? {} : { suggestion: error.suggestion }
|
|
22898
23006
|
};
|
|
22899
23007
|
}
|
|
22900
|
-
function createDeliveryPlan(graph, input) {
|
|
23008
|
+
function createDeliveryPlan(graph, input, validation) {
|
|
22901
23009
|
let options;
|
|
22902
23010
|
try {
|
|
22903
23011
|
options = parseDeliveryOptions(input);
|
|
@@ -22913,7 +23021,7 @@ function createDeliveryPlan(graph, input) {
|
|
|
22913
23021
|
}
|
|
22914
23022
|
const diagnostics = [
|
|
22915
23023
|
...(graph.diagnostics ?? []).map(diagnostic2),
|
|
22916
|
-
...validateGraph(graph, { strict: true }).map(diagnostic2)
|
|
23024
|
+
...validateGraph(graph, validation === undefined ? { strict: true } : { ...validation, strict: true }).map(diagnostic2)
|
|
22917
23025
|
];
|
|
22918
23026
|
const fail = (code, message, suggestion) => {
|
|
22919
23027
|
diagnostics.push({ severity: "error", code, message, suggestion });
|
|
@@ -22931,7 +23039,7 @@ function createDeliveryPlan(graph, input) {
|
|
|
22931
23039
|
if (target.name === "api" && target.kind !== "api" || target.name === "jobs" && target.kind !== "jobs") {
|
|
22932
23040
|
fail("delivery-reserved-target", `"${target.name}" is reserved for its matching workload kind.`, "Rename the target or use the matching kind.");
|
|
22933
23041
|
}
|
|
22934
|
-
for (const name of sorted(target.modules)) {
|
|
23042
|
+
for (const name of sorted(target.modules ?? [])) {
|
|
22935
23043
|
if (!modules.has(name)) {
|
|
22936
23044
|
fail("delivery-unknown-module", `Target "${target.name}" refers to unknown module "${name}".`, "Select a module name from the compiler graph.");
|
|
22937
23045
|
continue;
|
|
@@ -22945,6 +23053,23 @@ function createDeliveryPlan(graph, input) {
|
|
|
22945
23053
|
}
|
|
22946
23054
|
}
|
|
22947
23055
|
const targets = new Map;
|
|
23056
|
+
for (const declaration of declarations.values()) {
|
|
23057
|
+
if (declaration.kind !== "function" || declaration.function === undefined)
|
|
23058
|
+
continue;
|
|
23059
|
+
targets.set(declaration.name, {
|
|
23060
|
+
name: declaration.name,
|
|
23061
|
+
kind: "function",
|
|
23062
|
+
isolation: "shared",
|
|
23063
|
+
roots: [],
|
|
23064
|
+
modules: [],
|
|
23065
|
+
routes: [],
|
|
23066
|
+
jobs: [],
|
|
23067
|
+
externalTokens: [],
|
|
23068
|
+
function: declaration.function,
|
|
23069
|
+
requirements: { processIsolation: false, durableQueue: false, capabilities: sorted(declaration.capabilities ?? []) },
|
|
23070
|
+
runtimeStatus: options.runtime === undefined ? "unchecked" : "declared-compatible"
|
|
23071
|
+
});
|
|
23072
|
+
}
|
|
22948
23073
|
function getTarget(name, fallback) {
|
|
22949
23074
|
const existing = targets.get(name);
|
|
22950
23075
|
if (existing)
|
|
@@ -23110,7 +23235,7 @@ async function planDeliveryProject(options, delivery) {
|
|
|
23110
23235
|
return createDeliveryPlan({ modules: [], externalTokens: [] }, delivery);
|
|
23111
23236
|
}
|
|
23112
23237
|
const checked = await checkProject(options);
|
|
23113
|
-
return createDeliveryPlan({ ...checked.graph, diagnostics: checked.diagnostics }, delivery);
|
|
23238
|
+
return createDeliveryPlan({ ...checked.graph, diagnostics: checked.diagnostics }, delivery, options);
|
|
23114
23239
|
}
|
|
23115
23240
|
function formatDeliveryPlan(result) {
|
|
23116
23241
|
const lines = result.ok ? [
|
|
@@ -23811,9 +23936,17 @@ async function buildDeliveryProject(options, delivery) {
|
|
|
23811
23936
|
phase = "compiler-checks";
|
|
23812
23937
|
const sources = await sourceInventory(sourceRoot, generatedRoot);
|
|
23813
23938
|
const checked = await checkProject(options);
|
|
23814
|
-
const planned = createDeliveryPlan({ ...checked.graph, diagnostics: checked.diagnostics }, settings);
|
|
23939
|
+
const planned = createDeliveryPlan({ ...checked.graph, diagnostics: checked.diagnostics }, settings, options);
|
|
23815
23940
|
if (!planned.ok)
|
|
23816
23941
|
return failed(planned.diagnostics);
|
|
23942
|
+
if (planned.plan.targets.some((target) => target.kind === "function")) {
|
|
23943
|
+
return failed([{
|
|
23944
|
+
severity: "error",
|
|
23945
|
+
code: "delivery-function-build-unsupported",
|
|
23946
|
+
message: "Function target rendering is not implemented yet; the delivery build only produces HTTP and Worker application objects.",
|
|
23947
|
+
suggestion: "Declare HTTP/Worker application targets for now; Function and schedule delivery is tracked separately."
|
|
23948
|
+
}]);
|
|
23949
|
+
}
|
|
23817
23950
|
if (options.graphql && checked.mismatches.some((mismatch) => mismatch.startsWith("graphql"))) {
|
|
23818
23951
|
throw new Error("Refresh GraphQL artifacts with compile before building independent targets.");
|
|
23819
23952
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface SupaCloudConfig {
|
|
|
25
25
|
commandCapabilities?: CommandExecutionCapabilities;
|
|
26
26
|
treeShakeUnusedProviders?: boolean;
|
|
27
27
|
}
|
|
28
|
-
export declare const DEFAULT_SUPACLOUD_CONFIG: Required<Omit<SupaCloudConfig, "include" | "moduleBoundaryPreset" | "moduleBoundaries" | "typeSafety" | "delivery" | "allowRouteCommandBindings" | "
|
|
28
|
+
export declare const DEFAULT_SUPACLOUD_CONFIG: Required<Omit<SupaCloudConfig, "include" | "moduleBoundaryPreset" | "moduleBoundaries" | "typeSafety" | "delivery" | "allowRouteCommandBindings" | "openApi" | "effect">> & {
|
|
29
29
|
include: string[];
|
|
30
30
|
moduleBoundaryPreset: ModuleBoundaryPresetName;
|
|
31
31
|
effect: NonNullable<CompileOptions["effect"]>;
|
|
@@ -47,8 +47,8 @@ export declare function resolveSupacloudConfig(config?: SupaCloudConfig, cwd?: s
|
|
|
47
47
|
typeSafety?: NonNullable<CompileOptions["typeSafety"]>;
|
|
48
48
|
databaseGovernance?: NonNullable<CompileOptions["databaseGovernance"]>;
|
|
49
49
|
allowRouteCommandBindings?: boolean;
|
|
50
|
-
disallowControllerDirectDb
|
|
51
|
-
detectOrphanModules
|
|
50
|
+
disallowControllerDirectDb: boolean;
|
|
51
|
+
detectOrphanModules: boolean;
|
|
52
52
|
effect?: NonNullable<CompileOptions["effect"]>;
|
|
53
53
|
treeShakeUnusedProviders: boolean;
|
|
54
54
|
graphql?: GraphqlOptions;
|
|
@@ -43,14 +43,19 @@ declare const requestSchema: Type.TObject<{
|
|
|
43
43
|
}>>;
|
|
44
44
|
targets: Type.TOptional<Type.TArray<Type.TObject<{
|
|
45
45
|
name: Type.TString;
|
|
46
|
-
kind: Type.TUnion<[Type.TLiteral<"api">, Type.TLiteral<"jobs">, Type.TLiteral<"webhook">]>;
|
|
47
|
-
modules: Type.TArray<Type.TString
|
|
46
|
+
kind: Type.TUnion<[Type.TLiteral<"api">, Type.TLiteral<"jobs">, Type.TLiteral<"webhook">, Type.TLiteral<"function">]>;
|
|
47
|
+
modules: Type.TOptional<Type.TArray<Type.TString>>;
|
|
48
48
|
isolation: Type.TOptional<Type.TUnion<[Type.TLiteral<"shared">, Type.TLiteral<"process">]>>;
|
|
49
49
|
capabilities: Type.TOptional<Type.TArray<Type.TString>>;
|
|
50
50
|
compute: Type.TOptional<Type.TObject<{
|
|
51
51
|
cpuLimit: Type.TNumber;
|
|
52
52
|
memoryLimitMiB: Type.TInteger;
|
|
53
53
|
}>>;
|
|
54
|
+
function: Type.TOptional<Type.TObject<{
|
|
55
|
+
slug: Type.TString;
|
|
56
|
+
entrypoint: Type.TString;
|
|
57
|
+
verifyJwt: Type.TBoolean;
|
|
58
|
+
}>>;
|
|
54
59
|
}>>>;
|
|
55
60
|
runtime: Type.TOptional<Type.TObject<{
|
|
56
61
|
processIsolation: Type.TBoolean;
|
|
@@ -10064,6 +10064,11 @@ function parseDeliveryOptions(value) {
|
|
|
10064
10064
|
if (value.targets?.some((target) => target.compute && target.isolation === "shared")) {
|
|
10065
10065
|
throw new DeliveryConfigurationError;
|
|
10066
10066
|
}
|
|
10067
|
+
for (const target of value.targets ?? []) {
|
|
10068
|
+
const invalidShape = target.kind === "function" ? target.function === undefined || target.modules !== undefined || target.compute !== undefined : target.modules === undefined || target.function !== undefined;
|
|
10069
|
+
if (invalidShape)
|
|
10070
|
+
throw new DeliveryConfigurationError;
|
|
10071
|
+
}
|
|
10067
10072
|
const migrations = value.build?.migrations ?? [];
|
|
10068
10073
|
try {
|
|
10069
10074
|
validateWorkerExecution(value.execution?.groups ?? []);
|
|
@@ -10080,7 +10085,7 @@ function parseDeliveryPlanResult(value) {
|
|
|
10080
10085
|
throw new Error("Invalid delivery plan result.");
|
|
10081
10086
|
return value;
|
|
10082
10087
|
}
|
|
10083
|
-
var objectOptions, reference, references, kind, isolation, DeliveryFilePathSchema, DeliveryOptionsSchema, DeliveryDiagnosticSchema, routeSchema, jobSchema, DeliveryTargetSchema, DeliveryPlanSchema, DeliveryPlanResultSchema, DeliveryConfigurationError;
|
|
10088
|
+
var objectOptions, reference, references, kind, isolation, DeliveryFilePathSchema, functionTarget, DeliveryOptionsSchema, DeliveryDiagnosticSchema, routeSchema, jobSchema, DeliveryTargetSchema, DeliveryPlanSchema, DeliveryPlanResultSchema, DeliveryConfigurationError;
|
|
10084
10089
|
var init_delivery_schema = __esm(() => {
|
|
10085
10090
|
init_build2();
|
|
10086
10091
|
init_value2();
|
|
@@ -10088,22 +10093,28 @@ var init_delivery_schema = __esm(() => {
|
|
|
10088
10093
|
objectOptions = { additionalProperties: false };
|
|
10089
10094
|
reference = String2({ minLength: 1, maxLength: 128, pattern: "^[A-Za-z][A-Za-z0-9_.:/-]*$" });
|
|
10090
10095
|
references = _Array_(reference, { uniqueItems: true });
|
|
10091
|
-
kind = Union([Literal("api"), Literal("jobs"), Literal("webhook")]);
|
|
10096
|
+
kind = Union([Literal("api"), Literal("jobs"), Literal("webhook"), Literal("function")]);
|
|
10092
10097
|
isolation = Union([Literal("shared"), Literal("process")]);
|
|
10093
10098
|
DeliveryFilePathSchema = String2({
|
|
10094
10099
|
maxLength: 512,
|
|
10095
10100
|
pattern: "^(?:[A-Za-z0-9_-][A-Za-z0-9_.-]*/)*[A-Za-z0-9_-][A-Za-z0-9_.-]*$"
|
|
10096
10101
|
});
|
|
10102
|
+
functionTarget = _Object_({
|
|
10103
|
+
slug: String2({ pattern: "^[a-z][a-z0-9-]{0,62}$" }),
|
|
10104
|
+
entrypoint: DeliveryFilePathSchema,
|
|
10105
|
+
verifyJwt: Boolean2()
|
|
10106
|
+
}, objectOptions);
|
|
10097
10107
|
DeliveryOptionsSchema = _Object_({
|
|
10098
10108
|
version: Literal(1),
|
|
10099
10109
|
execution: Optional(WorkerExecutionSchema),
|
|
10100
10110
|
targets: Optional(_Array_(_Object_({
|
|
10101
10111
|
name: String2({ pattern: "^[a-z][a-z0-9-]{0,62}$" }),
|
|
10102
10112
|
kind,
|
|
10103
|
-
modules: _Array_(reference, { minItems: 1, uniqueItems: true }),
|
|
10113
|
+
modules: Optional(_Array_(reference, { minItems: 1, uniqueItems: true })),
|
|
10104
10114
|
isolation: Optional(isolation),
|
|
10105
10115
|
capabilities: Optional(references),
|
|
10106
|
-
compute: Optional(ComputeResourcesSchema)
|
|
10116
|
+
compute: Optional(ComputeResourcesSchema),
|
|
10117
|
+
function: Optional(functionTarget)
|
|
10107
10118
|
}, objectOptions))),
|
|
10108
10119
|
runtime: Optional(_Object_({
|
|
10109
10120
|
processIsolation: Boolean2(),
|
|
@@ -10181,6 +10192,7 @@ var init_delivery_schema = __esm(() => {
|
|
|
10181
10192
|
routes: _Array_(routeSchema),
|
|
10182
10193
|
jobs: _Array_(jobSchema),
|
|
10183
10194
|
externalTokens: _Array_(String2()),
|
|
10195
|
+
function: Optional(functionTarget),
|
|
10184
10196
|
requirements: _Object_({
|
|
10185
10197
|
processIsolation: Boolean2(),
|
|
10186
10198
|
durableQueue: Boolean2(),
|
package/dist/delivery-plan.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type DeliveryPlanResult } from "./delivery-schema";
|
|
2
|
-
import type { ApplicationGraph, CompileOptions } from "./types";
|
|
2
|
+
import type { ApplicationGraph, CompileOptions, ValidateOptions } from "./types";
|
|
3
3
|
/** Trusted compiler graph in, validated topology preview out. No file or remote writes. */
|
|
4
|
-
export declare function createDeliveryPlan(graph: ApplicationGraph, input?: unknown): DeliveryPlanResult;
|
|
4
|
+
export declare function createDeliveryPlan(graph: ApplicationGraph, input?: unknown, validation?: ValidateOptions): DeliveryPlanResult;
|
|
5
5
|
/** Reuse all compiler gates without requiring generated files to already exist. */
|
|
6
6
|
export declare function planDeliveryProject(options: CompileOptions, delivery?: unknown): Promise<DeliveryPlanResult>;
|
|
7
7
|
export declare function formatDeliveryPlan(result: DeliveryPlanResult): string;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { ApplicationGraph, Diagnostic, EffectCompilerOptions } from "./types";
|
|
2
2
|
/** Validates the static Effect contract without inspecting Effect runtime internals. */
|
|
3
3
|
export declare function validateEffectPolicies(graph: ApplicationGraph, options?: EffectCompilerOptions): Diagnostic[];
|
|
4
|
+
/** A retrying job must replay the same request without duplicating a write. */
|
|
5
|
+
export declare function validateJobPolicies(graph: ApplicationGraph): Diagnostic[];
|
package/dist/index.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ export { camelName } from "./util";
|
|
|
54
54
|
export { ANGULAR_ENTERPRISE_RULES, CLEAN_ARCHITECTURE_RULES, MODULAR_MONOLITH_RULES, MODULE_BOUNDARY_PROFILES, getModuleBoundaryPreset, getModuleBoundaryProfile, resolveModuleBoundaries, } from "./profiles";
|
|
55
55
|
export type { ApplicationGraph, AspectRefNode, CachedModuleEntry, CheckProjectResult, CommandExecutionCapabilities, CommandNode, CompileOptions, DatabaseGovernanceOptions, CompileResult, CompileStats, ControllerNode, DependencyGraphCache, DependencyGraphIndex, Diagnostic, DiagnosticFix, EffectCompilerOptions, EffectErrorMappingNode, EffectRetryPolicy, EffectRouteNode, ModuleBoundaryPresetName, ModuleBoundaryProfile, ModuleBoundaryRule, ModuleNode, JobNode, ProviderKind, ProviderNode, QueryNode, RouteNode, Scope, TokenKind, TypeSafetyOptions, ValidateOptions, WatchEvent, WatchHandle, WatchOptions, FeatureSpecNode, FeatureTransitionNode, GraphqlOptions, GraphqlContractSummary, OpenApiOptions, OpenApiSecurityScheme, OpenApiServer, } from "./types";
|
|
56
56
|
export { inspectRouteContracts, validateRouteContracts } from "./route-contracts";
|
|
57
|
-
export { validateEffectPolicies } from "./effect-governance";
|
|
57
|
+
export { validateEffectPolicies, validateJobPolicies } from "./effect-governance";
|
|
58
58
|
export { pullGraphqlSchema } from "./graphql-schema";
|
|
59
59
|
export type { PullGraphqlSchemaOptions } from "./graphql-schema";
|
|
60
60
|
export { checkGraphqlCompatibility, createRoleSnapshotReport, diffGraphqlSchemas, graphqlFeatureMatrix, introspectionQuery, readGraphqlSchema, schemaFromSource, } from "./graphql-governance";
|
package/dist/index.js
CHANGED
|
@@ -807,12 +807,15 @@ function validateGraph(graph, options = false) {
|
|
|
807
807
|
if (typeof options === "object" && options.commandCapabilities) {
|
|
808
808
|
const hostCaps = options.commandCapabilities;
|
|
809
809
|
const rpcCaps = command.rpc && Object.hasOwn(hostCaps.rpc ?? {}, command.rpc) ? hostCaps.rpc?.[command.rpc] : undefined;
|
|
810
|
-
if (hostCaps.requirePersistentAdapters && (command.rpc !== undefined && (!rpcCaps?.boundary || rpcCaps.audit !== true || rpcCaps.idempotency !== true) || hostCaps.permission !== true || !command.permission || !command.audit || command.idempotency !== "required" || command.rpc !== undefined && rpcCaps?.boundary === "database" && (rpcCaps.transaction !== true || command.transaction !== "required") || command.rpc === undefined && (hostCaps.audit !== true || hostCaps.idempotency !== true || hostCaps.transaction !== true || command.transaction !== "required"))) {
|
|
810
|
+
if (hostCaps.requirePersistentAdapters && (command.rpc !== undefined && (!rpcCaps?.boundary || rpcCaps.audit !== true || rpcCaps.idempotency !== true) || hostCaps.permission !== true || !command.permission || !command.audit || command.idempotency !== "required" || command.rpc !== undefined && rpcCaps?.boundary === "database" && (rpcCaps.transaction !== true || command.transaction !== "required") || command.rpc === undefined && (hostCaps.audit !== true || hostCaps.idempotency !== true || hostCaps.transaction !== true || command.transaction !== "required") || command.rpc !== undefined && rpcCaps?.boundary === "external" && rpcCaps.reconcile !== true)) {
|
|
811
811
|
error("command-persistence-required", `Command ${command.name} requires an explicit persistent adapter, permission, audit and idempotency policy.`, module.file, module.line, "Register a named database/external adapter, enable permission checks and declare permission, audit and required idempotency on the command.");
|
|
812
812
|
}
|
|
813
813
|
if (rpcCaps?.boundary === "external" && (command.transaction === "required" || rpcCaps.transaction === true)) {
|
|
814
814
|
error("command-external-transaction", `Command ${command.name} calls an external service; use durable intent and reconciliation, not transaction: 'required'.`, module.file, module.line, "Use transaction: 'none' with createExternalCommand; recover with read-only reconciliation. Do not claim a database transaction covers a remote request.");
|
|
815
815
|
}
|
|
816
|
+
if (rpcCaps?.boundary === "external" && rpcCaps.reconcile !== true) {
|
|
817
|
+
error("command-reconcile-required", `Command ${command.name} uses an external adapter without a receipt reconciliation capability.`, module.file, module.line, "Declare reconcile: true only when the adapter can look up the same idempotency key and resolve unknown outcomes without redispatching.");
|
|
818
|
+
}
|
|
816
819
|
const caps = command.rpc ? {
|
|
817
820
|
permission: hostCaps.permission,
|
|
818
821
|
audit: rpcCaps?.audit === true,
|
|
@@ -1229,6 +1232,7 @@ var init_validate = __esm(() => {
|
|
|
1229
1232
|
"invalid-job-timeout": { code: "SC4015", docsUrl: "https://supacloud.dev/errors/SC4015" },
|
|
1230
1233
|
"invalid-job-attempts": { code: "SC4016", docsUrl: "https://supacloud.dev/errors/SC4016" },
|
|
1231
1234
|
"invalid-job-idempotency": { code: "SC4017", docsUrl: "https://supacloud.dev/errors/SC4017" },
|
|
1235
|
+
"job-retry-idempotency-required": { code: "SC4027", docsUrl: "https://supacloud.dev/errors/SC4027" },
|
|
1232
1236
|
"invalid-job-mode": { code: "SC4018", docsUrl: "https://supacloud.dev/errors/SC4018" },
|
|
1233
1237
|
"invalid-job-schema": { code: "SC4019", docsUrl: "https://supacloud.dev/errors/SC4019" },
|
|
1234
1238
|
"command-persistence-required": { code: "SC4020", docsUrl: "https://supacloud.dev/errors/SC4020" },
|
|
@@ -1238,6 +1242,7 @@ var init_validate = __esm(() => {
|
|
|
1238
1242
|
"command-idempotency-unsupported": { code: "SC4024", docsUrl: "https://supacloud.dev/errors/SC4024" },
|
|
1239
1243
|
"command-transaction-rpc-only": { code: "SC4025", docsUrl: "https://supacloud.dev/errors/SC4025" },
|
|
1240
1244
|
"command-transaction-unsupported": { code: "SC4026", docsUrl: "https://supacloud.dev/errors/SC4026" },
|
|
1245
|
+
"command-reconcile-required": { code: "SC4028", docsUrl: "https://supacloud.dev/errors/SC4028" },
|
|
1241
1246
|
"invalid-route-contract": { code: "SC3020", docsUrl: "https://supacloud.dev/errors/SC3020" },
|
|
1242
1247
|
"route-contract-required": { code: "SC3021", docsUrl: "https://supacloud.dev/errors/SC3021" },
|
|
1243
1248
|
"route-contract-unverified": { code: "SC3022", docsUrl: "https://supacloud.dev/errors/SC3022" },
|
|
@@ -14557,6 +14562,11 @@ function parseDeliveryOptions(value) {
|
|
|
14557
14562
|
if (value.targets?.some((target) => target.compute && target.isolation === "shared")) {
|
|
14558
14563
|
throw new DeliveryConfigurationError;
|
|
14559
14564
|
}
|
|
14565
|
+
for (const target of value.targets ?? []) {
|
|
14566
|
+
const invalidShape = target.kind === "function" ? target.function === undefined || target.modules !== undefined || target.compute !== undefined : target.modules === undefined || target.function !== undefined;
|
|
14567
|
+
if (invalidShape)
|
|
14568
|
+
throw new DeliveryConfigurationError;
|
|
14569
|
+
}
|
|
14560
14570
|
const migrations = value.build?.migrations ?? [];
|
|
14561
14571
|
try {
|
|
14562
14572
|
validateWorkerExecution(value.execution?.groups ?? []);
|
|
@@ -14573,7 +14583,7 @@ function parseDeliveryPlanResult(value) {
|
|
|
14573
14583
|
throw new Error("Invalid delivery plan result.");
|
|
14574
14584
|
return value;
|
|
14575
14585
|
}
|
|
14576
|
-
var objectOptions, reference, references, kind, isolation, DeliveryFilePathSchema, DeliveryOptionsSchema, DeliveryDiagnosticSchema, routeSchema, jobSchema, DeliveryTargetSchema, DeliveryPlanSchema, DeliveryPlanResultSchema, DeliveryConfigurationError;
|
|
14586
|
+
var objectOptions, reference, references, kind, isolation, DeliveryFilePathSchema, functionTarget, DeliveryOptionsSchema, DeliveryDiagnosticSchema, routeSchema, jobSchema, DeliveryTargetSchema, DeliveryPlanSchema, DeliveryPlanResultSchema, DeliveryConfigurationError;
|
|
14577
14587
|
var init_delivery_schema = __esm(() => {
|
|
14578
14588
|
init_build();
|
|
14579
14589
|
init_value2();
|
|
@@ -14581,22 +14591,28 @@ var init_delivery_schema = __esm(() => {
|
|
|
14581
14591
|
objectOptions = { additionalProperties: false };
|
|
14582
14592
|
reference = String2({ minLength: 1, maxLength: 128, pattern: "^[A-Za-z][A-Za-z0-9_.:/-]*$" });
|
|
14583
14593
|
references = _Array_(reference, { uniqueItems: true });
|
|
14584
|
-
kind = Union([Literal("api"), Literal("jobs"), Literal("webhook")]);
|
|
14594
|
+
kind = Union([Literal("api"), Literal("jobs"), Literal("webhook"), Literal("function")]);
|
|
14585
14595
|
isolation = Union([Literal("shared"), Literal("process")]);
|
|
14586
14596
|
DeliveryFilePathSchema = String2({
|
|
14587
14597
|
maxLength: 512,
|
|
14588
14598
|
pattern: "^(?:[A-Za-z0-9_-][A-Za-z0-9_.-]*/)*[A-Za-z0-9_-][A-Za-z0-9_.-]*$"
|
|
14589
14599
|
});
|
|
14600
|
+
functionTarget = _Object_({
|
|
14601
|
+
slug: String2({ pattern: "^[a-z][a-z0-9-]{0,62}$" }),
|
|
14602
|
+
entrypoint: DeliveryFilePathSchema,
|
|
14603
|
+
verifyJwt: Boolean2()
|
|
14604
|
+
}, objectOptions);
|
|
14590
14605
|
DeliveryOptionsSchema = _Object_({
|
|
14591
14606
|
version: Literal(1),
|
|
14592
14607
|
execution: Optional(WorkerExecutionSchema),
|
|
14593
14608
|
targets: Optional(_Array_(_Object_({
|
|
14594
14609
|
name: String2({ pattern: "^[a-z][a-z0-9-]{0,62}$" }),
|
|
14595
14610
|
kind,
|
|
14596
|
-
modules: _Array_(reference, { minItems: 1, uniqueItems: true }),
|
|
14611
|
+
modules: Optional(_Array_(reference, { minItems: 1, uniqueItems: true })),
|
|
14597
14612
|
isolation: Optional(isolation),
|
|
14598
14613
|
capabilities: Optional(references),
|
|
14599
|
-
compute: Optional(ComputeResourcesSchema)
|
|
14614
|
+
compute: Optional(ComputeResourcesSchema),
|
|
14615
|
+
function: Optional(functionTarget)
|
|
14600
14616
|
}, objectOptions))),
|
|
14601
14617
|
runtime: Optional(_Object_({
|
|
14602
14618
|
processIsolation: Boolean2(),
|
|
@@ -14674,6 +14690,7 @@ var init_delivery_schema = __esm(() => {
|
|
|
14674
14690
|
routes: _Array_(routeSchema),
|
|
14675
14691
|
jobs: _Array_(jobSchema),
|
|
14676
14692
|
externalTokens: _Array_(String2()),
|
|
14693
|
+
function: Optional(functionTarget),
|
|
14677
14694
|
requirements: _Object_({
|
|
14678
14695
|
processIsolation: Boolean2(),
|
|
14679
14696
|
durableQueue: Boolean2(),
|
|
@@ -15059,7 +15076,7 @@ function createContextPack(graph, subject) {
|
|
|
15059
15076
|
}
|
|
15060
15077
|
}
|
|
15061
15078
|
}
|
|
15062
|
-
const modules = graph.modules.filter((module) => selected.has(module.name));
|
|
15079
|
+
const modules = graph.modules.filter((module) => selected.has(module.name)).map(sanitizeContextModule);
|
|
15063
15080
|
const queryDocuments = graph.graphql?.documents.filter((file) => modules.some((module) => {
|
|
15064
15081
|
const path = relative6(dirname5(module.file), file);
|
|
15065
15082
|
return path !== ".." && !path.startsWith(`..${sep6}`);
|
|
@@ -15101,7 +15118,12 @@ function createContextPack(graph, subject) {
|
|
|
15101
15118
|
singleResult: "async-await",
|
|
15102
15119
|
eventComposition: "rxjs",
|
|
15103
15120
|
angularHost: "@supacloud/app/angular",
|
|
15104
|
-
cancellation: "explicit-owner-signal"
|
|
15121
|
+
cancellation: "explicit-owner-signal",
|
|
15122
|
+
readTransport: "postgrest-supabase-js",
|
|
15123
|
+
writeTransport: "command-rpc",
|
|
15124
|
+
writeRetry: "disabled",
|
|
15125
|
+
externalOutcome: "receipt-reconcile",
|
|
15126
|
+
databaseSource: "drizzle-append-only"
|
|
15105
15127
|
},
|
|
15106
15128
|
relatedModules: {
|
|
15107
15129
|
imports: subjectModule.imports.filter((name) => selected.has(name)),
|
|
@@ -15109,6 +15131,44 @@ function createContextPack(graph, subject) {
|
|
|
15109
15131
|
}
|
|
15110
15132
|
};
|
|
15111
15133
|
}
|
|
15134
|
+
function sanitizeContextModule(module) {
|
|
15135
|
+
const sanitizeAspect = (aspect) => ({
|
|
15136
|
+
...aspect,
|
|
15137
|
+
expression: "[omitted]"
|
|
15138
|
+
});
|
|
15139
|
+
return {
|
|
15140
|
+
...module,
|
|
15141
|
+
providers: module.providers.map((provider) => ({
|
|
15142
|
+
...provider,
|
|
15143
|
+
...provider.useValueExpr === undefined ? {} : { useValueExpr: "[omitted]" },
|
|
15144
|
+
...provider.useFactoryName === undefined ? {} : { useFactoryName: "[omitted]" },
|
|
15145
|
+
...provider.functionalInjects === undefined ? {} : {
|
|
15146
|
+
functionalInjects: provider.functionalInjects.map((inject) => ({ ...inject, expression: "[omitted]" }))
|
|
15147
|
+
}
|
|
15148
|
+
})),
|
|
15149
|
+
...module.aspects === undefined ? {} : { aspects: module.aspects.map(sanitizeAspect) },
|
|
15150
|
+
controllers: module.controllers.map((controller) => ({
|
|
15151
|
+
...controller,
|
|
15152
|
+
...controller.functionalInjects === undefined ? {} : {
|
|
15153
|
+
functionalInjects: controller.functionalInjects.map((inject) => ({ ...inject, expression: "[omitted]" }))
|
|
15154
|
+
},
|
|
15155
|
+
routes: controller.routes.map((route) => ({
|
|
15156
|
+
...route,
|
|
15157
|
+
...route.aspects === undefined ? {} : { aspects: route.aspects.map(sanitizeAspect) }
|
|
15158
|
+
}))
|
|
15159
|
+
})),
|
|
15160
|
+
commands: module.commands.map((command) => ({
|
|
15161
|
+
...command,
|
|
15162
|
+
...command.aspects === undefined ? {} : { aspects: command.aspects.map(sanitizeAspect) }
|
|
15163
|
+
})),
|
|
15164
|
+
...module.jobs === undefined ? {} : {
|
|
15165
|
+
jobs: module.jobs.map((job) => ({
|
|
15166
|
+
...job,
|
|
15167
|
+
...job.aspects === undefined ? {} : { aspects: job.aspects.map(sanitizeAspect) }
|
|
15168
|
+
}))
|
|
15169
|
+
}
|
|
15170
|
+
};
|
|
15171
|
+
}
|
|
15112
15172
|
function resolveContextModule(graph, subject) {
|
|
15113
15173
|
const exact = graph.modules.find((module) => module.name === subject);
|
|
15114
15174
|
if (exact)
|
|
@@ -21104,9 +21164,25 @@ import * as ts9 from "@typescript/typescript6";
|
|
|
21104
21164
|
var SOURCE_SEMANTIC_DIAGNOSTIC_CODES = {
|
|
21105
21165
|
"source-unsafe-any": { errorCode: "SC6009", docsUrl: "https://supacloud.dev/errors/SC6009" },
|
|
21106
21166
|
"source-floating-promise": { errorCode: "SC6010", docsUrl: "https://supacloud.dev/errors/SC6010" },
|
|
21107
|
-
"source-non-exhaustive-switch": { errorCode: "SC6011", docsUrl: "https://supacloud.dev/errors/SC6011" }
|
|
21167
|
+
"source-non-exhaustive-switch": { errorCode: "SC6011", docsUrl: "https://supacloud.dev/errors/SC6011" },
|
|
21168
|
+
"source-alternative-library": { errorCode: "SC6012", docsUrl: "https://supacloud.dev/errors/SC6012" }
|
|
21108
21169
|
};
|
|
21109
|
-
|
|
21170
|
+
var DEFAULT_ALTERNATIVE_LIBRARIES = [
|
|
21171
|
+
"@apollo/client",
|
|
21172
|
+
"@urql/core",
|
|
21173
|
+
"graphql-request",
|
|
21174
|
+
"kefir",
|
|
21175
|
+
"most",
|
|
21176
|
+
"@pgflow",
|
|
21177
|
+
"pgflow",
|
|
21178
|
+
"pg_durable",
|
|
21179
|
+
"react-relay",
|
|
21180
|
+
"redux-observable",
|
|
21181
|
+
"urql",
|
|
21182
|
+
"wonka",
|
|
21183
|
+
"xstream"
|
|
21184
|
+
];
|
|
21185
|
+
function scanSourceSemantics(source, checker, file, rootDir, options = {}) {
|
|
21110
21186
|
const diagnostics = [];
|
|
21111
21187
|
const report = (code, node, message) => {
|
|
21112
21188
|
diagnostics.push({
|
|
@@ -21118,6 +21194,16 @@ function scanSourceSemantics(source, checker, file, rootDir) {
|
|
|
21118
21194
|
line: source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1
|
|
21119
21195
|
});
|
|
21120
21196
|
};
|
|
21197
|
+
const allowedLibraries = new Set(options.allowAlternativeLibraries ?? []);
|
|
21198
|
+
for (const statement of source.statements) {
|
|
21199
|
+
if (!ts9.isImportDeclaration(statement) || !ts9.isStringLiteral(statement.moduleSpecifier))
|
|
21200
|
+
continue;
|
|
21201
|
+
const moduleName = statement.moduleSpecifier.text;
|
|
21202
|
+
const alternative = DEFAULT_ALTERNATIVE_LIBRARIES.find((name) => moduleName === name || moduleName.startsWith(`${name}/`));
|
|
21203
|
+
if (alternative && !allowedLibraries.has(alternative) && !allowedLibraries.has(moduleName)) {
|
|
21204
|
+
report("source-alternative-library", statement.moduleSpecifier, `Production source imports alternative library "${moduleName}". Use the governed Supabase client/PostgREST transport and RxJS event composition by default, or allow this package explicitly in typeSafety.allowAlternativeLibraries for a documented boundary.`);
|
|
21205
|
+
}
|
|
21206
|
+
}
|
|
21121
21207
|
const unsafe = (value, target) => {
|
|
21122
21208
|
if (isUnknownBoundary(target, checker) || containsAny(target, checker, new Set, rootDir))
|
|
21123
21209
|
return;
|
|
@@ -21398,7 +21484,7 @@ function scanProductionSource(options) {
|
|
|
21398
21484
|
}
|
|
21399
21485
|
for (const sourceFile of sourceFiles) {
|
|
21400
21486
|
scanSourceFile(sourceFile, checker, rootDir, diagnostics, options.strict ?? false);
|
|
21401
|
-
diagnostics.push(...scanSourceSemantics(sourceFile, checker, normalizeRelative(rootDir, sourceFile.fileName), rootDir));
|
|
21487
|
+
diagnostics.push(...scanSourceSemantics(sourceFile, checker, normalizeRelative(rootDir, sourceFile.fileName), rootDir, options.allowAlternativeLibraries === undefined ? {} : { allowAlternativeLibraries: options.allowAlternativeLibraries }));
|
|
21402
21488
|
diagnostics.push(...scanDrizzleSql(sourceFile, checker, normalizeRelative(rootDir, sourceFile.fileName), options.strict ?? false));
|
|
21403
21489
|
const scanner = ts10.createScanner(ts10.ScriptTarget.Latest, false, sourceFile.languageVariant, sourceFile.text);
|
|
21404
21490
|
for (let kind = scanner.scan();kind !== ts10.SyntaxKind.EndOfFileToken; kind = scanner.scan()) {
|
|
@@ -21660,6 +21746,17 @@ function validateEffectPolicies(graph, options = {}) {
|
|
|
21660
21746
|
}
|
|
21661
21747
|
return diagnostics;
|
|
21662
21748
|
}
|
|
21749
|
+
function validateJobPolicies(graph) {
|
|
21750
|
+
const diagnostics = [];
|
|
21751
|
+
for (const module of graph.modules) {
|
|
21752
|
+
for (const job of module.jobs ?? []) {
|
|
21753
|
+
if (job.maxAttempts !== undefined && job.maxAttempts > 1 && job.idempotency !== "required") {
|
|
21754
|
+
diagnostics.push(diagnostic("job-retry-idempotency-required", `Job ${job.name} (${job.className}) enables ${job.maxAttempts} attempts without an idempotency contract.`, module.file, 'Use idempotency: "required" with a durable receipt, or set maxAttempts: 1.'));
|
|
21755
|
+
}
|
|
21756
|
+
}
|
|
21757
|
+
}
|
|
21758
|
+
return diagnostics;
|
|
21759
|
+
}
|
|
21663
21760
|
|
|
21664
21761
|
// src/database-governance.ts
|
|
21665
21762
|
import { execFile } from "node:child_process";
|
|
@@ -21709,13 +21806,19 @@ async function checkDatabaseGovernance(rootDir, value) {
|
|
|
21709
21806
|
|
|
21710
21807
|
// src/compile.ts
|
|
21711
21808
|
function withDefaultGovernance(options) {
|
|
21712
|
-
return
|
|
21713
|
-
|
|
21714
|
-
|
|
21715
|
-
|
|
21716
|
-
|
|
21717
|
-
|
|
21718
|
-
|
|
21809
|
+
return {
|
|
21810
|
+
...options,
|
|
21811
|
+
requireRouteContracts: options.requireRouteContracts ?? true,
|
|
21812
|
+
disallowControllerDirectDb: options.disallowControllerDirectDb ?? true,
|
|
21813
|
+
detectOrphanModules: options.detectOrphanModules ?? true,
|
|
21814
|
+
...options.commandCapabilities === undefined ? { commandCapabilities: {
|
|
21815
|
+
requirePersistentAdapters: true,
|
|
21816
|
+
permission: true,
|
|
21817
|
+
audit: true,
|
|
21818
|
+
idempotency: true,
|
|
21819
|
+
transaction: true
|
|
21820
|
+
} } : {}
|
|
21821
|
+
};
|
|
21719
21822
|
}
|
|
21720
21823
|
async function renderOptionalGraphql(options) {
|
|
21721
21824
|
return options.graphql ? (await Promise.resolve().then(() => (init_graphql(), exports_graphql))).renderGraphql(options) : { diagnostics: [], files: {} };
|
|
@@ -21727,7 +21830,8 @@ async function compileProject(options) {
|
|
|
21727
21830
|
...graph.diagnostics ?? [],
|
|
21728
21831
|
...validateGraph(graph, options),
|
|
21729
21832
|
...validateClientSchemaBoundaries(graph, options),
|
|
21730
|
-
...validateEffectPolicies(graph, options.effect)
|
|
21833
|
+
...validateEffectPolicies(graph, options.effect),
|
|
21834
|
+
...validateJobPolicies(graph)
|
|
21731
21835
|
];
|
|
21732
21836
|
const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance);
|
|
21733
21837
|
diagnostics.push(...databaseDiagnostics);
|
|
@@ -21797,7 +21901,8 @@ async function checkProject(options) {
|
|
|
21797
21901
|
...graph.diagnostics ?? [],
|
|
21798
21902
|
...validateGraph(graph, options),
|
|
21799
21903
|
...validateClientSchemaBoundaries(graph, options),
|
|
21800
|
-
...validateEffectPolicies(graph, options.effect)
|
|
21904
|
+
...validateEffectPolicies(graph, options.effect),
|
|
21905
|
+
...validateJobPolicies(graph)
|
|
21801
21906
|
];
|
|
21802
21907
|
const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance);
|
|
21803
21908
|
diagnostics.push(...databaseDiagnostics);
|
|
@@ -21877,7 +21982,8 @@ function resolveTypeSafety(options) {
|
|
|
21877
21982
|
return {
|
|
21878
21983
|
noAnyInGenerated: true,
|
|
21879
21984
|
scanProductionSource: true,
|
|
21880
|
-
...options.typeSafety?.exclude ? { exclude: options.typeSafety.exclude } : {}
|
|
21985
|
+
...options.typeSafety?.exclude ? { exclude: options.typeSafety.exclude } : {},
|
|
21986
|
+
...options.typeSafety?.allowAlternativeLibraries ? { allowAlternativeLibraries: options.typeSafety.allowAlternativeLibraries } : {}
|
|
21881
21987
|
};
|
|
21882
21988
|
}
|
|
21883
21989
|
|
|
@@ -21898,7 +22004,7 @@ function diagnostic2(error) {
|
|
|
21898
22004
|
...error.suggestion === undefined ? {} : { suggestion: error.suggestion }
|
|
21899
22005
|
};
|
|
21900
22006
|
}
|
|
21901
|
-
function createDeliveryPlan(graph, input) {
|
|
22007
|
+
function createDeliveryPlan(graph, input, validation) {
|
|
21902
22008
|
let options;
|
|
21903
22009
|
try {
|
|
21904
22010
|
options = parseDeliveryOptions(input);
|
|
@@ -21914,7 +22020,7 @@ function createDeliveryPlan(graph, input) {
|
|
|
21914
22020
|
}
|
|
21915
22021
|
const diagnostics = [
|
|
21916
22022
|
...(graph.diagnostics ?? []).map(diagnostic2),
|
|
21917
|
-
...validateGraph(graph, { strict: true }).map(diagnostic2)
|
|
22023
|
+
...validateGraph(graph, validation === undefined ? { strict: true } : { ...validation, strict: true }).map(diagnostic2)
|
|
21918
22024
|
];
|
|
21919
22025
|
const fail = (code, message, suggestion) => {
|
|
21920
22026
|
diagnostics.push({ severity: "error", code, message, suggestion });
|
|
@@ -21932,7 +22038,7 @@ function createDeliveryPlan(graph, input) {
|
|
|
21932
22038
|
if (target.name === "api" && target.kind !== "api" || target.name === "jobs" && target.kind !== "jobs") {
|
|
21933
22039
|
fail("delivery-reserved-target", `"${target.name}" is reserved for its matching workload kind.`, "Rename the target or use the matching kind.");
|
|
21934
22040
|
}
|
|
21935
|
-
for (const name of sorted(target.modules)) {
|
|
22041
|
+
for (const name of sorted(target.modules ?? [])) {
|
|
21936
22042
|
if (!modules.has(name)) {
|
|
21937
22043
|
fail("delivery-unknown-module", `Target "${target.name}" refers to unknown module "${name}".`, "Select a module name from the compiler graph.");
|
|
21938
22044
|
continue;
|
|
@@ -21946,6 +22052,23 @@ function createDeliveryPlan(graph, input) {
|
|
|
21946
22052
|
}
|
|
21947
22053
|
}
|
|
21948
22054
|
const targets = new Map;
|
|
22055
|
+
for (const declaration of declarations.values()) {
|
|
22056
|
+
if (declaration.kind !== "function" || declaration.function === undefined)
|
|
22057
|
+
continue;
|
|
22058
|
+
targets.set(declaration.name, {
|
|
22059
|
+
name: declaration.name,
|
|
22060
|
+
kind: "function",
|
|
22061
|
+
isolation: "shared",
|
|
22062
|
+
roots: [],
|
|
22063
|
+
modules: [],
|
|
22064
|
+
routes: [],
|
|
22065
|
+
jobs: [],
|
|
22066
|
+
externalTokens: [],
|
|
22067
|
+
function: declaration.function,
|
|
22068
|
+
requirements: { processIsolation: false, durableQueue: false, capabilities: sorted(declaration.capabilities ?? []) },
|
|
22069
|
+
runtimeStatus: options.runtime === undefined ? "unchecked" : "declared-compatible"
|
|
22070
|
+
});
|
|
22071
|
+
}
|
|
21949
22072
|
function getTarget(name, fallback) {
|
|
21950
22073
|
const existing = targets.get(name);
|
|
21951
22074
|
if (existing)
|
|
@@ -22111,7 +22234,7 @@ async function planDeliveryProject(options, delivery) {
|
|
|
22111
22234
|
return createDeliveryPlan({ modules: [], externalTokens: [] }, delivery);
|
|
22112
22235
|
}
|
|
22113
22236
|
const checked = await checkProject(options);
|
|
22114
|
-
return createDeliveryPlan({ ...checked.graph, diagnostics: checked.diagnostics }, delivery);
|
|
22237
|
+
return createDeliveryPlan({ ...checked.graph, diagnostics: checked.diagnostics }, delivery, options);
|
|
22115
22238
|
}
|
|
22116
22239
|
function formatDeliveryPlan(result) {
|
|
22117
22240
|
const lines = result.ok ? [
|
|
@@ -22811,9 +22934,17 @@ async function buildDeliveryProject(options, delivery) {
|
|
|
22811
22934
|
phase = "compiler-checks";
|
|
22812
22935
|
const sources = await sourceInventory(sourceRoot, generatedRoot);
|
|
22813
22936
|
const checked = await checkProject(options);
|
|
22814
|
-
const planned = createDeliveryPlan({ ...checked.graph, diagnostics: checked.diagnostics }, settings);
|
|
22937
|
+
const planned = createDeliveryPlan({ ...checked.graph, diagnostics: checked.diagnostics }, settings, options);
|
|
22815
22938
|
if (!planned.ok)
|
|
22816
22939
|
return failed(planned.diagnostics);
|
|
22940
|
+
if (planned.plan.targets.some((target) => target.kind === "function")) {
|
|
22941
|
+
return failed([{
|
|
22942
|
+
severity: "error",
|
|
22943
|
+
code: "delivery-function-build-unsupported",
|
|
22944
|
+
message: "Function target rendering is not implemented yet; the delivery build only produces HTTP and Worker application objects.",
|
|
22945
|
+
suggestion: "Declare HTTP/Worker application targets for now; Function and schedule delivery is tracked separately."
|
|
22946
|
+
}]);
|
|
22947
|
+
}
|
|
22817
22948
|
if (options.graphql && checked.mismatches.some((mismatch) => mismatch.startsWith("graphql"))) {
|
|
22818
22949
|
throw new Error("Refresh GraphQL artifacts with compile before building independent targets.");
|
|
22819
22950
|
}
|
|
@@ -24932,11 +25063,13 @@ var DEFAULT_SUPACLOUD_CONFIG = {
|
|
|
24932
25063
|
outDir: "generated",
|
|
24933
25064
|
include: ["**/*.module.ts", "**/*.ts"],
|
|
24934
25065
|
strict: true,
|
|
24935
|
-
requireRouteContracts:
|
|
25066
|
+
requireRouteContracts: true,
|
|
24936
25067
|
generateClient: true,
|
|
24937
25068
|
generateOpenApi: true,
|
|
24938
25069
|
generatePermissions: true,
|
|
24939
25070
|
treeShakeUnusedProviders: true,
|
|
25071
|
+
disallowControllerDirectDb: true,
|
|
25072
|
+
detectOrphanModules: true,
|
|
24940
25073
|
commandCapabilities: {
|
|
24941
25074
|
requirePersistentAdapters: true,
|
|
24942
25075
|
permission: true,
|
|
@@ -24991,9 +25124,9 @@ function validateGovernanceConfig(config) {
|
|
|
24991
25124
|
if (!isRecord(adapters))
|
|
24992
25125
|
throw new Error("commandCapabilities.rpc must contain named adapters.");
|
|
24993
25126
|
for (const [name, adapter] of Object.entries(adapters)) {
|
|
24994
|
-
if (!name.trim() || !isRecord(adapter) || Object.keys(adapter).some((key) => !["audit", "idempotency", "transaction", "boundary"].includes(key)))
|
|
25127
|
+
if (!name.trim() || !isRecord(adapter) || Object.keys(adapter).some((key) => !["audit", "idempotency", "reconcile", "transaction", "boundary"].includes(key)))
|
|
24995
25128
|
throw new Error("Invalid command adapter.");
|
|
24996
|
-
for (const key of ["audit", "idempotency", "transaction"]) {
|
|
25129
|
+
for (const key of ["audit", "idempotency", "reconcile", "transaction"]) {
|
|
24997
25130
|
if (adapter[key] !== undefined && typeof adapter[key] !== "boolean")
|
|
24998
25131
|
throw new Error("Invalid command adapter capability.");
|
|
24999
25132
|
}
|
|
@@ -25018,8 +25151,8 @@ function validateGovernanceConfig(config) {
|
|
|
25018
25151
|
}
|
|
25019
25152
|
if (config.typeSafety !== undefined) {
|
|
25020
25153
|
const rules = config.typeSafety;
|
|
25021
|
-
if (!isRecord(rules) || Object.keys(rules).some((key) => !["scanProductionSource", "noAnyInGenerated", "exclude"].includes(key)) || rules["scanProductionSource"] !== undefined && typeof rules["scanProductionSource"] !== "boolean" || rules["noAnyInGenerated"] !== undefined && typeof rules["noAnyInGenerated"] !== "boolean" || rules["exclude"] !== undefined && !isStrings(rules["exclude"])) {
|
|
25022
|
-
throw new Error("typeSafety accepts boolean scanProductionSource/noAnyInGenerated and
|
|
25154
|
+
if (!isRecord(rules) || Object.keys(rules).some((key) => !["scanProductionSource", "noAnyInGenerated", "exclude", "allowAlternativeLibraries"].includes(key)) || rules["scanProductionSource"] !== undefined && typeof rules["scanProductionSource"] !== "boolean" || rules["noAnyInGenerated"] !== undefined && typeof rules["noAnyInGenerated"] !== "boolean" || rules["exclude"] !== undefined && !isStrings(rules["exclude"]) || rules["allowAlternativeLibraries"] !== undefined && !isStrings(rules["allowAlternativeLibraries"])) {
|
|
25155
|
+
throw new Error("typeSafety accepts boolean scanProductionSource/noAnyInGenerated and string arrays exclude/allowAlternativeLibraries.");
|
|
25023
25156
|
}
|
|
25024
25157
|
}
|
|
25025
25158
|
for (const key of ["allowRouteCommandBindings", "disallowControllerDirectDb", "detectOrphanModules"]) {
|
|
@@ -25078,8 +25211,8 @@ function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
|
|
|
25078
25211
|
}
|
|
25079
25212
|
},
|
|
25080
25213
|
...resolved.allowRouteCommandBindings === undefined ? {} : { allowRouteCommandBindings: resolved.allowRouteCommandBindings },
|
|
25081
|
-
|
|
25082
|
-
|
|
25214
|
+
disallowControllerDirectDb: resolved.disallowControllerDirectDb ?? DEFAULT_SUPACLOUD_CONFIG.disallowControllerDirectDb,
|
|
25215
|
+
detectOrphanModules: resolved.detectOrphanModules ?? DEFAULT_SUPACLOUD_CONFIG.detectOrphanModules,
|
|
25083
25216
|
effect: resolved.effect === undefined ? { ...DEFAULT_SUPACLOUD_CONFIG.effect } : { ...DEFAULT_SUPACLOUD_CONFIG.effect, ...resolved.effect },
|
|
25084
25217
|
treeShakeUnusedProviders: resolved.treeShakeUnusedProviders ?? DEFAULT_SUPACLOUD_CONFIG.treeShakeUnusedProviders,
|
|
25085
25218
|
...resolved.graphql === undefined || resolved.graphql === false ? {} : { graphql: {
|
|
@@ -25494,6 +25627,7 @@ export {
|
|
|
25494
25627
|
validateEffectPolicies,
|
|
25495
25628
|
validateFeatureSpec,
|
|
25496
25629
|
validateGraph,
|
|
25630
|
+
validateJobPolicies,
|
|
25497
25631
|
validateRouteContracts,
|
|
25498
25632
|
watchProject,
|
|
25499
25633
|
writeOpenApiJson
|
package/dist/inspect.d.ts
CHANGED
|
@@ -22,6 +22,11 @@ export interface ContextPack {
|
|
|
22
22
|
eventComposition: "rxjs";
|
|
23
23
|
angularHost: "@supacloud/app/angular";
|
|
24
24
|
cancellation: "explicit-owner-signal";
|
|
25
|
+
readTransport: "postgrest-supabase-js";
|
|
26
|
+
writeTransport: "command-rpc";
|
|
27
|
+
writeRetry: "disabled";
|
|
28
|
+
externalOutcome: "receipt-reconcile";
|
|
29
|
+
databaseSource: "drizzle-append-only";
|
|
25
30
|
};
|
|
26
31
|
relatedModules: {
|
|
27
32
|
importedBy: string[];
|
|
@@ -13,6 +13,12 @@ export declare const SOURCE_SEMANTIC_DIAGNOSTIC_CODES: {
|
|
|
13
13
|
readonly errorCode: "SC6011";
|
|
14
14
|
readonly docsUrl: "https://supacloud.dev/errors/SC6011";
|
|
15
15
|
};
|
|
16
|
+
readonly "source-alternative-library": {
|
|
17
|
+
readonly errorCode: "SC6012";
|
|
18
|
+
readonly docsUrl: "https://supacloud.dev/errors/SC6012";
|
|
19
|
+
};
|
|
16
20
|
};
|
|
17
21
|
/** Inspect values at use sites, including types resolved through imported declarations. */
|
|
18
|
-
export declare function scanSourceSemantics(source: ts.SourceFile, checker: ts.TypeChecker, file: string, rootDir: string
|
|
22
|
+
export declare function scanSourceSemantics(source: ts.SourceFile, checker: ts.TypeChecker, file: string, rootDir: string, options?: {
|
|
23
|
+
allowAlternativeLibraries?: readonly string[];
|
|
24
|
+
}): Diagnostic[];
|
package/dist/type-safety.d.ts
CHANGED
|
@@ -36,6 +36,10 @@ export declare const TYPE_SAFETY_DIAGNOSTIC_CODES: {
|
|
|
36
36
|
readonly errorCode: "SC6011";
|
|
37
37
|
readonly docsUrl: "https://supacloud.dev/errors/SC6011";
|
|
38
38
|
};
|
|
39
|
+
readonly "source-alternative-library": {
|
|
40
|
+
readonly errorCode: "SC6012";
|
|
41
|
+
readonly docsUrl: "https://supacloud.dev/errors/SC6012";
|
|
42
|
+
};
|
|
39
43
|
readonly "sql-result-assertion": {
|
|
40
44
|
readonly errorCode: "SC6007";
|
|
41
45
|
readonly docsUrl: "https://supacloud.dev/errors/SC6007";
|
package/dist/types.d.ts
CHANGED
|
@@ -536,6 +536,8 @@ export interface TypeSafetyOptions {
|
|
|
536
536
|
scanProductionSource?: boolean;
|
|
537
537
|
/** Additional relative glob patterns excluded from the production-source scan. */
|
|
538
538
|
exclude?: string[];
|
|
539
|
+
/** Explicitly approved alternatives to the default client/stream libraries. */
|
|
540
|
+
allowAlternativeLibraries?: string[];
|
|
539
541
|
}
|
|
540
542
|
export interface DatabaseGovernanceOptions {
|
|
541
543
|
/** Off for existing callers; check validates sources offline, enforce also checks SQL and Catalog. */
|
|
@@ -590,6 +592,7 @@ export interface CommandExecutionCapabilities {
|
|
|
590
592
|
rpc?: Record<string, {
|
|
591
593
|
audit?: boolean;
|
|
592
594
|
idempotency?: boolean;
|
|
595
|
+
reconcile?: boolean;
|
|
593
596
|
transaction?: boolean;
|
|
594
597
|
boundary?: "database" | "external";
|
|
595
598
|
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.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",
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
"directory": "packages/compiler"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@supacloud/db": "0.
|
|
61
|
-
"@supacloud/delivery": "0.
|
|
60
|
+
"@supacloud/db": "0.17.0",
|
|
61
|
+
"@supacloud/delivery": "0.17.0",
|
|
62
62
|
"typebox": "1.3.36",
|
|
63
63
|
"@graphql-codegen/core": "^6.2.2",
|
|
64
64
|
"@graphql-codegen/typed-document-node": "^7.1.1",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@supabase/supabase-js": "2.117.3",
|
|
77
77
|
"drizzle-orm": "1.0.0-rc.5-169397b",
|
|
78
|
-
"@types/bun": "^1.4.
|
|
78
|
+
"@types/bun": "^1.4.3",
|
|
79
79
|
"typescript": "^7.0.2"
|
|
80
80
|
}
|
|
81
81
|
}
|