@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 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.boundary === "database" && (rpcCaps.transaction !== true || command.transaction !== "required"))) {
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.14.0",
12298
+ "@supacloud/app": "0.16.0",
12177
12299
  "@supacloud/compiler": compilerVersion(),
12178
- "@supacloud/elysia": "0.17.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" | "commandCapabilities" | "moduleBoundaries" | "typeSafety" | "delivery" | "allowRouteCommandBindings" | "disallowControllerDirectDb" | "detectOrphanModules" | "openApi">> & {
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;
@@ -14,6 +14,7 @@ export interface GenerateOptions {
14
14
  export interface RenderedArtifacts {
15
15
  applicationCode: string;
16
16
  manifestJson: string;
17
+ contractsManifestJson: string;
17
18
  clientCode?: string;
18
19
  openApiCode?: string;
19
20
  permissionsCode?: string;
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.boundary === "database" && (rpcCaps.transaction !== true || command.transaction !== "required"))) {
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.14.0",
11868
+ "@supacloud/app": "0.16.0",
11754
11869
  "@supacloud/compiler": compilerVersion(),
11755
- "@supacloud/elysia": "0.17.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.22.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",