@supacloud/compiler 0.22.0 → 0.24.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 +130 -8
- package/dist/config.d.ts +1 -1
- package/dist/contract-manifest.d.ts +51 -0
- package/dist/generate.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +132 -8
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -78,6 +78,83 @@ function findClosestMatch(target, candidates) {
|
|
|
78
78
|
}
|
|
79
79
|
var REQUEST_CONTEXT_TOKEN_NAME = "supacloud.request-context", JOB_CONTEXT_TOKEN_NAME = "supacloud.job-context";
|
|
80
80
|
|
|
81
|
+
// src/contract-manifest.ts
|
|
82
|
+
function buildContractManifest(graph, artifacts) {
|
|
83
|
+
const commands = [];
|
|
84
|
+
const queries = [];
|
|
85
|
+
const routes = [];
|
|
86
|
+
const permissions = new Set;
|
|
87
|
+
const rpc = [];
|
|
88
|
+
const events = [];
|
|
89
|
+
const fixtures = new Set;
|
|
90
|
+
for (const module of graph.modules) {
|
|
91
|
+
for (const command of module.commands) {
|
|
92
|
+
if (command.permission)
|
|
93
|
+
permissions.add(command.permission);
|
|
94
|
+
if (command.rpc)
|
|
95
|
+
rpc.push({ command: command.name, adapter: command.rpc });
|
|
96
|
+
if (command.audit)
|
|
97
|
+
events.push({ name: command.audit, source: "command.audit", command: command.name });
|
|
98
|
+
commands.push({
|
|
99
|
+
name: command.name,
|
|
100
|
+
className: command.className,
|
|
101
|
+
module: module.name,
|
|
102
|
+
...command.permission === undefined ? {} : { permission: command.permission },
|
|
103
|
+
...command.rpc === undefined ? {} : { rpc: command.rpc },
|
|
104
|
+
transaction: command.transaction,
|
|
105
|
+
idempotency: command.idempotency,
|
|
106
|
+
...command.audit === undefined ? {} : { auditEvent: command.audit }
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
for (const query of module.queries) {
|
|
110
|
+
queries.push({ name: query.name, className: query.className, module: module.name });
|
|
111
|
+
}
|
|
112
|
+
for (const controller of module.controllers) {
|
|
113
|
+
for (const route of controller.routes) {
|
|
114
|
+
const command = route.command ? module.commands.find((candidate) => candidate.className === route.command) : undefined;
|
|
115
|
+
if (command?.permission)
|
|
116
|
+
permissions.add(command.permission);
|
|
117
|
+
if (route.contract?.evidence)
|
|
118
|
+
fixtures.add(route.contract.evidence);
|
|
119
|
+
routes.push({
|
|
120
|
+
method: route.method,
|
|
121
|
+
path: joinRoutePaths(controller.path, route.path),
|
|
122
|
+
module: module.name,
|
|
123
|
+
controller: controller.className,
|
|
124
|
+
handler: route.handler,
|
|
125
|
+
...route.command === undefined ? {} : { command: route.command },
|
|
126
|
+
...command?.permission === undefined ? {} : { permission: command.permission },
|
|
127
|
+
requestSchemas: Object.fromEntries(["body", "params", "query", "headers", "cookie"].flatMap((key) => route[key] === undefined ? [] : [[key, route[key]]])),
|
|
128
|
+
...route.response === undefined ? {} : { responseSchema: route.response },
|
|
129
|
+
...route.contract?.evidence === undefined ? {} : { evidence: route.contract.evidence }
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
commands.sort((left, right) => left.name.localeCompare(right.name));
|
|
135
|
+
queries.sort((left, right) => left.name.localeCompare(right.name));
|
|
136
|
+
routes.sort((left, right) => `${left.method} ${left.path}`.localeCompare(`${right.method} ${right.path}`));
|
|
137
|
+
rpc.sort((left, right) => left.command.localeCompare(right.command));
|
|
138
|
+
events.sort((left, right) => left.name.localeCompare(right.name));
|
|
139
|
+
return {
|
|
140
|
+
version: 1,
|
|
141
|
+
commands,
|
|
142
|
+
queries,
|
|
143
|
+
routes,
|
|
144
|
+
permissions: [...permissions].sort(),
|
|
145
|
+
rpc,
|
|
146
|
+
events,
|
|
147
|
+
fixtures: [...fixtures].sort(),
|
|
148
|
+
artifacts: {
|
|
149
|
+
...artifacts,
|
|
150
|
+
sdk: "generated-client",
|
|
151
|
+
openapiDocument: "generated",
|
|
152
|
+
permissionManifest: "generated"
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
var init_contract_manifest = () => {};
|
|
157
|
+
|
|
81
158
|
// src/generate.ts
|
|
82
159
|
import { createHash as createHash4 } from "node:crypto";
|
|
83
160
|
import { access, mkdir, rename, unlink, writeFile } from "node:fs/promises";
|
|
@@ -197,17 +274,38 @@ function renderApplication(graph, options) {
|
|
|
197
274
|
""
|
|
198
275
|
].join(`
|
|
199
276
|
`);
|
|
277
|
+
const commandGovernance = graph.modules.flatMap((module) => module.commands.map((command) => ({
|
|
278
|
+
module: module.name,
|
|
279
|
+
className: command.className,
|
|
280
|
+
name: command.name,
|
|
281
|
+
permission: command.permission ?? null,
|
|
282
|
+
rpc: command.rpc ?? null,
|
|
283
|
+
transaction: command.transaction ?? null,
|
|
284
|
+
audit: command.audit ?? null,
|
|
285
|
+
idempotency: command.idempotency ?? null
|
|
286
|
+
}))).sort((left, right) => `${left.module}:${left.name}`.localeCompare(`${right.module}:${right.name}`));
|
|
200
287
|
const manifest = {
|
|
201
288
|
version: 1,
|
|
202
289
|
modules: graph.modules,
|
|
203
|
-
externalTokens: graph.externalTokens
|
|
290
|
+
externalTokens: graph.externalTokens,
|
|
291
|
+
commandGovernance: {
|
|
292
|
+
defaults: { authorization: "required", audit: "required", idempotency: "required", transaction: "required" },
|
|
293
|
+
commands: commandGovernance
|
|
294
|
+
}
|
|
204
295
|
};
|
|
205
296
|
const clientCode = options.generateClient ? renderClient(graph, options) : undefined;
|
|
206
297
|
const openApiCode = options.generateOpenApi ? renderOpenApi(graph, options) : undefined;
|
|
207
298
|
const permissionsCode = options.generatePermissions ? renderPermissions(graph) : undefined;
|
|
299
|
+
const contractsManifest = buildContractManifest(graph, {
|
|
300
|
+
client: clientCode !== undefined,
|
|
301
|
+
openapi: openApiCode !== undefined,
|
|
302
|
+
permissions: permissionsCode !== undefined
|
|
303
|
+
});
|
|
208
304
|
return {
|
|
209
305
|
applicationCode: code,
|
|
210
306
|
manifestJson: JSON.stringify(manifest, null, 2) + `
|
|
307
|
+
`,
|
|
308
|
+
contractsManifestJson: JSON.stringify(contractsManifest, null, 2) + `
|
|
211
309
|
`,
|
|
212
310
|
...clientCode === undefined ? {} : { clientCode },
|
|
213
311
|
...openApiCode === undefined ? {} : { openApiCode },
|
|
@@ -219,9 +317,11 @@ async function generateApplication(graph, options) {
|
|
|
219
317
|
await mkdir(options.outDir, { recursive: true });
|
|
220
318
|
const applicationPath = join2(options.outDir, "application.ts");
|
|
221
319
|
const manifestPath = join2(options.outDir, "app.manifest.json");
|
|
320
|
+
const contractsManifestPath = join2(options.outDir, "contracts.manifest.json");
|
|
222
321
|
const writeCandidates = [
|
|
223
322
|
{ path: applicationPath, content: rendered.applicationCode },
|
|
224
|
-
{ path: manifestPath, content: rendered.manifestJson }
|
|
323
|
+
{ path: manifestPath, content: rendered.manifestJson },
|
|
324
|
+
{ path: contractsManifestPath, content: rendered.contractsManifestJson }
|
|
225
325
|
];
|
|
226
326
|
if (rendered.clientCode) {
|
|
227
327
|
writeCandidates.push({ path: join2(options.outDir, "client.ts"), content: rendered.clientCode });
|
|
@@ -2082,7 +2182,9 @@ function destroyScopeInstances(
|
|
|
2082
2182
|
scopeDestructions.set(scope, destruction);
|
|
2083
2183
|
return destruction;
|
|
2084
2184
|
}`;
|
|
2085
|
-
var init_generate = () => {
|
|
2185
|
+
var init_generate = __esm(() => {
|
|
2186
|
+
init_contract_manifest();
|
|
2187
|
+
});
|
|
2086
2188
|
|
|
2087
2189
|
// src/graphql-client.ts
|
|
2088
2190
|
var GRAPHQL_CLIENT_SOURCE = `
|
|
@@ -3953,7 +4055,7 @@ function validateGraph(graph, options = false) {
|
|
|
3953
4055
|
if (typeof options === "object" && options.commandCapabilities) {
|
|
3954
4056
|
const hostCaps = options.commandCapabilities;
|
|
3955
4057
|
const rpcCaps = command.rpc && Object.hasOwn(hostCaps.rpc ?? {}, command.rpc) ? hostCaps.rpc?.[command.rpc] : undefined;
|
|
3956
|
-
if (hostCaps.requirePersistentAdapters && (!rpcCaps?.boundary || rpcCaps.audit !== true || rpcCaps.idempotency !== true || hostCaps.permission !== true || !command.permission || !command.audit || command.idempotency !== "required" || rpcCaps
|
|
4058
|
+
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"))) {
|
|
3957
4059
|
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.");
|
|
3958
4060
|
}
|
|
3959
4061
|
if (rpcCaps?.boundary === "external" && (command.transaction === "required" || rpcCaps.transaction === true)) {
|
|
@@ -6923,10 +7025,20 @@ function validateRouteContracts(graph) {
|
|
|
6923
7025
|
}
|
|
6924
7026
|
|
|
6925
7027
|
// src/compile.ts
|
|
7028
|
+
function withDefaultGovernance(options) {
|
|
7029
|
+
return options.commandCapabilities === undefined ? { ...options, commandCapabilities: {
|
|
7030
|
+
requirePersistentAdapters: true,
|
|
7031
|
+
permission: true,
|
|
7032
|
+
audit: true,
|
|
7033
|
+
idempotency: true,
|
|
7034
|
+
transaction: true
|
|
7035
|
+
} } : options;
|
|
7036
|
+
}
|
|
6926
7037
|
async function renderOptionalGraphql(options) {
|
|
6927
7038
|
return options.graphql ? (await Promise.resolve().then(() => (init_graphql(), exports_graphql))).renderGraphql(options) : { diagnostics: [], files: {} };
|
|
6928
7039
|
}
|
|
6929
7040
|
async function compileProject(options) {
|
|
7041
|
+
options = withDefaultGovernance(options);
|
|
6930
7042
|
const graph = await analyzeProject(options.rootDir, options.include, options.cache, options.changedPaths);
|
|
6931
7043
|
const diagnostics = [
|
|
6932
7044
|
...graph.diagnostics ?? [],
|
|
@@ -6961,6 +7073,7 @@ async function compileProject(options) {
|
|
|
6961
7073
|
"client.ts": rendered.clientCode,
|
|
6962
7074
|
"openapi.ts": rendered.openApiCode,
|
|
6963
7075
|
"permissions.ts": rendered.permissionsCode,
|
|
7076
|
+
"contracts.manifest.json": rendered.contractsManifestJson,
|
|
6964
7077
|
"graphql.ts": graphql.files["graphql.ts"],
|
|
6965
7078
|
"graphql.documents.ts": graphql.files["graphql.documents.ts"]
|
|
6966
7079
|
}, options.strict ?? false));
|
|
@@ -6988,6 +7101,7 @@ async function compileProject(options) {
|
|
|
6988
7101
|
return { diagnostics, graph, written, ...stats ? { stats } : {} };
|
|
6989
7102
|
}
|
|
6990
7103
|
async function checkProject(options) {
|
|
7104
|
+
options = withDefaultGovernance(options);
|
|
6991
7105
|
const graph = await analyzeProject(options.rootDir, options.include, options.cache, options.changedPaths);
|
|
6992
7106
|
const diagnostics = [
|
|
6993
7107
|
...graph.diagnostics ?? [],
|
|
@@ -7029,7 +7143,8 @@ async function checkProject(options) {
|
|
|
7029
7143
|
const expectedFiles = {
|
|
7030
7144
|
...graphql.files,
|
|
7031
7145
|
"application.ts": rendered.applicationCode,
|
|
7032
|
-
"app.manifest.json": rendered.manifestJson
|
|
7146
|
+
"app.manifest.json": rendered.manifestJson,
|
|
7147
|
+
"contracts.manifest.json": rendered.contractsManifestJson
|
|
7033
7148
|
};
|
|
7034
7149
|
if (rendered.clientCode) {
|
|
7035
7150
|
expectedFiles["client.ts"] = rendered.clientCode;
|
|
@@ -11017,6 +11132,13 @@ var DEFAULT_SUPACLOUD_CONFIG = {
|
|
|
11017
11132
|
generateOpenApi: true,
|
|
11018
11133
|
generatePermissions: true,
|
|
11019
11134
|
treeShakeUnusedProviders: true,
|
|
11135
|
+
commandCapabilities: {
|
|
11136
|
+
requirePersistentAdapters: true,
|
|
11137
|
+
permission: true,
|
|
11138
|
+
audit: true,
|
|
11139
|
+
idempotency: true,
|
|
11140
|
+
transaction: true
|
|
11141
|
+
},
|
|
11020
11142
|
moduleBoundaryPreset: "modular-monolith"
|
|
11021
11143
|
};
|
|
11022
11144
|
function defineSupacloudConfig(config = {}) {
|
|
@@ -11100,7 +11222,7 @@ function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
|
|
|
11100
11222
|
...resolved.openApi === undefined ? {} : { openApi: resolved.openApi },
|
|
11101
11223
|
generatePermissions: resolved.generatePermissions ?? DEFAULT_SUPACLOUD_CONFIG.generatePermissions,
|
|
11102
11224
|
moduleBoundaryPreset: resolved.moduleBoundaryPreset ?? DEFAULT_SUPACLOUD_CONFIG.moduleBoundaryPreset,
|
|
11103
|
-
commandCapabilities: resolved.commandCapabilities,
|
|
11225
|
+
commandCapabilities: resolved.commandCapabilities ?? DEFAULT_SUPACLOUD_CONFIG.commandCapabilities,
|
|
11104
11226
|
...resolved.moduleBoundaries ? { moduleBoundaries: resolved.moduleBoundaries } : {},
|
|
11105
11227
|
...resolved.typeSafety ? { typeSafety: resolved.typeSafety } : {},
|
|
11106
11228
|
...resolved.allowRouteCommandBindings === undefined ? {} : { allowRouteCommandBindings: resolved.allowRouteCommandBindings },
|
|
@@ -12173,9 +12295,9 @@ function compilerVersion() {
|
|
|
12173
12295
|
}
|
|
12174
12296
|
function migrationDependencies() {
|
|
12175
12297
|
return {
|
|
12176
|
-
"@supacloud/app": "0.
|
|
12298
|
+
"@supacloud/app": "0.16.0",
|
|
12177
12299
|
"@supacloud/compiler": compilerVersion(),
|
|
12178
|
-
"@supacloud/elysia": "0.
|
|
12300
|
+
"@supacloud/elysia": "0.18.0",
|
|
12179
12301
|
elysia: "1.4.30",
|
|
12180
12302
|
typescript: "7.0.2"
|
|
12181
12303
|
};
|
package/dist/config.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export interface SupaCloudConfig {
|
|
|
23
23
|
commandCapabilities?: CommandExecutionCapabilities;
|
|
24
24
|
treeShakeUnusedProviders?: boolean;
|
|
25
25
|
}
|
|
26
|
-
export declare const DEFAULT_SUPACLOUD_CONFIG: Required<Omit<SupaCloudConfig, "include" | "moduleBoundaryPreset" | "
|
|
26
|
+
export declare const DEFAULT_SUPACLOUD_CONFIG: Required<Omit<SupaCloudConfig, "include" | "moduleBoundaryPreset" | "moduleBoundaries" | "typeSafety" | "delivery" | "allowRouteCommandBindings" | "disallowControllerDirectDb" | "detectOrphanModules" | "openApi">> & {
|
|
27
27
|
include: string[];
|
|
28
28
|
moduleBoundaryPreset: ModuleBoundaryPresetName;
|
|
29
29
|
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ApplicationGraph } from "./types";
|
|
2
|
+
export interface ContractManifest {
|
|
3
|
+
version: 1;
|
|
4
|
+
commands: Array<{
|
|
5
|
+
name: string;
|
|
6
|
+
className: string;
|
|
7
|
+
module: string;
|
|
8
|
+
permission?: string;
|
|
9
|
+
rpc?: string;
|
|
10
|
+
transaction: "required" | "none";
|
|
11
|
+
idempotency: "required" | "none";
|
|
12
|
+
auditEvent?: string;
|
|
13
|
+
}>;
|
|
14
|
+
queries: Array<{
|
|
15
|
+
name: string;
|
|
16
|
+
className: string;
|
|
17
|
+
module: string;
|
|
18
|
+
}>;
|
|
19
|
+
routes: Array<{
|
|
20
|
+
method: string;
|
|
21
|
+
path: string;
|
|
22
|
+
module: string;
|
|
23
|
+
controller: string;
|
|
24
|
+
handler: string;
|
|
25
|
+
command?: string;
|
|
26
|
+
permission?: string;
|
|
27
|
+
requestSchemas: Partial<Record<"body" | "params" | "query" | "headers" | "cookie", string>>;
|
|
28
|
+
responseSchema?: string;
|
|
29
|
+
evidence?: string;
|
|
30
|
+
}>;
|
|
31
|
+
permissions: string[];
|
|
32
|
+
rpc: Array<{
|
|
33
|
+
command: string;
|
|
34
|
+
adapter: string;
|
|
35
|
+
}>;
|
|
36
|
+
events: Array<{
|
|
37
|
+
name: string;
|
|
38
|
+
source: string;
|
|
39
|
+
command: string;
|
|
40
|
+
}>;
|
|
41
|
+
fixtures: string[];
|
|
42
|
+
artifacts: {
|
|
43
|
+
client: boolean;
|
|
44
|
+
openapi: boolean;
|
|
45
|
+
permissions: boolean;
|
|
46
|
+
sdk: "generated-client";
|
|
47
|
+
openapiDocument: "generated";
|
|
48
|
+
permissionManifest: "generated";
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export declare function buildContractManifest(graph: ApplicationGraph, artifacts: Pick<ContractManifest["artifacts"], "client" | "openapi" | "permissions">): ContractManifest;
|
package/dist/generate.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export { TraitCompiler } from "./traits";
|
|
|
22
22
|
export type { TraitCompilation, TraitHandler, TraitKind, TraitRecord } from "./traits";
|
|
23
23
|
export { generateApplication, renderApplication, renderClient, renderOpenApi } from "./generate";
|
|
24
24
|
export type { GenerateOptions, RenderedArtifacts } from "./generate";
|
|
25
|
+
export { buildContractManifest } from "./contract-manifest";
|
|
26
|
+
export type { ContractManifest } from "./contract-manifest";
|
|
25
27
|
export { diffOpenApiDocuments, exportGeneratedOpenApiJson, formatOpenApiDiff, loadGeneratedOpenApiDocument, parseOpenApiDocument, readOpenApiJson, serializeOpenApiJson, writeOpenApiJson, OpenApiDocumentError, } from "./openapi-tools";
|
|
26
28
|
export type { OpenApiDiffChange, OpenApiDiffResult, OpenApiDocument, OpenApiExportOptions, OpenApiJsonWriteResult, OpenApiObject, } from "./openapi-tools";
|
|
27
29
|
export type { ContextPack, DoctorResult, ExecutionPlan } from "./inspect";
|
package/dist/index.js
CHANGED
|
@@ -77,6 +77,83 @@ function findClosestMatch(target, candidates) {
|
|
|
77
77
|
}
|
|
78
78
|
var REQUEST_CONTEXT_TOKEN_NAME = "supacloud.request-context", JOB_CONTEXT_TOKEN_NAME = "supacloud.job-context";
|
|
79
79
|
|
|
80
|
+
// src/contract-manifest.ts
|
|
81
|
+
function buildContractManifest(graph, artifacts) {
|
|
82
|
+
const commands = [];
|
|
83
|
+
const queries = [];
|
|
84
|
+
const routes = [];
|
|
85
|
+
const permissions = new Set;
|
|
86
|
+
const rpc = [];
|
|
87
|
+
const events = [];
|
|
88
|
+
const fixtures = new Set;
|
|
89
|
+
for (const module of graph.modules) {
|
|
90
|
+
for (const command of module.commands) {
|
|
91
|
+
if (command.permission)
|
|
92
|
+
permissions.add(command.permission);
|
|
93
|
+
if (command.rpc)
|
|
94
|
+
rpc.push({ command: command.name, adapter: command.rpc });
|
|
95
|
+
if (command.audit)
|
|
96
|
+
events.push({ name: command.audit, source: "command.audit", command: command.name });
|
|
97
|
+
commands.push({
|
|
98
|
+
name: command.name,
|
|
99
|
+
className: command.className,
|
|
100
|
+
module: module.name,
|
|
101
|
+
...command.permission === undefined ? {} : { permission: command.permission },
|
|
102
|
+
...command.rpc === undefined ? {} : { rpc: command.rpc },
|
|
103
|
+
transaction: command.transaction,
|
|
104
|
+
idempotency: command.idempotency,
|
|
105
|
+
...command.audit === undefined ? {} : { auditEvent: command.audit }
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
for (const query of module.queries) {
|
|
109
|
+
queries.push({ name: query.name, className: query.className, module: module.name });
|
|
110
|
+
}
|
|
111
|
+
for (const controller of module.controllers) {
|
|
112
|
+
for (const route of controller.routes) {
|
|
113
|
+
const command = route.command ? module.commands.find((candidate) => candidate.className === route.command) : undefined;
|
|
114
|
+
if (command?.permission)
|
|
115
|
+
permissions.add(command.permission);
|
|
116
|
+
if (route.contract?.evidence)
|
|
117
|
+
fixtures.add(route.contract.evidence);
|
|
118
|
+
routes.push({
|
|
119
|
+
method: route.method,
|
|
120
|
+
path: joinRoutePaths(controller.path, route.path),
|
|
121
|
+
module: module.name,
|
|
122
|
+
controller: controller.className,
|
|
123
|
+
handler: route.handler,
|
|
124
|
+
...route.command === undefined ? {} : { command: route.command },
|
|
125
|
+
...command?.permission === undefined ? {} : { permission: command.permission },
|
|
126
|
+
requestSchemas: Object.fromEntries(["body", "params", "query", "headers", "cookie"].flatMap((key) => route[key] === undefined ? [] : [[key, route[key]]])),
|
|
127
|
+
...route.response === undefined ? {} : { responseSchema: route.response },
|
|
128
|
+
...route.contract?.evidence === undefined ? {} : { evidence: route.contract.evidence }
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
commands.sort((left, right) => left.name.localeCompare(right.name));
|
|
134
|
+
queries.sort((left, right) => left.name.localeCompare(right.name));
|
|
135
|
+
routes.sort((left, right) => `${left.method} ${left.path}`.localeCompare(`${right.method} ${right.path}`));
|
|
136
|
+
rpc.sort((left, right) => left.command.localeCompare(right.command));
|
|
137
|
+
events.sort((left, right) => left.name.localeCompare(right.name));
|
|
138
|
+
return {
|
|
139
|
+
version: 1,
|
|
140
|
+
commands,
|
|
141
|
+
queries,
|
|
142
|
+
routes,
|
|
143
|
+
permissions: [...permissions].sort(),
|
|
144
|
+
rpc,
|
|
145
|
+
events,
|
|
146
|
+
fixtures: [...fixtures].sort(),
|
|
147
|
+
artifacts: {
|
|
148
|
+
...artifacts,
|
|
149
|
+
sdk: "generated-client",
|
|
150
|
+
openapiDocument: "generated",
|
|
151
|
+
permissionManifest: "generated"
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
var init_contract_manifest = () => {};
|
|
156
|
+
|
|
80
157
|
// src/generate.ts
|
|
81
158
|
import { createHash as createHash4 } from "node:crypto";
|
|
82
159
|
import { access, mkdir, rename, unlink, writeFile } from "node:fs/promises";
|
|
@@ -196,17 +273,38 @@ function renderApplication(graph, options) {
|
|
|
196
273
|
""
|
|
197
274
|
].join(`
|
|
198
275
|
`);
|
|
276
|
+
const commandGovernance = graph.modules.flatMap((module) => module.commands.map((command) => ({
|
|
277
|
+
module: module.name,
|
|
278
|
+
className: command.className,
|
|
279
|
+
name: command.name,
|
|
280
|
+
permission: command.permission ?? null,
|
|
281
|
+
rpc: command.rpc ?? null,
|
|
282
|
+
transaction: command.transaction ?? null,
|
|
283
|
+
audit: command.audit ?? null,
|
|
284
|
+
idempotency: command.idempotency ?? null
|
|
285
|
+
}))).sort((left, right) => `${left.module}:${left.name}`.localeCompare(`${right.module}:${right.name}`));
|
|
199
286
|
const manifest = {
|
|
200
287
|
version: 1,
|
|
201
288
|
modules: graph.modules,
|
|
202
|
-
externalTokens: graph.externalTokens
|
|
289
|
+
externalTokens: graph.externalTokens,
|
|
290
|
+
commandGovernance: {
|
|
291
|
+
defaults: { authorization: "required", audit: "required", idempotency: "required", transaction: "required" },
|
|
292
|
+
commands: commandGovernance
|
|
293
|
+
}
|
|
203
294
|
};
|
|
204
295
|
const clientCode = options.generateClient ? renderClient(graph, options) : undefined;
|
|
205
296
|
const openApiCode = options.generateOpenApi ? renderOpenApi(graph, options) : undefined;
|
|
206
297
|
const permissionsCode = options.generatePermissions ? renderPermissions(graph) : undefined;
|
|
298
|
+
const contractsManifest = buildContractManifest(graph, {
|
|
299
|
+
client: clientCode !== undefined,
|
|
300
|
+
openapi: openApiCode !== undefined,
|
|
301
|
+
permissions: permissionsCode !== undefined
|
|
302
|
+
});
|
|
207
303
|
return {
|
|
208
304
|
applicationCode: code,
|
|
209
305
|
manifestJson: JSON.stringify(manifest, null, 2) + `
|
|
306
|
+
`,
|
|
307
|
+
contractsManifestJson: JSON.stringify(contractsManifest, null, 2) + `
|
|
210
308
|
`,
|
|
211
309
|
...clientCode === undefined ? {} : { clientCode },
|
|
212
310
|
...openApiCode === undefined ? {} : { openApiCode },
|
|
@@ -218,9 +316,11 @@ async function generateApplication(graph, options) {
|
|
|
218
316
|
await mkdir(options.outDir, { recursive: true });
|
|
219
317
|
const applicationPath = join2(options.outDir, "application.ts");
|
|
220
318
|
const manifestPath = join2(options.outDir, "app.manifest.json");
|
|
319
|
+
const contractsManifestPath = join2(options.outDir, "contracts.manifest.json");
|
|
221
320
|
const writeCandidates = [
|
|
222
321
|
{ path: applicationPath, content: rendered.applicationCode },
|
|
223
|
-
{ path: manifestPath, content: rendered.manifestJson }
|
|
322
|
+
{ path: manifestPath, content: rendered.manifestJson },
|
|
323
|
+
{ path: contractsManifestPath, content: rendered.contractsManifestJson }
|
|
224
324
|
];
|
|
225
325
|
if (rendered.clientCode) {
|
|
226
326
|
writeCandidates.push({ path: join2(options.outDir, "client.ts"), content: rendered.clientCode });
|
|
@@ -2081,7 +2181,9 @@ function destroyScopeInstances(
|
|
|
2081
2181
|
scopeDestructions.set(scope, destruction);
|
|
2082
2182
|
return destruction;
|
|
2083
2183
|
}`;
|
|
2084
|
-
var init_generate = () => {
|
|
2184
|
+
var init_generate = __esm(() => {
|
|
2185
|
+
init_contract_manifest();
|
|
2186
|
+
});
|
|
2085
2187
|
|
|
2086
2188
|
// src/graphql-client.ts
|
|
2087
2189
|
var GRAPHQL_CLIENT_SOURCE = `
|
|
@@ -3948,7 +4050,7 @@ function validateGraph(graph, options = false) {
|
|
|
3948
4050
|
if (typeof options === "object" && options.commandCapabilities) {
|
|
3949
4051
|
const hostCaps = options.commandCapabilities;
|
|
3950
4052
|
const rpcCaps = command.rpc && Object.hasOwn(hostCaps.rpc ?? {}, command.rpc) ? hostCaps.rpc?.[command.rpc] : undefined;
|
|
3951
|
-
if (hostCaps.requirePersistentAdapters && (!rpcCaps?.boundary || rpcCaps.audit !== true || rpcCaps.idempotency !== true || hostCaps.permission !== true || !command.permission || !command.audit || command.idempotency !== "required" || rpcCaps
|
|
4053
|
+
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"))) {
|
|
3952
4054
|
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.");
|
|
3953
4055
|
}
|
|
3954
4056
|
if (rpcCaps?.boundary === "external" && (command.transaction === "required" || rpcCaps.transaction === true)) {
|
|
@@ -6920,10 +7022,20 @@ function validateRouteContracts(graph) {
|
|
|
6920
7022
|
}
|
|
6921
7023
|
|
|
6922
7024
|
// src/compile.ts
|
|
7025
|
+
function withDefaultGovernance(options) {
|
|
7026
|
+
return options.commandCapabilities === undefined ? { ...options, commandCapabilities: {
|
|
7027
|
+
requirePersistentAdapters: true,
|
|
7028
|
+
permission: true,
|
|
7029
|
+
audit: true,
|
|
7030
|
+
idempotency: true,
|
|
7031
|
+
transaction: true
|
|
7032
|
+
} } : options;
|
|
7033
|
+
}
|
|
6923
7034
|
async function renderOptionalGraphql(options) {
|
|
6924
7035
|
return options.graphql ? (await Promise.resolve().then(() => (init_graphql(), exports_graphql))).renderGraphql(options) : { diagnostics: [], files: {} };
|
|
6925
7036
|
}
|
|
6926
7037
|
async function compileProject(options) {
|
|
7038
|
+
options = withDefaultGovernance(options);
|
|
6927
7039
|
const graph = await analyzeProject(options.rootDir, options.include, options.cache, options.changedPaths);
|
|
6928
7040
|
const diagnostics = [
|
|
6929
7041
|
...graph.diagnostics ?? [],
|
|
@@ -6958,6 +7070,7 @@ async function compileProject(options) {
|
|
|
6958
7070
|
"client.ts": rendered.clientCode,
|
|
6959
7071
|
"openapi.ts": rendered.openApiCode,
|
|
6960
7072
|
"permissions.ts": rendered.permissionsCode,
|
|
7073
|
+
"contracts.manifest.json": rendered.contractsManifestJson,
|
|
6961
7074
|
"graphql.ts": graphql.files["graphql.ts"],
|
|
6962
7075
|
"graphql.documents.ts": graphql.files["graphql.documents.ts"]
|
|
6963
7076
|
}, options.strict ?? false));
|
|
@@ -6985,6 +7098,7 @@ async function compileProject(options) {
|
|
|
6985
7098
|
return { diagnostics, graph, written, ...stats ? { stats } : {} };
|
|
6986
7099
|
}
|
|
6987
7100
|
async function checkProject(options) {
|
|
7101
|
+
options = withDefaultGovernance(options);
|
|
6988
7102
|
const graph = await analyzeProject(options.rootDir, options.include, options.cache, options.changedPaths);
|
|
6989
7103
|
const diagnostics = [
|
|
6990
7104
|
...graph.diagnostics ?? [],
|
|
@@ -7026,7 +7140,8 @@ async function checkProject(options) {
|
|
|
7026
7140
|
const expectedFiles = {
|
|
7027
7141
|
...graphql.files,
|
|
7028
7142
|
"application.ts": rendered.applicationCode,
|
|
7029
|
-
"app.manifest.json": rendered.manifestJson
|
|
7143
|
+
"app.manifest.json": rendered.manifestJson,
|
|
7144
|
+
"contracts.manifest.json": rendered.contractsManifestJson
|
|
7030
7145
|
};
|
|
7031
7146
|
if (rendered.clientCode) {
|
|
7032
7147
|
expectedFiles["client.ts"] = rendered.clientCode;
|
|
@@ -11750,9 +11865,9 @@ function compilerVersion() {
|
|
|
11750
11865
|
}
|
|
11751
11866
|
function migrationDependencies() {
|
|
11752
11867
|
return {
|
|
11753
|
-
"@supacloud/app": "0.
|
|
11868
|
+
"@supacloud/app": "0.16.0",
|
|
11754
11869
|
"@supacloud/compiler": compilerVersion(),
|
|
11755
|
-
"@supacloud/elysia": "0.
|
|
11870
|
+
"@supacloud/elysia": "0.18.0",
|
|
11756
11871
|
elysia: "1.4.30",
|
|
11757
11872
|
typescript: "7.0.2"
|
|
11758
11873
|
};
|
|
@@ -12452,6 +12567,7 @@ function exportGraphDot(graph) {
|
|
|
12452
12567
|
|
|
12453
12568
|
// src/index.ts
|
|
12454
12569
|
init_generate();
|
|
12570
|
+
init_contract_manifest();
|
|
12455
12571
|
|
|
12456
12572
|
// src/openapi-tools.ts
|
|
12457
12573
|
import { mkdir as mkdir4, readFile as readFile10, rename as rename6, unlink as unlink3, writeFile as writeFile5 } from "node:fs/promises";
|
|
@@ -12973,6 +13089,13 @@ var DEFAULT_SUPACLOUD_CONFIG = {
|
|
|
12973
13089
|
generateOpenApi: true,
|
|
12974
13090
|
generatePermissions: true,
|
|
12975
13091
|
treeShakeUnusedProviders: true,
|
|
13092
|
+
commandCapabilities: {
|
|
13093
|
+
requirePersistentAdapters: true,
|
|
13094
|
+
permission: true,
|
|
13095
|
+
audit: true,
|
|
13096
|
+
idempotency: true,
|
|
13097
|
+
transaction: true
|
|
13098
|
+
},
|
|
12976
13099
|
moduleBoundaryPreset: "modular-monolith"
|
|
12977
13100
|
};
|
|
12978
13101
|
function defineSupacloudConfig(config = {}) {
|
|
@@ -13056,7 +13179,7 @@ function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
|
|
|
13056
13179
|
...resolved.openApi === undefined ? {} : { openApi: resolved.openApi },
|
|
13057
13180
|
generatePermissions: resolved.generatePermissions ?? DEFAULT_SUPACLOUD_CONFIG.generatePermissions,
|
|
13058
13181
|
moduleBoundaryPreset: resolved.moduleBoundaryPreset ?? DEFAULT_SUPACLOUD_CONFIG.moduleBoundaryPreset,
|
|
13059
|
-
commandCapabilities: resolved.commandCapabilities,
|
|
13182
|
+
commandCapabilities: resolved.commandCapabilities ?? DEFAULT_SUPACLOUD_CONFIG.commandCapabilities,
|
|
13060
13183
|
...resolved.moduleBoundaries ? { moduleBoundaries: resolved.moduleBoundaries } : {},
|
|
13061
13184
|
...resolved.typeSafety ? { typeSafety: resolved.typeSafety } : {},
|
|
13062
13185
|
...resolved.allowRouteCommandBindings === undefined ? {} : { allowRouteCommandBindings: resolved.allowRouteCommandBindings },
|
|
@@ -13111,6 +13234,7 @@ export {
|
|
|
13111
13234
|
TraitCompiler,
|
|
13112
13235
|
analyzeProject,
|
|
13113
13236
|
applyDiagnosticFix,
|
|
13237
|
+
buildContractManifest,
|
|
13114
13238
|
buildDeliveryProject,
|
|
13115
13239
|
camelName,
|
|
13116
13240
|
checkProject,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.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",
|