@lunora/codegen 1.0.0-alpha.55 → 1.0.0-alpha.57
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/index.d.mts +59 -5
- package/dist/index.d.ts +59 -5
- package/dist/index.mjs +1 -38
- package/dist/packem_shared/AGENTS_FILENAME-BesaDYkL.mjs +1 -0
- package/dist/packem_shared/CONTAINERS_FILENAME-CFwdyaCI.mjs +1 -0
- package/dist/packem_shared/CodegenDiagnosticError-DPezpZTz.mjs +1 -0
- package/dist/packem_shared/FLAGS_FILENAME-DW5oGq1q.mjs +1 -0
- package/dist/packem_shared/GENERATED_HEADER-hlHbtIBb.mjs +1 -0
- package/dist/packem_shared/LUNORA_ERROR_CODES-Um9hC1gr.mjs +1 -0
- package/dist/packem_shared/MUTATORS_FILENAME-BKKPROzu.mjs +1 -0
- package/dist/packem_shared/NOTIFY_FILENAME-C-c7Lg-U.mjs +1 -0
- package/dist/packem_shared/OPENRPC_VERSION-DWoqALFJ.mjs +3 -0
- package/dist/packem_shared/QUEUES_FILENAME-CSE6i-ld.mjs +1 -0
- package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-Cuf8KyVF.mjs +3 -0
- package/dist/packem_shared/SCHEMA_SNAPSHOT_VERSION-Bpf1FGMG.mjs +15 -0
- package/dist/packem_shared/SHAPES_FILENAME-DTkSJSsg.mjs +1 -0
- package/dist/packem_shared/WORKFLOWS_FILENAME-Bk5upkGM.mjs +1 -0
- package/dist/packem_shared/buildOpenApiDocument-BRCc5AIx.mjs +3 -0
- package/dist/packem_shared/discover-ast-CezKqEhE.mjs +1 -0
- package/dist/packem_shared/discoverAuthApiCalls-DOfSQqVK.mjs +1 -0
- package/dist/packem_shared/discoverCrons-B09_uEuL.mjs +1 -0
- package/dist/packem_shared/discoverFunctions-BdibLQmc.mjs +1 -0
- package/dist/packem_shared/discoverHttpRoutes-BOWz6y_n.mjs +1 -0
- package/dist/packem_shared/discoverInserts-BtteTyNy.mjs +1 -0
- package/dist/packem_shared/discoverMaskProcedures-COPmwMBm.mjs +1 -0
- package/dist/packem_shared/discoverMigrations-iGMlp9YJ.mjs +1 -0
- package/dist/packem_shared/discoverNondeterministicCalls-Dmgc4QBx.mjs +1 -0
- package/dist/packem_shared/discoverQueries-CcfuGKHh.mjs +1 -0
- package/dist/packem_shared/discoverR2sqlCalls-CjZKvg-N.mjs +1 -0
- package/dist/packem_shared/discoverRlsMetadata-BXmNOgry.mjs +1 -0
- package/dist/packem_shared/discoverSandboxUsage-CDdBqWcM.mjs +1 -0
- package/dist/packem_shared/discoverSchema-m2voTncu.mjs +1 -0
- package/dist/packem_shared/discoverStorageRulesMetadata-b71cJJHz.mjs +1 -0
- package/dist/packem_shared/emit-BYVxPDqO.mjs +2127 -0
- package/dist/packem_shared/emitApp-CHCOfQer.mjs +413 -0
- package/dist/packem_shared/formatAdvisories-DZvv-0MG.mjs +2 -0
- package/dist/packem_shared/parse-validator-CSDlMEvE.mjs +1 -0
- package/dist/packem_shared/paths-BmX5O1sG.mjs +1 -0
- package/dist/packem_shared/redact-CQ8-to6l.mjs +1 -0
- package/dist/packem_shared/schemaFromIr-R1ZFzVyy.mjs +1 -0
- package/package.json +3 -3
- package/dist/packem_shared/AGENTS_FILENAME-Cxx7wiSs.mjs +0 -158
- package/dist/packem_shared/CONTAINERS_FILENAME-DjpXMqhp.mjs +0 -224
- package/dist/packem_shared/CodegenDiagnosticError-DyQ5FwkM.mjs +0 -24
- package/dist/packem_shared/FLAGS_FILENAME-D5igzqwR.mjs +0 -139
- package/dist/packem_shared/GENERATED_HEADER-C8tAvxqP.mjs +0 -4
- package/dist/packem_shared/LUNORA_ERROR_CODES-DvTLozCu.mjs +0 -68
- package/dist/packem_shared/MUTATORS_FILENAME-BZOfUhlY.mjs +0 -81
- package/dist/packem_shared/NOTIFY_FILENAME-CK2IrsrP.mjs +0 -152
- package/dist/packem_shared/OPENRPC_VERSION-DvDChMhC.mjs +0 -60
- package/dist/packem_shared/QUEUES_FILENAME-Xv6Vy-Ku.mjs +0 -145
- package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-YAL6GrxI.mjs +0 -3597
- package/dist/packem_shared/SCHEMA_SNAPSHOT_VERSION-DGD73E_n.mjs +0 -252
- package/dist/packem_shared/SHAPES_FILENAME-DOhPGi-6.mjs +0 -94
- package/dist/packem_shared/WORKFLOWS_FILENAME-Dtuzvluc.mjs +0 -158
- package/dist/packem_shared/buildOpenApiDocument-DCQSLuFc.mjs +0 -262
- package/dist/packem_shared/discover-ast-CT6BgBr4.mjs +0 -13
- package/dist/packem_shared/discoverAuthApiCalls-Dhwgf4J4.mjs +0 -62
- package/dist/packem_shared/discoverCrons-UbQLLmMS.mjs +0 -286
- package/dist/packem_shared/discoverFunctions-COG_Xkyz.mjs +0 -544
- package/dist/packem_shared/discoverHttpRoutes-CVXWZkzo.mjs +0 -139
- package/dist/packem_shared/discoverInserts-DdWnXmLS.mjs +0 -61
- package/dist/packem_shared/discoverMaskProcedures-BG58_fw3.mjs +0 -273
- package/dist/packem_shared/discoverMigrations-Bw1U01zr.mjs +0 -97
- package/dist/packem_shared/discoverNondeterministicCalls-BbXEFZUT.mjs +0 -122
- package/dist/packem_shared/discoverQueries-CDmxu2ET.mjs +0 -62
- package/dist/packem_shared/discoverR2sqlCalls-Bn7QVv7J.mjs +0 -80
- package/dist/packem_shared/discoverRlsMetadata-D9uN35no.mjs +0 -280
- package/dist/packem_shared/discoverSandboxUsage-B5jDU6Bt.mjs +0 -38
- package/dist/packem_shared/discoverSchema-BJp5_T4o.mjs +0 -906
- package/dist/packem_shared/discoverStorageRulesMetadata-BqMhuEjo.mjs +0 -97
- package/dist/packem_shared/emit-DIbr7U31.mjs +0 -3884
- package/dist/packem_shared/emitApp-DbMM8iHe.mjs +0 -706
- package/dist/packem_shared/formatAdvisories-Cans5TWS.mjs +0 -131
- package/dist/packem_shared/parse-validator-BDGV8iue.mjs +0 -144
- package/dist/packem_shared/paths-BRd6JHuF.mjs +0 -11
- package/dist/packem_shared/redact-oTmsol5A.mjs +0 -33
- package/dist/packem_shared/schemaFromIr-DTYsLBaA.mjs +0 -57
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
import { G as GENERATED_HEADER } from './emit-DIbr7U31.mjs';
|
|
2
|
-
import { s as sanitizeNamespace } from './paths-BRd6JHuF.mjs';
|
|
3
|
-
import { LUNORA_ERROR_CODES, objectSchema, argsObjectSchema, validatorIrToJsonSchema } from './LUNORA_ERROR_CODES-DvTLozCu.mjs';
|
|
4
|
-
|
|
5
|
-
const REST_PATH_PREFIX = "/_lunora/rest";
|
|
6
|
-
const splitFunctionPath = (functionPath) => {
|
|
7
|
-
const colon = functionPath.indexOf(":");
|
|
8
|
-
if (colon <= 0 || colon >= functionPath.length - 1 || functionPath.indexOf(":", colon + 1) !== -1) {
|
|
9
|
-
return void 0;
|
|
10
|
-
}
|
|
11
|
-
return { name: functionPath.slice(colon + 1), namespace: functionPath.slice(0, colon) };
|
|
12
|
-
};
|
|
13
|
-
const restPathForFunction = (functionPath) => {
|
|
14
|
-
const parts = splitFunctionPath(functionPath);
|
|
15
|
-
if (parts === void 0) {
|
|
16
|
-
return void 0;
|
|
17
|
-
}
|
|
18
|
-
return `${REST_PATH_PREFIX}/${parts.namespace}/${parts.name}`;
|
|
19
|
-
};
|
|
20
|
-
const restMethodForKind = (kind) => kind === "query" ? "GET" : "POST";
|
|
21
|
-
|
|
22
|
-
const ERROR_COMPONENT_REF = "#/components/responses/LunoraError";
|
|
23
|
-
const ROUTE_PARAM_RE = /:([A-Za-z_$][\w$]*)/gu;
|
|
24
|
-
const pathParameterNames = (path) => [...path.matchAll(ROUTE_PARAM_RE)].map((match) => match[1]);
|
|
25
|
-
const toOpenApiPath = (path) => path.replaceAll(ROUTE_PARAM_RE, "{$1}");
|
|
26
|
-
const buildParameters = (route) => {
|
|
27
|
-
const declaredPathNames = new Set(pathParameterNames(route.path));
|
|
28
|
-
const parameters = [];
|
|
29
|
-
for (const [name, validator] of Object.entries(route.searchParams)) {
|
|
30
|
-
const inner = validator.kind === "optional" ? validator.inner ?? validator : validator;
|
|
31
|
-
parameters.push({
|
|
32
|
-
description: `Query parameter \`${name}\``,
|
|
33
|
-
in: "query",
|
|
34
|
-
name,
|
|
35
|
-
required: validator.kind !== "optional",
|
|
36
|
-
schema: validatorIrToJsonSchema(inner)
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
for (const [name, validator] of Object.entries(route.params)) {
|
|
40
|
-
const inner = validator.kind === "optional" ? validator.inner ?? validator : validator;
|
|
41
|
-
parameters.push({
|
|
42
|
-
description: `Path parameter \`${name}\``,
|
|
43
|
-
// A path param the template doesn't name is still surfaced (OpenAPI
|
|
44
|
-
// requires path params to be `required: true`), but flagged so the
|
|
45
|
-
// mismatch is visible rather than silently dropped.
|
|
46
|
-
in: declaredPathNames.has(name) ? "path" : "query",
|
|
47
|
-
name,
|
|
48
|
-
required: declaredPathNames.has(name) ? true : validator.kind !== "optional",
|
|
49
|
-
schema: validatorIrToJsonSchema(inner)
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
return parameters;
|
|
53
|
-
};
|
|
54
|
-
const successResponse = (output) => {
|
|
55
|
-
if (output) {
|
|
56
|
-
return {
|
|
57
|
-
content: { "application/json": { schema: validatorIrToJsonSchema(output) } },
|
|
58
|
-
description: "Successful response."
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
return {
|
|
62
|
-
content: { "application/json": { schema: { description: "Return shape is TS-inferred (no `.output()` declared); best-effort — any JSON." } } },
|
|
63
|
-
description: "Successful response. The return shape is TypeScript-inferred and not declared via `.output()`, so it is documented best-effort."
|
|
64
|
-
};
|
|
65
|
-
};
|
|
66
|
-
const httpRouteOperation = (route) => {
|
|
67
|
-
const tag = sanitizeNamespace(route.filePath);
|
|
68
|
-
const parameters = buildParameters(route);
|
|
69
|
-
const operation = {
|
|
70
|
-
description: `${route.stream ? "Streaming (SSE) " : ""}HTTP route handler \`${route.exportName}\` (${route.method} ${route.path}).`,
|
|
71
|
-
operationId: `${route.method.toLowerCase()}_${sanitizeNamespace(route.path)}`,
|
|
72
|
-
responses: {
|
|
73
|
-
"200": successResponse(route.output),
|
|
74
|
-
"204": { description: "No content (handler returned `undefined`)." },
|
|
75
|
-
default: { $ref: ERROR_COMPONENT_REF }
|
|
76
|
-
},
|
|
77
|
-
summary: `${route.method} ${route.path}`,
|
|
78
|
-
tags: [tag]
|
|
79
|
-
};
|
|
80
|
-
if (parameters.length > 0) {
|
|
81
|
-
operation.parameters = parameters;
|
|
82
|
-
}
|
|
83
|
-
if (Object.keys(route.body).length > 0) {
|
|
84
|
-
operation.requestBody = {
|
|
85
|
-
content: { "application/json": { schema: objectSchema(route.body) } },
|
|
86
|
-
required: true
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
if (route.stream) {
|
|
90
|
-
operation["x-lunora-stream"] = "text/event-stream";
|
|
91
|
-
}
|
|
92
|
-
return operation;
|
|
93
|
-
};
|
|
94
|
-
const rpcOperation = (definition) => {
|
|
95
|
-
const functionPath = `${sanitizeNamespace(definition.filePath)}:${definition.exportName}`;
|
|
96
|
-
const tag = sanitizeNamespace(definition.filePath);
|
|
97
|
-
const requestSchema = {
|
|
98
|
-
additionalProperties: false,
|
|
99
|
-
properties: {
|
|
100
|
-
args: argsObjectSchema(definition.args),
|
|
101
|
-
functionPath: { const: functionPath, type: "string" },
|
|
102
|
-
shardKey: { description: "Optional shard key; omitted routes to the default shard.", type: "string" }
|
|
103
|
-
},
|
|
104
|
-
required: ["functionPath"],
|
|
105
|
-
type: "object"
|
|
106
|
-
};
|
|
107
|
-
const operation = {
|
|
108
|
-
description: `Invoke the \`${definition.kind}\` \`${functionPath}\` over the Lunora RPC envelope (POST /_lunora/rpc).`,
|
|
109
|
-
operationId: functionPath,
|
|
110
|
-
requestBody: {
|
|
111
|
-
content: { "application/json": { schema: requestSchema } },
|
|
112
|
-
required: true
|
|
113
|
-
},
|
|
114
|
-
responses: {
|
|
115
|
-
"200": {
|
|
116
|
-
content: {
|
|
117
|
-
"application/json": {
|
|
118
|
-
schema: { description: "RPC result. The shape is TS-inferred from the function's return type; best-effort — any JSON." }
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
description: "Successful RPC result (TypeScript-inferred return shape, documented best-effort)."
|
|
122
|
-
},
|
|
123
|
-
default: { $ref: ERROR_COMPONENT_REF }
|
|
124
|
-
},
|
|
125
|
-
summary: `${definition.kind}: ${functionPath}`,
|
|
126
|
-
tags: [tag],
|
|
127
|
-
"x-lunora-function-kind": definition.kind
|
|
128
|
-
};
|
|
129
|
-
return { operation, pathKey: `/_lunora/rpc#${functionPath}` };
|
|
130
|
-
};
|
|
131
|
-
const restOperation = (definition) => {
|
|
132
|
-
const functionPath = `${sanitizeNamespace(definition.filePath)}:${definition.exportName}`;
|
|
133
|
-
const path = restPathForFunction(functionPath);
|
|
134
|
-
if (path === void 0) {
|
|
135
|
-
return void 0;
|
|
136
|
-
}
|
|
137
|
-
const tag = sanitizeNamespace(definition.filePath);
|
|
138
|
-
const method = restMethodForKind(definition.kind);
|
|
139
|
-
const isQuery = method === "GET";
|
|
140
|
-
const operation = {
|
|
141
|
-
description: `Public REST endpoint for the \`${definition.kind}\` \`${functionPath}\` (opt-in via \`.expose({ rest: true })\`). Routed through the procedure, so auth / RLS / validators are enforced.`,
|
|
142
|
-
operationId: `rest_${sanitizeNamespace(path)}`,
|
|
143
|
-
responses: {
|
|
144
|
-
"200": {
|
|
145
|
-
content: {
|
|
146
|
-
"application/json": {
|
|
147
|
-
schema: { description: "Procedure result. The shape is TS-inferred from the return type; best-effort — any JSON." }
|
|
148
|
-
}
|
|
149
|
-
},
|
|
150
|
-
description: "Successful result (TypeScript-inferred return shape, documented best-effort)."
|
|
151
|
-
},
|
|
152
|
-
default: { $ref: ERROR_COMPONENT_REF }
|
|
153
|
-
},
|
|
154
|
-
summary: `${method} ${path}`,
|
|
155
|
-
tags: [tag],
|
|
156
|
-
"x-lunora-function-kind": definition.kind
|
|
157
|
-
};
|
|
158
|
-
if (isQuery) {
|
|
159
|
-
const parameters = Object.entries(definition.args).map(([name, validator]) => {
|
|
160
|
-
const inner = validator.kind === "optional" ? validator.inner ?? validator : validator;
|
|
161
|
-
return {
|
|
162
|
-
description: `Argument \`${name}\` (JSON-encoded for non-string values).`,
|
|
163
|
-
in: "query",
|
|
164
|
-
name,
|
|
165
|
-
required: validator.kind !== "optional",
|
|
166
|
-
schema: validatorIrToJsonSchema(inner)
|
|
167
|
-
};
|
|
168
|
-
});
|
|
169
|
-
if (parameters.length > 0) {
|
|
170
|
-
operation.parameters = parameters;
|
|
171
|
-
}
|
|
172
|
-
return { method: "get", operation, path };
|
|
173
|
-
}
|
|
174
|
-
operation.requestBody = {
|
|
175
|
-
content: { "application/json": { schema: argsObjectSchema(definition.args) } },
|
|
176
|
-
required: Object.keys(definition.args).length > 0
|
|
177
|
-
};
|
|
178
|
-
return { method: "post", operation, path };
|
|
179
|
-
};
|
|
180
|
-
const buildOpenApiDocument = (input) => {
|
|
181
|
-
const version = input.version ?? "0.0.0";
|
|
182
|
-
const paths = {};
|
|
183
|
-
const tagNames = /* @__PURE__ */ new Set();
|
|
184
|
-
for (const route of input.httpRoutes) {
|
|
185
|
-
const openApiPath = toOpenApiPath(route.path);
|
|
186
|
-
const pathItem = paths[openApiPath] ?? {};
|
|
187
|
-
pathItem[route.method.toLowerCase()] = httpRouteOperation(route);
|
|
188
|
-
paths[openApiPath] = pathItem;
|
|
189
|
-
tagNames.add(sanitizeNamespace(route.filePath));
|
|
190
|
-
}
|
|
191
|
-
const rpcFunctions = input.functions.filter((definition) => definition.visibility !== "internal" && definition.kind !== "stream");
|
|
192
|
-
for (const definition of rpcFunctions) {
|
|
193
|
-
const { operation, pathKey } = rpcOperation(definition);
|
|
194
|
-
paths[pathKey] = { post: operation };
|
|
195
|
-
tagNames.add(sanitizeNamespace(definition.filePath));
|
|
196
|
-
}
|
|
197
|
-
for (const definition of rpcFunctions) {
|
|
198
|
-
if (definition.expose?.rest !== true) {
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
const rest = restOperation(definition);
|
|
202
|
-
if (rest === void 0) {
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
const pathItem = paths[rest.path] ?? {};
|
|
206
|
-
pathItem[rest.method] = rest.operation;
|
|
207
|
-
paths[rest.path] = pathItem;
|
|
208
|
-
tagNames.add(sanitizeNamespace(definition.filePath));
|
|
209
|
-
}
|
|
210
|
-
const tags = [...tagNames].toSorted((a, b) => a.localeCompare(b)).map((name) => {
|
|
211
|
-
return { description: `Operations declared in \`lunora/${name}\`.`, name };
|
|
212
|
-
});
|
|
213
|
-
const document = {
|
|
214
|
-
components: {
|
|
215
|
-
responses: {
|
|
216
|
-
LunoraError: {
|
|
217
|
-
content: {
|
|
218
|
-
"application/json": {
|
|
219
|
-
schema: {
|
|
220
|
-
additionalProperties: false,
|
|
221
|
-
description: "Standard Lunora error envelope.",
|
|
222
|
-
properties: {
|
|
223
|
-
error: {
|
|
224
|
-
additionalProperties: false,
|
|
225
|
-
properties: {
|
|
226
|
-
code: {
|
|
227
|
-
description: "Machine-readable error code. Clients switch on this value.",
|
|
228
|
-
enum: LUNORA_ERROR_CODES,
|
|
229
|
-
type: "string"
|
|
230
|
-
},
|
|
231
|
-
message: { description: "Human-readable error message (never echoes internal details).", type: "string" }
|
|
232
|
-
},
|
|
233
|
-
required: ["code", "message"],
|
|
234
|
-
type: "object"
|
|
235
|
-
}
|
|
236
|
-
},
|
|
237
|
-
required: ["error"],
|
|
238
|
-
type: "object"
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
},
|
|
242
|
-
description: "A Lunora error response. The HTTP status reflects the error code (e.g. BAD_REQUEST→400, UNAUTHORIZED→401, FORBIDDEN→403, NOT_FOUND→404)."
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
},
|
|
246
|
-
info: {
|
|
247
|
-
description: "Auto-generated from @lunora/values-typed functions by @lunora/codegen. Do not edit — run `lunora codegen` to regenerate.",
|
|
248
|
-
title: "Lunora API",
|
|
249
|
-
version
|
|
250
|
-
},
|
|
251
|
-
openapi: "3.1.0",
|
|
252
|
-
paths,
|
|
253
|
-
tags
|
|
254
|
-
};
|
|
255
|
-
return document;
|
|
256
|
-
};
|
|
257
|
-
const emitOpenApi = (input) => `${JSON.stringify(buildOpenApiDocument(input), void 0, 2)}
|
|
258
|
-
`;
|
|
259
|
-
const emitOpenApiModule = (document_) => `${GENERATED_HEADER}export const openApiSpec: Record<string, unknown> = ${JSON.stringify(document_, void 0, 4)};
|
|
260
|
-
`;
|
|
261
|
-
|
|
262
|
-
export { buildOpenApiDocument, emitOpenApi, emitOpenApiModule };
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Node } from 'ts-morph';
|
|
2
|
-
|
|
3
|
-
const TS_EXTENSION_RE = /\.ts$/u;
|
|
4
|
-
const enclosingExportName = (call) => {
|
|
5
|
-
for (const ancestor of call.getAncestors()) {
|
|
6
|
-
if (Node.isVariableDeclaration(ancestor) && ancestor.getVariableStatement()?.hasExportKeyword() === true) {
|
|
7
|
-
return ancestor.getName();
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
return "";
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export { TS_EXTENSION_RE as T, enclosingExportName as e };
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { relative, sep } from 'node:path';
|
|
2
|
-
import { SyntaxKind, Node } from 'ts-morph';
|
|
3
|
-
import { T as TS_EXTENSION_RE, e as enclosingExportName } from './discover-ast-CT6BgBr4.mjs';
|
|
4
|
-
import { listLunoraSourceFiles } from './discoverFunctions-COG_Xkyz.mjs';
|
|
5
|
-
|
|
6
|
-
const isAuthApiCall = (call) => {
|
|
7
|
-
const callee = call.getExpression();
|
|
8
|
-
if (!Node.isPropertyAccessExpression(callee)) {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
const receiver = callee.getExpression();
|
|
12
|
-
if (Node.isPropertyAccessExpression(receiver)) {
|
|
13
|
-
return receiver.getName() === "authApi";
|
|
14
|
-
}
|
|
15
|
-
return Node.isIdentifier(receiver) && receiver.getText() === "authApi";
|
|
16
|
-
};
|
|
17
|
-
const hasHeadersProp = (call) => {
|
|
18
|
-
const argument = call.getArguments()[0];
|
|
19
|
-
if (argument === void 0) {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
if (!Node.isObjectLiteralExpression(argument)) {
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
for (const property of argument.getProperties()) {
|
|
26
|
-
if ((Node.isPropertyAssignment(property) || Node.isShorthandPropertyAssignment(property)) && property.getName() === "headers") {
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
if (Node.isSpreadAssignment(property)) {
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return false;
|
|
34
|
-
};
|
|
35
|
-
const discoverAuthApiCalls = (project, lunoraDirectory) => {
|
|
36
|
-
const calls = [];
|
|
37
|
-
for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
|
|
38
|
-
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
|
|
39
|
-
const relativePath = relative(lunoraDirectory, filePath).replace(TS_EXTENSION_RE, "").split(sep).join("/");
|
|
40
|
-
for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
41
|
-
if (!isAuthApiCall(call)) {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
const exportName = enclosingExportName(call);
|
|
45
|
-
if (exportName === "") {
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
const callee = call.getExpression();
|
|
49
|
-
const method = Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
50
|
-
calls.push({
|
|
51
|
-
exportName,
|
|
52
|
-
file: relativePath,
|
|
53
|
-
hasHeaders: hasHeadersProp(call),
|
|
54
|
-
line: call.getStartLineNumber(),
|
|
55
|
-
method
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return calls;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
export { discoverAuthApiCalls as default };
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
import { isValidCronExpression, compileCronSchedule, CRON_SCHEDULE_KINDS } from '@lunora/scheduler';
|
|
3
|
-
import { SyntaxKind, Node } from 'ts-morph';
|
|
4
|
-
import { diagnosticAt } from './CodegenDiagnosticError-DyQ5FwkM.mjs';
|
|
5
|
-
import { listLunoraSourceFiles } from './discoverFunctions-COG_Xkyz.mjs';
|
|
6
|
-
import { s as sanitizeNamespace } from './paths-BRd6JHuF.mjs';
|
|
7
|
-
|
|
8
|
-
const CRON_METHODS = /* @__PURE__ */ new Set([...CRON_SCHEDULE_KINDS, "cron"]);
|
|
9
|
-
const CRON_JOBS_SOURCES = /* @__PURE__ */ new Set(["@lunora/scheduler", "@lunora/server"]);
|
|
10
|
-
const isCronJobsFactory = (identifier) => {
|
|
11
|
-
const symbol = identifier.getSymbol();
|
|
12
|
-
if (!symbol) {
|
|
13
|
-
return identifier.getText() === "cronJobs";
|
|
14
|
-
}
|
|
15
|
-
for (const declaration of symbol.getDeclarations()) {
|
|
16
|
-
if (!Node.isImportSpecifier(declaration)) {
|
|
17
|
-
continue;
|
|
18
|
-
}
|
|
19
|
-
if (!CRON_JOBS_SOURCES.has(declaration.getImportDeclaration().getModuleSpecifierValue())) {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
return declaration.getNameNode().getText() === "cronJobs";
|
|
23
|
-
}
|
|
24
|
-
return false;
|
|
25
|
-
};
|
|
26
|
-
const collectCronBuilderNames = (source) => {
|
|
27
|
-
const names = /* @__PURE__ */ new Set();
|
|
28
|
-
for (const declaration of source.getVariableDeclarations()) {
|
|
29
|
-
const initializer = declaration.getInitializer();
|
|
30
|
-
if (initializer?.getKind() !== SyntaxKind.CallExpression) {
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
const callee = initializer.getExpression();
|
|
34
|
-
if (Node.isIdentifier(callee) && isCronJobsFactory(callee)) {
|
|
35
|
-
names.add(declaration.getName());
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return names;
|
|
39
|
-
};
|
|
40
|
-
const rootIdentifierOf = (node) => {
|
|
41
|
-
let current = node;
|
|
42
|
-
while (Node.isCallExpression(current) || Node.isPropertyAccessExpression(current)) {
|
|
43
|
-
current = current.getExpression();
|
|
44
|
-
}
|
|
45
|
-
return Node.isIdentifier(current) ? current.getText() : void 0;
|
|
46
|
-
};
|
|
47
|
-
const stringArgument = (call, index) => {
|
|
48
|
-
const argument = call.getArguments()[index];
|
|
49
|
-
return argument && (Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument)) ? argument.getLiteralValue() : void 0;
|
|
50
|
-
};
|
|
51
|
-
const literalValue = (node, jobName) => {
|
|
52
|
-
if (Node.isStringLiteral(node) || Node.isNoSubstitutionTemplateLiteral(node)) {
|
|
53
|
-
return node.getLiteralValue();
|
|
54
|
-
}
|
|
55
|
-
if (Node.isNumericLiteral(node)) {
|
|
56
|
-
return node.getLiteralValue();
|
|
57
|
-
}
|
|
58
|
-
if (Node.isTrueLiteral(node)) {
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
if (Node.isFalseLiteral(node)) {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
if (Node.isPrefixUnaryExpression(node) && node.getOperatorToken() === SyntaxKind.MinusToken) {
|
|
65
|
-
const operand = node.getOperand();
|
|
66
|
-
if (Node.isNumericLiteral(operand)) {
|
|
67
|
-
return -operand.getLiteralValue();
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
if (Node.isObjectLiteralExpression(node)) {
|
|
71
|
-
return objectLiteralValue(node, jobName);
|
|
72
|
-
}
|
|
73
|
-
if (Node.isArrayLiteralExpression(node)) {
|
|
74
|
-
return node.getElements().map((element) => literalValue(element, jobName));
|
|
75
|
-
}
|
|
76
|
-
throw diagnosticAt(node, `Cron job "${jobName}" passes a non-static value where a literal is required; codegen can only read literals.`, {
|
|
77
|
-
code: "CRON_NON_STATIC_VALUE",
|
|
78
|
-
name: "LunoraError",
|
|
79
|
-
status: 500
|
|
80
|
-
});
|
|
81
|
-
};
|
|
82
|
-
const objectLiteralValue = (object, jobName) => {
|
|
83
|
-
const result = {};
|
|
84
|
-
for (const property of object.getProperties()) {
|
|
85
|
-
if (!Node.isPropertyAssignment(property)) {
|
|
86
|
-
throw diagnosticAt(
|
|
87
|
-
property,
|
|
88
|
-
`Cron job "${jobName}" uses an unsupported object property (shorthand/spread/method) where a static literal is required.`,
|
|
89
|
-
{
|
|
90
|
-
code: "CRON_NON_STATIC_VALUE",
|
|
91
|
-
name: "LunoraError",
|
|
92
|
-
status: 500
|
|
93
|
-
}
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
const initializer = property.getInitializer();
|
|
97
|
-
if (initializer) {
|
|
98
|
-
result[property.getName()] = literalValue(initializer, jobName);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return result;
|
|
102
|
-
};
|
|
103
|
-
const functionPathFromArgument = (call, index, jobName) => {
|
|
104
|
-
const argument = call.getArguments()[index];
|
|
105
|
-
if (!argument || !Node.isPropertyAccessExpression(argument)) {
|
|
106
|
-
throw diagnosticAt(
|
|
107
|
-
call,
|
|
108
|
-
`Cron job "${jobName}" must reference a function statically (e.g. internal.email.digest); codegen cannot resolve a dynamic reference.`,
|
|
109
|
-
{
|
|
110
|
-
code: "CRON_NON_STATIC_FN",
|
|
111
|
-
name: "LunoraError",
|
|
112
|
-
status: 500
|
|
113
|
-
}
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
const functionName = argument.getName();
|
|
117
|
-
const receiver = argument.getExpression();
|
|
118
|
-
if (!Node.isPropertyAccessExpression(receiver)) {
|
|
119
|
-
throw diagnosticAt(argument, `Cron job "${jobName}" function reference must be of the form internal.file.fn (two property accesses).`, {
|
|
120
|
-
code: "CRON_NON_STATIC_FN",
|
|
121
|
-
name: "LunoraError",
|
|
122
|
-
status: 500
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
const namespace = receiver.getName();
|
|
126
|
-
return `${sanitizeNamespace(namespace)}:${functionName}`;
|
|
127
|
-
};
|
|
128
|
-
const workflowTarget = (workflow) => {
|
|
129
|
-
return { workflow: { binding: workflow.bindingName, exportName: workflow.exportName } };
|
|
130
|
-
};
|
|
131
|
-
const agentTarget = (agent) => {
|
|
132
|
-
return { workflow: { binding: agent.bindingName, exportName: agent.exportName } };
|
|
133
|
-
};
|
|
134
|
-
const resolveReferenceAccess = (argument, receiverName, byName, jobName, kind, build) => {
|
|
135
|
-
const receiver = argument.getExpression();
|
|
136
|
-
if (!Node.isIdentifier(receiver) || receiver.getText() !== receiverName) {
|
|
137
|
-
return void 0;
|
|
138
|
-
}
|
|
139
|
-
const definition = byName.get(argument.getName());
|
|
140
|
-
if (definition) {
|
|
141
|
-
return build(definition);
|
|
142
|
-
}
|
|
143
|
-
throw diagnosticAt(
|
|
144
|
-
argument,
|
|
145
|
-
`Cron job "${jobName}" targets ${receiverName}.${argument.getName()}, but no such ${kind} is declared in lunora/${kind}s.ts.`,
|
|
146
|
-
{
|
|
147
|
-
code: "CRON_NON_STATIC_FN",
|
|
148
|
-
name: "LunoraError",
|
|
149
|
-
status: 500
|
|
150
|
-
}
|
|
151
|
-
);
|
|
152
|
-
};
|
|
153
|
-
const resolveTarget = (call, index, jobName, workflowsByName, agentsByName) => {
|
|
154
|
-
const argument = call.getArguments()[index];
|
|
155
|
-
if (argument && Node.isPropertyAccessExpression(argument)) {
|
|
156
|
-
const workflowReference = resolveReferenceAccess(argument, "workflows", workflowsByName, jobName, "workflow", workflowTarget);
|
|
157
|
-
if (workflowReference) {
|
|
158
|
-
return workflowReference;
|
|
159
|
-
}
|
|
160
|
-
const agentReference = resolveReferenceAccess(argument, "agents", agentsByName, jobName, "agent", agentTarget);
|
|
161
|
-
if (agentReference) {
|
|
162
|
-
return agentReference;
|
|
163
|
-
}
|
|
164
|
-
return { functionPath: functionPathFromArgument(call, index, jobName) };
|
|
165
|
-
}
|
|
166
|
-
if (argument && Node.isIdentifier(argument)) {
|
|
167
|
-
const workflow = workflowsByName.get(argument.getText());
|
|
168
|
-
const agent = agentsByName.get(argument.getText());
|
|
169
|
-
if (workflow && agent) {
|
|
170
|
-
throw diagnosticAt(
|
|
171
|
-
argument,
|
|
172
|
-
`Cron job "${jobName}" references "${argument.getText()}", which is ambiguous: a workflow and an agent are both declared under that name.`,
|
|
173
|
-
{
|
|
174
|
-
code: "CRON_NON_STATIC_FN",
|
|
175
|
-
name: "LunoraError",
|
|
176
|
-
status: 500
|
|
177
|
-
}
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
if (workflow) {
|
|
181
|
-
return workflowTarget(workflow);
|
|
182
|
-
}
|
|
183
|
-
if (agent) {
|
|
184
|
-
return agentTarget(agent);
|
|
185
|
-
}
|
|
186
|
-
throw diagnosticAt(
|
|
187
|
-
argument,
|
|
188
|
-
`Cron job "${jobName}" references "${argument.getText()}", which is neither a function (internal.file.fn / api.file.fn) nor a declared workflow in lunora/workflows.ts nor a declared agent in lunora/agents.ts.`,
|
|
189
|
-
{
|
|
190
|
-
code: "CRON_NON_STATIC_FN",
|
|
191
|
-
name: "LunoraError",
|
|
192
|
-
status: 500
|
|
193
|
-
}
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
return { functionPath: functionPathFromArgument(call, index, jobName) };
|
|
197
|
-
};
|
|
198
|
-
const cronFromCall = (call, callee, builderNames, workflowsByName, agentsByName) => {
|
|
199
|
-
const method = callee.getName();
|
|
200
|
-
if (!CRON_METHODS.has(method)) {
|
|
201
|
-
return void 0;
|
|
202
|
-
}
|
|
203
|
-
if (!builderNames.has(rootIdentifierOf(callee.getExpression()) ?? "")) {
|
|
204
|
-
return void 0;
|
|
205
|
-
}
|
|
206
|
-
const name = stringArgument(call, 0);
|
|
207
|
-
if (name === void 0 || name.trim() === "") {
|
|
208
|
-
throw diagnosticAt(call, `A cron ".${method}(...)" registration must pass a non-empty string-literal name as its first argument.`, {
|
|
209
|
-
code: "CRON_NAME_NOT_STATIC",
|
|
210
|
-
name: "LunoraError",
|
|
211
|
-
status: 500
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
let cron;
|
|
215
|
-
if (method === "cron") {
|
|
216
|
-
const expression = stringArgument(call, 1);
|
|
217
|
-
if (expression === void 0) {
|
|
218
|
-
throw diagnosticAt(call, `Cron job "${name}" must pass a string-literal cron expression to ".cron(...)".`, {
|
|
219
|
-
code: "CRON_EXPR_NOT_STATIC",
|
|
220
|
-
name: "LunoraError",
|
|
221
|
-
status: 500
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
if (!isValidCronExpression(expression)) {
|
|
225
|
-
throw diagnosticAt(call, `Cron job "${name}" has an invalid cron expression "${expression}" — expected 5 or 6 space-separated fields.`, {
|
|
226
|
-
code: "CRON_EXPR_INVALID",
|
|
227
|
-
name: "LunoraError",
|
|
228
|
-
status: 500
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
cron = expression;
|
|
232
|
-
} else {
|
|
233
|
-
const scheduleArgument = call.getArguments()[1];
|
|
234
|
-
if (!scheduleArgument || !Node.isObjectLiteralExpression(scheduleArgument)) {
|
|
235
|
-
throw diagnosticAt(call, `Cron job "${name}" must pass an object-literal schedule to ".${method}(...)".`, {
|
|
236
|
-
code: "CRON_SCHEDULE_NOT_STATIC",
|
|
237
|
-
name: "LunoraError",
|
|
238
|
-
status: 500
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
cron = compileCronSchedule(method, objectLiteralValue(scheduleArgument, name));
|
|
242
|
-
}
|
|
243
|
-
const target = resolveTarget(call, 2, name, workflowsByName, agentsByName);
|
|
244
|
-
const argumentsNode = call.getArguments()[3];
|
|
245
|
-
const args = argumentsNode && Node.isObjectLiteralExpression(argumentsNode) ? objectLiteralValue(argumentsNode, name) : {};
|
|
246
|
-
return { args, cron, name, ...target };
|
|
247
|
-
};
|
|
248
|
-
const assertUniqueNames = (crons) => {
|
|
249
|
-
const seen = /* @__PURE__ */ new Set();
|
|
250
|
-
for (const cron of crons) {
|
|
251
|
-
if (seen.has(cron.name)) {
|
|
252
|
-
throw new LunoraError("DUPLICATE_CRON_NAME", `Duplicate cron job name "${cron.name}": cron names must be unique across the project.`, {
|
|
253
|
-
status: 500
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
seen.add(cron.name);
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
const discoverCrons = (project, lunoraDirectory, workflows = [], agents = []) => {
|
|
260
|
-
const filePaths = listLunoraSourceFiles(lunoraDirectory);
|
|
261
|
-
const crons = [];
|
|
262
|
-
const workflowsByName = new Map(workflows.map((workflow) => [workflow.exportName, workflow]));
|
|
263
|
-
const agentsByName = new Map(agents.map((agent) => [agent.exportName, agent]));
|
|
264
|
-
for (const filePath of filePaths) {
|
|
265
|
-
const source = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
|
|
266
|
-
const builderNames = collectCronBuilderNames(source);
|
|
267
|
-
if (builderNames.size === 0) {
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
for (const call of source.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
271
|
-
const callee = call.getExpression();
|
|
272
|
-
if (!Node.isPropertyAccessExpression(callee)) {
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
const cron = cronFromCall(call, callee, builderNames, workflowsByName, agentsByName);
|
|
276
|
-
if (cron) {
|
|
277
|
-
crons.push(cron);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
crons.sort((a, b) => a.name.localeCompare(b.name));
|
|
282
|
-
assertUniqueNames(crons);
|
|
283
|
-
return crons;
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
export { discoverCrons as default };
|