@lunora/codegen 0.0.0 → 1.0.0-alpha.2

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.
Files changed (34) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +117 -9
  3. package/__assets__/package-og.svg +14 -0
  4. package/dist/index.d.mts +1473 -0
  5. package/dist/index.d.ts +1473 -0
  6. package/dist/index.mjs +28 -0
  7. package/dist/packem_shared/CONTAINERS_FILENAME-0K-pjNb8.mjs +224 -0
  8. package/dist/packem_shared/CodegenDiagnosticError-54jWDxA9.mjs +22 -0
  9. package/dist/packem_shared/LUNORA_ERROR_CODES-CySpQPD3.mjs +61 -0
  10. package/dist/packem_shared/buildOpenApiDocument-yHVN66Xd.mjs +183 -0
  11. package/dist/packem_shared/buildOpenRpcDocument-BZGY1-jT.mjs +60 -0
  12. package/dist/packem_shared/buildSchemaSnapshot-DzLDbWk3.mjs +233 -0
  13. package/dist/packem_shared/createCodegenProject-DGJm0_Pk.mjs +895 -0
  14. package/dist/packem_shared/discover-ast-CT6BgBr4.mjs +13 -0
  15. package/dist/packem_shared/discoverAuthApiCalls-C35R6z0T.mjs +62 -0
  16. package/dist/packem_shared/discoverCrons-BL6iGuJ3.mjs +254 -0
  17. package/dist/packem_shared/discoverFunctions-DEgAcRuD.mjs +460 -0
  18. package/dist/packem_shared/discoverHttpRoutes-C978pBiG.mjs +131 -0
  19. package/dist/packem_shared/discoverInserts-CRQdXvHO.mjs +39 -0
  20. package/dist/packem_shared/discoverMaskProcedures-B64zA740.mjs +217 -0
  21. package/dist/packem_shared/discoverMigrations-Doj_-BAA.mjs +91 -0
  22. package/dist/packem_shared/discoverNondeterministicCalls-4KiPQxQU.mjs +122 -0
  23. package/dist/packem_shared/discoverQueries-BkIi0dBD.mjs +62 -0
  24. package/dist/packem_shared/discoverRlsMetadata-DpRB1HMe.mjs +280 -0
  25. package/dist/packem_shared/discoverSchema-BBulgGbH.mjs +542 -0
  26. package/dist/packem_shared/discoverStorageRulesMetadata-DAqJUxUv.mjs +97 -0
  27. package/dist/packem_shared/discoverWorkflows-DRDQdhfq.mjs +84 -0
  28. package/dist/packem_shared/emitApi-hRVC-kE7.mjs +2426 -0
  29. package/dist/packem_shared/emitApp-CzZ6GbrD.mjs +593 -0
  30. package/dist/packem_shared/lintSchema-DicbOHvH.mjs +68 -0
  31. package/dist/packem_shared/parse-validator-tuQtHrsr.mjs +132 -0
  32. package/dist/packem_shared/paths-BRd6JHuF.mjs +11 -0
  33. package/dist/packem_shared/schemaFromIr-DTYsLBaA.mjs +57 -0
  34. package/package.json +45 -17
package/dist/index.mjs ADDED
@@ -0,0 +1,28 @@
1
+ export { formatAdvisories, lintSchema } from './packem_shared/lintSchema-DicbOHvH.mjs';
2
+ export { CodegenDiagnosticError, diagnosticAt } from './packem_shared/CodegenDiagnosticError-54jWDxA9.mjs';
3
+ export { default as discoverAuthApiCalls } from './packem_shared/discoverAuthApiCalls-C35R6z0T.mjs';
4
+ export { CONTAINERS_FILENAME, discoverContainers } from './packem_shared/CONTAINERS_FILENAME-0K-pjNb8.mjs';
5
+ export { default as discoverCrons } from './packem_shared/discoverCrons-BL6iGuJ3.mjs';
6
+ export { discoverFunctions } from './packem_shared/discoverFunctions-DEgAcRuD.mjs';
7
+ export { default as discoverHttpRoutes } from './packem_shared/discoverHttpRoutes-C978pBiG.mjs';
8
+ export { default as discoverInserts } from './packem_shared/discoverInserts-CRQdXvHO.mjs';
9
+ export { default as discoverMaskProcedures } from './packem_shared/discoverMaskProcedures-B64zA740.mjs';
10
+ export { default as discoverMigrations } from './packem_shared/discoverMigrations-Doj_-BAA.mjs';
11
+ export { default as discoverNondeterministicCalls } from './packem_shared/discoverNondeterministicCalls-4KiPQxQU.mjs';
12
+ export { default as discoverQueries } from './packem_shared/discoverQueries-BkIi0dBD.mjs';
13
+ export { discoverRlsMetadata, default as discoverRlsProcedures } from './packem_shared/discoverRlsMetadata-DpRB1HMe.mjs';
14
+ export { default as discoverSchema } from './packem_shared/discoverSchema-BBulgGbH.mjs';
15
+ export { default as discoverStorageRulesMetadata } from './packem_shared/discoverStorageRulesMetadata-DAqJUxUv.mjs';
16
+ export { WORKFLOWS_FILENAME, discoverWorkflows } from './packem_shared/discoverWorkflows-DRDQdhfq.mjs';
17
+ export { GENERATED_HEADER, emitApi, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers } from './packem_shared/emitApi-hRVC-kE7.mjs';
18
+ export { emitApp } from './packem_shared/emitApp-CzZ6GbrD.mjs';
19
+ export { buildOpenApiDocument, emitOpenApi, emitOpenApiModule } from './packem_shared/buildOpenApiDocument-yHVN66Xd.mjs';
20
+ export { OPENRPC_VERSION, buildOpenRpcDocument, emitOpenRpc, emitOpenRpcModule } from './packem_shared/buildOpenRpcDocument-BZGY1-jT.mjs';
21
+ export { SCHEMA_SNAPSHOT_FILENAME, createCodegenProject, refreshCodegenProject, runCodegen } from './packem_shared/createCodegenProject-DGJm0_Pk.mjs';
22
+ export { SCHEMA_SNAPSHOT_VERSION, SchemaSnapshotParseError, buildSchemaSnapshot, diffSchemaSnapshots, evaluateSchemaDrift, parseSchemaSnapshot, serializeSchemaSnapshot } from './packem_shared/buildSchemaSnapshot-DzLDbWk3.mjs';
23
+ export { schemaFromIr } from './packem_shared/schemaFromIr-DTYsLBaA.mjs';
24
+ export { LUNORA_ERROR_CODES, validatorIrToJsonSchema } from './packem_shared/LUNORA_ERROR_CODES-CySpQPD3.mjs';
25
+
26
+ const VERSION = "0.0.0";
27
+
28
+ export { VERSION };
@@ -0,0 +1,224 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { containerClassName, containerBindingName, normalizeContainerImage } from '@lunora/container';
4
+ import { SyntaxKind, Node } from 'ts-morph';
5
+ import { diagnosticAt } from './CodegenDiagnosticError-54jWDxA9.mjs';
6
+
7
+ const CONTAINERS_FILENAME = "containers.ts";
8
+ const isDefineContainer = (identifier) => {
9
+ const symbol = identifier.getSymbol();
10
+ if (!symbol) {
11
+ return identifier.getText() === "defineContainer";
12
+ }
13
+ for (const declaration of symbol.getDeclarations()) {
14
+ if (!Node.isImportSpecifier(declaration)) {
15
+ continue;
16
+ }
17
+ if (declaration.getImportDeclaration().getModuleSpecifierValue() !== "@lunora/container") {
18
+ return false;
19
+ }
20
+ return declaration.getNameNode().getText() === "defineContainer";
21
+ }
22
+ return false;
23
+ };
24
+ const stringProperty = (expression, exportName, property) => {
25
+ if (Node.isStringLiteral(expression) || Node.isNoSubstitutionTemplateLiteral(expression)) {
26
+ return expression.getLiteralValue();
27
+ }
28
+ throw diagnosticAt(
29
+ expression,
30
+ `container "${exportName}": \`${property}\` must be a static string literal — it is deploy configuration codegen writes into wrangler.jsonc`
31
+ );
32
+ };
33
+ const numberProperty = (expression, exportName, property) => {
34
+ if (Node.isNumericLiteral(expression)) {
35
+ return expression.getLiteralValue();
36
+ }
37
+ throw diagnosticAt(
38
+ expression,
39
+ `container "${exportName}": \`${property}\` must be a static number literal — it is deploy configuration codegen writes into wrangler.jsonc`
40
+ );
41
+ };
42
+ const imageFromExpression = (expression, exportName) => {
43
+ if (Node.isStringLiteral(expression) || Node.isNoSubstitutionTemplateLiteral(expression)) {
44
+ return normalizeContainerImage(expression.getLiteralValue());
45
+ }
46
+ if (Node.isObjectLiteralExpression(expression)) {
47
+ const registry = expression.getProperty("registry");
48
+ if (registry && Node.isPropertyAssignment(registry)) {
49
+ const initializer = registry.getInitializerOrThrow();
50
+ return normalizeContainerImage({ registry: stringProperty(initializer, exportName, "image.registry") });
51
+ }
52
+ const build = expression.getProperty("build");
53
+ if (build && Node.isPropertyAssignment(build)) {
54
+ const initializer = build.getInitializerOrThrow();
55
+ return normalizeContainerImage({ build: stringProperty(initializer, exportName, "image.build") });
56
+ }
57
+ }
58
+ throw diagnosticAt(expression, `container "${exportName}": \`image\` must be a static string path, { registry: "…" }, or { build: "…" } literal`);
59
+ };
60
+ const stringRecordLiteral = (expression) => {
61
+ if (!Node.isObjectLiteralExpression(expression)) {
62
+ return void 0;
63
+ }
64
+ const record = {};
65
+ for (const property of expression.getProperties()) {
66
+ if (!Node.isPropertyAssignment(property)) {
67
+ continue;
68
+ }
69
+ const value = property.getInitializerOrThrow();
70
+ if (Node.isStringLiteral(value) || Node.isNoSubstitutionTemplateLiteral(value)) {
71
+ record[property.getName()] = value.getLiteralValue();
72
+ }
73
+ }
74
+ return Object.keys(record).length > 0 ? record : void 0;
75
+ };
76
+ const rolloutLiteral = (expression) => {
77
+ if (!Node.isObjectLiteralExpression(expression)) {
78
+ return void 0;
79
+ }
80
+ const rollout = {};
81
+ for (const property of expression.getProperties()) {
82
+ if (!Node.isPropertyAssignment(property)) {
83
+ continue;
84
+ }
85
+ const key = property.getName();
86
+ const value = property.getInitializerOrThrow();
87
+ if (Node.isNumericLiteral(value) && (key === "gracePeriodSeconds" || key === "stepPercentage")) {
88
+ rollout[key] = value.getLiteralValue();
89
+ }
90
+ }
91
+ return rollout.gracePeriodSeconds === void 0 && rollout.stepPercentage === void 0 ? void 0 : rollout;
92
+ };
93
+ const booleanLiteral = (expression) => {
94
+ if (Node.isTrueLiteral(expression)) {
95
+ return true;
96
+ }
97
+ if (Node.isFalseLiteral(expression)) {
98
+ return false;
99
+ }
100
+ return void 0;
101
+ };
102
+ const stringOrNumberLiteral = (expression) => {
103
+ if (Node.isStringLiteral(expression) || Node.isNoSubstitutionTemplateLiteral(expression)) {
104
+ return expression.getLiteralValue();
105
+ }
106
+ if (Node.isNumericLiteral(expression)) {
107
+ return expression.getLiteralValue();
108
+ }
109
+ return void 0;
110
+ };
111
+ const instanceTypeFromExpression = (expression, exportName) => {
112
+ if (Node.isStringLiteral(expression)) {
113
+ return expression.getLiteralValue();
114
+ }
115
+ if (Node.isObjectLiteralExpression(expression)) {
116
+ const custom = {};
117
+ for (const property of expression.getProperties()) {
118
+ if (!Node.isPropertyAssignment(property)) {
119
+ throw diagnosticAt(property, `container "${exportName}": \`instanceType\` must be an object of static number literals`);
120
+ }
121
+ const key = property.getName();
122
+ if (key !== "diskMb" && key !== "memoryMib" && key !== "vcpu") {
123
+ throw diagnosticAt(property, `container "${exportName}": unknown \`instanceType\` field "${key}" — expected vcpu, memoryMib, or diskMb`);
124
+ }
125
+ custom[key] = numberProperty(property.getInitializerOrThrow(), exportName, `instanceType.${key}`);
126
+ }
127
+ return custom;
128
+ }
129
+ throw diagnosticAt(expression, `container "${exportName}": \`instanceType\` must be a static string or { vcpu, memoryMib, diskMb } literal`);
130
+ };
131
+ const containerFromCall = (call, exportName) => {
132
+ const argument = call.getArguments()[0];
133
+ if (!argument || !Node.isObjectLiteralExpression(argument)) {
134
+ throw diagnosticAt(call, `container "${exportName}": defineContainer must be passed an inline object literal`);
135
+ }
136
+ const ir = {
137
+ bindingName: containerBindingName(exportName),
138
+ className: containerClassName(exportName),
139
+ exportName,
140
+ image: { buildContext: ".", dockerfilePath: "./Dockerfile", kind: "dockerfile" }
141
+ };
142
+ let sawImage = false;
143
+ for (const property of argument.getProperties()) {
144
+ if (!Node.isPropertyAssignment(property)) {
145
+ continue;
146
+ }
147
+ const key = property.getName();
148
+ const initializer = property.getInitializerOrThrow();
149
+ switch (key) {
150
+ case "buildArgs": {
151
+ ir.buildArgs = stringRecordLiteral(initializer);
152
+ break;
153
+ }
154
+ case "enableInternet": {
155
+ ir.enableInternet = booleanLiteral(initializer);
156
+ break;
157
+ }
158
+ case "image": {
159
+ ir.image = imageFromExpression(initializer, exportName);
160
+ sawImage = true;
161
+ break;
162
+ }
163
+ case "instanceType": {
164
+ ir.instanceType = instanceTypeFromExpression(initializer, exportName);
165
+ break;
166
+ }
167
+ case "maxInstances": {
168
+ ir.maxInstances = numberProperty(initializer, exportName, "maxInstances");
169
+ break;
170
+ }
171
+ case "name": {
172
+ ir.name = stringProperty(initializer, exportName, "name");
173
+ break;
174
+ }
175
+ case "rollout": {
176
+ ir.rollout = rolloutLiteral(initializer);
177
+ break;
178
+ }
179
+ case "sleepAfter": {
180
+ ir.sleepAfter = stringOrNumberLiteral(initializer);
181
+ break;
182
+ }
183
+ }
184
+ }
185
+ if (!sawImage) {
186
+ throw diagnosticAt(argument, `container "${exportName}": defineContainer requires a static \`image\` property`);
187
+ }
188
+ return ir;
189
+ };
190
+ const containersFromSource = (source) => {
191
+ const containers = [];
192
+ for (const declaration of source.getVariableDeclarations()) {
193
+ if (!declaration.isExported()) {
194
+ continue;
195
+ }
196
+ const initializer = declaration.getInitializer();
197
+ if (initializer?.getKind() !== SyntaxKind.CallExpression) {
198
+ continue;
199
+ }
200
+ const call = initializer;
201
+ const callee = call.getExpression();
202
+ if (!Node.isIdentifier(callee) || !isDefineContainer(callee)) {
203
+ continue;
204
+ }
205
+ const nameNode = declaration.getNameNode();
206
+ if (!Node.isIdentifier(nameNode)) {
207
+ throw diagnosticAt(nameNode, "defineContainer exports must be plain named exports (no destructuring)");
208
+ }
209
+ containers.push(containerFromCall(call, nameNode.getText()));
210
+ }
211
+ return containers;
212
+ };
213
+ const discoverContainers = (project, lunoraDirectory) => {
214
+ const containersPath = join(lunoraDirectory, CONTAINERS_FILENAME);
215
+ if (!existsSync(containersPath)) {
216
+ return [];
217
+ }
218
+ const source = project.getSourceFile(containersPath) ?? project.addSourceFileAtPath(containersPath);
219
+ const containers = containersFromSource(source);
220
+ containers.sort((a, b) => a.exportName.localeCompare(b.exportName));
221
+ return containers;
222
+ };
223
+
224
+ export { CONTAINERS_FILENAME, discoverContainers };
@@ -0,0 +1,22 @@
1
+ class CodegenDiagnosticError extends Error {
2
+ column;
3
+ file;
4
+ line;
5
+ constructor(message, file, line, column) {
6
+ super(message);
7
+ this.name = "CodegenDiagnosticError";
8
+ this.file = file;
9
+ this.line = line;
10
+ this.column = column;
11
+ }
12
+ }
13
+ const diagnosticAt = (node, detail) => {
14
+ const sourceFile = node.getSourceFile();
15
+ const file = sourceFile.getFilePath();
16
+ const line = node.getStartLineNumber();
17
+ const { column } = sourceFile.getLineAndColumnAtPos(node.getStart());
18
+ const message = `@lunora/codegen: ${detail} (${file}:${line.toString()}:${column.toString()})`;
19
+ return new CodegenDiagnosticError(message, file, line, column);
20
+ };
21
+
22
+ export { CodegenDiagnosticError, diagnosticAt };
@@ -0,0 +1,61 @@
1
+ import { jsonSchemaFromNode, objectSchemaFromNodes } from '@lunora/values';
2
+
3
+ const literalConst = (literalValue) => {
4
+ if (literalValue === void 0) {
5
+ return {};
6
+ }
7
+ const trimmed = literalValue.trim();
8
+ if (trimmed === "true") {
9
+ return { const: true };
10
+ }
11
+ if (trimmed === "false") {
12
+ return { const: false };
13
+ }
14
+ if (trimmed === "null") {
15
+ return { const: null, type: "null" };
16
+ }
17
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
18
+ return { const: trimmed.slice(1, -1), type: "string" };
19
+ }
20
+ const asNumber = Number(trimmed);
21
+ if (!Number.isNaN(asNumber)) {
22
+ return { const: asNumber };
23
+ }
24
+ return { const: trimmed };
25
+ };
26
+ const irReader = {
27
+ constraints: () => void 0,
28
+ inner: (validator) => validator.inner,
29
+ isNullable: (validator) => validator.column?.notNull === false,
30
+ // `ValidatorIR.kind` is a loose `string` (build-time AST); narrow it to the
31
+ // shared reader's `ValidatorKind`. Unknown kinds fall through the mapper's
32
+ // `default` branch to an empty schema, exactly as before.
33
+ kind: (validator) => validator.kind,
34
+ literalSchema: (validator) => literalConst(validator.literalValue),
35
+ members: (validator) => validator.members ?? [],
36
+ shape: (validator) => validator.shape ?? {},
37
+ tableName: (validator) => validator.tableName,
38
+ valueChild: (validator) => validator.valueType
39
+ };
40
+ const validatorIrToJsonSchema = (validator) => jsonSchemaFromNode(validator, irReader);
41
+ const objectSchema = (shape) => objectSchemaFromNodes(shape, irReader);
42
+ const argsObjectSchema = (args) => objectSchema(args);
43
+ const LUNORA_ERROR_CODES = [
44
+ "BAD_REQUEST",
45
+ "CONFLICT",
46
+ "COUNT_RLS_UNSUPPORTED",
47
+ "FORBIDDEN",
48
+ "FUNCTION_NOT_FOUND",
49
+ "INTERNAL",
50
+ "INTERNAL_SERVER_ERROR",
51
+ "METHOD_NOT_ALLOWED",
52
+ "NOT_FOUND",
53
+ "NOT_IMPLEMENTED",
54
+ "PAYLOAD_TOO_LARGE",
55
+ "TOO_MANY_REQUESTS",
56
+ "UNAUTHORIZED",
57
+ "UNPROCESSABLE",
58
+ "VALIDATION_ERROR"
59
+ ];
60
+
61
+ export { LUNORA_ERROR_CODES, argsObjectSchema, literalConst, objectSchema, validatorIrToJsonSchema };
@@ -0,0 +1,183 @@
1
+ import { GENERATED_HEADER } from './emitApi-hRVC-kE7.mjs';
2
+ import { s as sanitizeNamespace } from './paths-BRd6JHuF.mjs';
3
+ import { LUNORA_ERROR_CODES, objectSchema, argsObjectSchema, validatorIrToJsonSchema } from './LUNORA_ERROR_CODES-CySpQPD3.mjs';
4
+
5
+ const ERROR_COMPONENT_REF = "#/components/responses/LunoraError";
6
+ const ROUTE_PARAM_RE = /:([A-Za-z_$][\w$]*)/gu;
7
+ const pathParameterNames = (path) => [...path.matchAll(ROUTE_PARAM_RE)].map((match) => match[1]);
8
+ const toOpenApiPath = (path) => path.replaceAll(ROUTE_PARAM_RE, "{$1}");
9
+ const buildParameters = (route) => {
10
+ const declaredPathNames = new Set(pathParameterNames(route.path));
11
+ const parameters = [];
12
+ for (const [name, validator] of Object.entries(route.searchParams)) {
13
+ const inner = validator.kind === "optional" ? validator.inner ?? validator : validator;
14
+ parameters.push({
15
+ description: `Query parameter \`${name}\``,
16
+ in: "query",
17
+ name,
18
+ required: validator.kind !== "optional",
19
+ schema: validatorIrToJsonSchema(inner)
20
+ });
21
+ }
22
+ for (const [name, validator] of Object.entries(route.params)) {
23
+ const inner = validator.kind === "optional" ? validator.inner ?? validator : validator;
24
+ parameters.push({
25
+ description: `Path parameter \`${name}\``,
26
+ // A path param the template doesn't name is still surfaced (OpenAPI
27
+ // requires path params to be `required: true`), but flagged so the
28
+ // mismatch is visible rather than silently dropped.
29
+ in: declaredPathNames.has(name) ? "path" : "query",
30
+ name,
31
+ required: declaredPathNames.has(name) ? true : validator.kind !== "optional",
32
+ schema: validatorIrToJsonSchema(inner)
33
+ });
34
+ }
35
+ return parameters;
36
+ };
37
+ const successResponse = (output) => {
38
+ if (output) {
39
+ return {
40
+ content: { "application/json": { schema: validatorIrToJsonSchema(output) } },
41
+ description: "Successful response."
42
+ };
43
+ }
44
+ return {
45
+ content: { "application/json": { schema: { description: "Return shape is TS-inferred (no `.output()` declared); best-effort — any JSON." } } },
46
+ description: "Successful response. The return shape is TypeScript-inferred and not declared via `.output()`, so it is documented best-effort."
47
+ };
48
+ };
49
+ const httpRouteOperation = (route) => {
50
+ const tag = sanitizeNamespace(route.filePath);
51
+ const parameters = buildParameters(route);
52
+ const operation = {
53
+ description: `${route.stream ? "Streaming (SSE) " : ""}HTTP route handler \`${route.exportName}\` (${route.method} ${route.path}).`,
54
+ operationId: `${route.method.toLowerCase()}_${sanitizeNamespace(route.path)}`,
55
+ responses: {
56
+ "200": successResponse(route.output),
57
+ "204": { description: "No content (handler returned `undefined`)." },
58
+ default: { $ref: ERROR_COMPONENT_REF }
59
+ },
60
+ summary: `${route.method} ${route.path}`,
61
+ tags: [tag]
62
+ };
63
+ if (parameters.length > 0) {
64
+ operation.parameters = parameters;
65
+ }
66
+ if (Object.keys(route.body).length > 0) {
67
+ operation.requestBody = {
68
+ content: { "application/json": { schema: objectSchema(route.body) } },
69
+ required: true
70
+ };
71
+ }
72
+ if (route.stream) {
73
+ operation["x-lunora-stream"] = "text/event-stream";
74
+ }
75
+ return operation;
76
+ };
77
+ const rpcOperation = (definition) => {
78
+ const functionPath = `${sanitizeNamespace(definition.filePath)}:${definition.exportName}`;
79
+ const tag = sanitizeNamespace(definition.filePath);
80
+ const requestSchema = {
81
+ additionalProperties: false,
82
+ properties: {
83
+ args: argsObjectSchema(definition.args),
84
+ functionPath: { const: functionPath, type: "string" },
85
+ shardKey: { description: "Optional shard key; omitted routes to the default shard.", type: "string" }
86
+ },
87
+ required: ["functionPath"],
88
+ type: "object"
89
+ };
90
+ const operation = {
91
+ description: `Invoke the \`${definition.kind}\` \`${functionPath}\` over the Lunora RPC envelope (POST /_lunora/rpc).`,
92
+ operationId: functionPath,
93
+ requestBody: {
94
+ content: { "application/json": { schema: requestSchema } },
95
+ required: true
96
+ },
97
+ responses: {
98
+ "200": {
99
+ content: {
100
+ "application/json": {
101
+ schema: { description: "RPC result. The shape is TS-inferred from the function's return type; best-effort — any JSON." }
102
+ }
103
+ },
104
+ description: "Successful RPC result (TypeScript-inferred return shape, documented best-effort)."
105
+ },
106
+ default: { $ref: ERROR_COMPONENT_REF }
107
+ },
108
+ summary: `${definition.kind}: ${functionPath}`,
109
+ tags: [tag],
110
+ "x-lunora-function-kind": definition.kind
111
+ };
112
+ return { operation, pathKey: `/_lunora/rpc#${functionPath}` };
113
+ };
114
+ const buildOpenApiDocument = (input) => {
115
+ const version = input.version ?? "0.0.0";
116
+ const paths = {};
117
+ const tagNames = /* @__PURE__ */ new Set();
118
+ for (const route of input.httpRoutes) {
119
+ const openApiPath = toOpenApiPath(route.path);
120
+ const pathItem = paths[openApiPath] ?? {};
121
+ pathItem[route.method.toLowerCase()] = httpRouteOperation(route);
122
+ paths[openApiPath] = pathItem;
123
+ tagNames.add(sanitizeNamespace(route.filePath));
124
+ }
125
+ const rpcFunctions = input.functions.filter((definition) => definition.visibility !== "internal" && definition.kind !== "stream");
126
+ for (const definition of rpcFunctions) {
127
+ const { operation, pathKey } = rpcOperation(definition);
128
+ paths[pathKey] = { post: operation };
129
+ tagNames.add(sanitizeNamespace(definition.filePath));
130
+ }
131
+ const tags = [...tagNames].toSorted((a, b) => a.localeCompare(b)).map((name) => {
132
+ return { description: `Operations declared in \`lunora/${name}\`.`, name };
133
+ });
134
+ const document = {
135
+ components: {
136
+ responses: {
137
+ LunoraError: {
138
+ content: {
139
+ "application/json": {
140
+ schema: {
141
+ additionalProperties: false,
142
+ description: "Standard Lunora error envelope.",
143
+ properties: {
144
+ error: {
145
+ additionalProperties: false,
146
+ properties: {
147
+ code: {
148
+ description: "Machine-readable error code. Clients switch on this value.",
149
+ enum: LUNORA_ERROR_CODES,
150
+ type: "string"
151
+ },
152
+ message: { description: "Human-readable error message (never echoes internal details).", type: "string" }
153
+ },
154
+ required: ["code", "message"],
155
+ type: "object"
156
+ }
157
+ },
158
+ required: ["error"],
159
+ type: "object"
160
+ }
161
+ }
162
+ },
163
+ description: "A Lunora error response. The HTTP status reflects the error code (e.g. BAD_REQUEST→400, UNAUTHORIZED→401, FORBIDDEN→403, NOT_FOUND→404)."
164
+ }
165
+ }
166
+ },
167
+ info: {
168
+ description: "Auto-generated from @lunora/values-typed functions by @lunora/codegen. Do not edit — run `lunora codegen` to regenerate.",
169
+ title: "Lunora API",
170
+ version
171
+ },
172
+ openapi: "3.1.0",
173
+ paths,
174
+ tags
175
+ };
176
+ return document;
177
+ };
178
+ const emitOpenApi = (input) => `${JSON.stringify(buildOpenApiDocument(input), void 0, 2)}
179
+ `;
180
+ const emitOpenApiModule = (document_) => `${GENERATED_HEADER}export const openApiSpec: Record<string, unknown> = ${JSON.stringify(document_, void 0, 4)};
181
+ `;
182
+
183
+ export { buildOpenApiDocument, emitOpenApi, emitOpenApiModule };
@@ -0,0 +1,60 @@
1
+ import { GENERATED_HEADER } from './emitApi-hRVC-kE7.mjs';
2
+ import { s as sanitizeNamespace } from './paths-BRd6JHuF.mjs';
3
+ import { argsObjectSchema, LUNORA_ERROR_CODES } from './LUNORA_ERROR_CODES-CySpQPD3.mjs';
4
+
5
+ const OPENRPC_VERSION = "1.3.2";
6
+ const inferredResultSchema = () => {
7
+ return { description: "Result is TS-inferred from the function's return type (no `.output()` declared); best-effort — any JSON." };
8
+ };
9
+ const rpcMethod = (definition) => {
10
+ const namespace = sanitizeNamespace(definition.filePath);
11
+ const functionPath = `${namespace}:${definition.exportName}`;
12
+ return {
13
+ description: `Invoke the \`${definition.kind}\` \`${functionPath}\` over the Lunora RPC envelope (POST /_lunora/rpc, body \`{ "functionPath": "${functionPath}", "args": { … } }\`).`,
14
+ errors: LUNORA_ERROR_CODES.map((code, index) => {
15
+ return { code: -32e3 - index, data: { code }, message: code };
16
+ }),
17
+ name: functionPath,
18
+ params: [
19
+ {
20
+ description: "The function's argument object (the RPC envelope's `args`).",
21
+ name: "args",
22
+ // A function with no declared args still takes an (empty) args
23
+ // object; `required` reflects whether the function declares any
24
+ // arguments at all.
25
+ required: Object.keys(definition.args).length > 0,
26
+ schema: argsObjectSchema(definition.args)
27
+ }
28
+ ],
29
+ result: {
30
+ name: "result",
31
+ schema: inferredResultSchema()
32
+ },
33
+ summary: `${definition.kind}: ${functionPath}`,
34
+ "x-lunora-function-kind": definition.kind,
35
+ // OpenRPC has no first-class tag grouping; surface the file namespace as
36
+ // an `x-tags` extension so tooling can group methods by source file.
37
+ "x-tags": [{ name: namespace }]
38
+ };
39
+ };
40
+ const buildOpenRpcDocument = (input) => {
41
+ const version = input.version ?? "0.0.0";
42
+ const rpcFunctions = input.functions.filter((definition) => definition.visibility !== "internal" && definition.kind !== "stream");
43
+ const methods = rpcFunctions.map((definition) => rpcMethod(definition)).toSorted((a, b) => a.name.localeCompare(b.name));
44
+ const document = {
45
+ info: {
46
+ description: "Auto-generated from @lunora/values-typed functions by @lunora/codegen. Do not edit — run `lunora codegen` to regenerate.",
47
+ title: "Lunora RPC",
48
+ version
49
+ },
50
+ methods,
51
+ openrpc: OPENRPC_VERSION
52
+ };
53
+ return document;
54
+ };
55
+ const emitOpenRpc = (input) => `${JSON.stringify(buildOpenRpcDocument(input), void 0, 2)}
56
+ `;
57
+ const emitOpenRpcModule = (document_) => `${GENERATED_HEADER}export const openRpcSpec: Record<string, unknown> = ${JSON.stringify(document_, void 0, 4)};
58
+ `;
59
+
60
+ export { OPENRPC_VERSION, buildOpenRpcDocument, emitOpenRpc, emitOpenRpcModule };