@lunora/codegen 1.0.0-alpha.55 → 1.0.0-alpha.56

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 (78) hide show
  1. package/dist/index.d.mts +59 -5
  2. package/dist/index.d.ts +59 -5
  3. package/dist/index.mjs +1 -38
  4. package/dist/packem_shared/AGENTS_FILENAME-BesaDYkL.mjs +1 -0
  5. package/dist/packem_shared/CONTAINERS_FILENAME-CFwdyaCI.mjs +1 -0
  6. package/dist/packem_shared/CodegenDiagnosticError-DPezpZTz.mjs +1 -0
  7. package/dist/packem_shared/FLAGS_FILENAME-DW5oGq1q.mjs +1 -0
  8. package/dist/packem_shared/GENERATED_HEADER-C94PuLpQ.mjs +1 -0
  9. package/dist/packem_shared/LUNORA_ERROR_CODES-Um9hC1gr.mjs +1 -0
  10. package/dist/packem_shared/MUTATORS_FILENAME-BKKPROzu.mjs +1 -0
  11. package/dist/packem_shared/NOTIFY_FILENAME-C-c7Lg-U.mjs +1 -0
  12. package/dist/packem_shared/OPENRPC_VERSION-DpV3v2W8.mjs +3 -0
  13. package/dist/packem_shared/QUEUES_FILENAME-CSE6i-ld.mjs +1 -0
  14. package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-DJsxljOQ.mjs +3 -0
  15. package/dist/packem_shared/SCHEMA_SNAPSHOT_VERSION-Bpf1FGMG.mjs +15 -0
  16. package/dist/packem_shared/SHAPES_FILENAME-DTkSJSsg.mjs +1 -0
  17. package/dist/packem_shared/WORKFLOWS_FILENAME-Bk5upkGM.mjs +1 -0
  18. package/dist/packem_shared/buildOpenApiDocument-DXX1G3GZ.mjs +3 -0
  19. package/dist/packem_shared/discover-ast-CezKqEhE.mjs +1 -0
  20. package/dist/packem_shared/discoverAuthApiCalls-DOfSQqVK.mjs +1 -0
  21. package/dist/packem_shared/discoverCrons-B09_uEuL.mjs +1 -0
  22. package/dist/packem_shared/discoverFunctions-BdibLQmc.mjs +1 -0
  23. package/dist/packem_shared/discoverHttpRoutes-BOWz6y_n.mjs +1 -0
  24. package/dist/packem_shared/discoverInserts-BtteTyNy.mjs +1 -0
  25. package/dist/packem_shared/discoverMaskProcedures-COPmwMBm.mjs +1 -0
  26. package/dist/packem_shared/discoverMigrations-iGMlp9YJ.mjs +1 -0
  27. package/dist/packem_shared/discoverNondeterministicCalls-Dmgc4QBx.mjs +1 -0
  28. package/dist/packem_shared/discoverQueries-CcfuGKHh.mjs +1 -0
  29. package/dist/packem_shared/discoverR2sqlCalls-CjZKvg-N.mjs +1 -0
  30. package/dist/packem_shared/discoverRlsMetadata-BXmNOgry.mjs +1 -0
  31. package/dist/packem_shared/discoverSandboxUsage-CDdBqWcM.mjs +1 -0
  32. package/dist/packem_shared/discoverSchema-m2voTncu.mjs +1 -0
  33. package/dist/packem_shared/discoverStorageRulesMetadata-b71cJJHz.mjs +1 -0
  34. package/dist/packem_shared/emit-Dr6XSLfW.mjs +2104 -0
  35. package/dist/packem_shared/emitApp-B671iU7r.mjs +413 -0
  36. package/dist/packem_shared/formatAdvisories-DZvv-0MG.mjs +2 -0
  37. package/dist/packem_shared/parse-validator-CSDlMEvE.mjs +1 -0
  38. package/dist/packem_shared/paths-BmX5O1sG.mjs +1 -0
  39. package/dist/packem_shared/redact-CQ8-to6l.mjs +1 -0
  40. package/dist/packem_shared/schemaFromIr-R1ZFzVyy.mjs +1 -0
  41. package/package.json +3 -3
  42. package/dist/packem_shared/AGENTS_FILENAME-Cxx7wiSs.mjs +0 -158
  43. package/dist/packem_shared/CONTAINERS_FILENAME-DjpXMqhp.mjs +0 -224
  44. package/dist/packem_shared/CodegenDiagnosticError-DyQ5FwkM.mjs +0 -24
  45. package/dist/packem_shared/FLAGS_FILENAME-D5igzqwR.mjs +0 -139
  46. package/dist/packem_shared/GENERATED_HEADER-C8tAvxqP.mjs +0 -4
  47. package/dist/packem_shared/LUNORA_ERROR_CODES-DvTLozCu.mjs +0 -68
  48. package/dist/packem_shared/MUTATORS_FILENAME-BZOfUhlY.mjs +0 -81
  49. package/dist/packem_shared/NOTIFY_FILENAME-CK2IrsrP.mjs +0 -152
  50. package/dist/packem_shared/OPENRPC_VERSION-DvDChMhC.mjs +0 -60
  51. package/dist/packem_shared/QUEUES_FILENAME-Xv6Vy-Ku.mjs +0 -145
  52. package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-YAL6GrxI.mjs +0 -3597
  53. package/dist/packem_shared/SCHEMA_SNAPSHOT_VERSION-DGD73E_n.mjs +0 -252
  54. package/dist/packem_shared/SHAPES_FILENAME-DOhPGi-6.mjs +0 -94
  55. package/dist/packem_shared/WORKFLOWS_FILENAME-Dtuzvluc.mjs +0 -158
  56. package/dist/packem_shared/buildOpenApiDocument-DCQSLuFc.mjs +0 -262
  57. package/dist/packem_shared/discover-ast-CT6BgBr4.mjs +0 -13
  58. package/dist/packem_shared/discoverAuthApiCalls-Dhwgf4J4.mjs +0 -62
  59. package/dist/packem_shared/discoverCrons-UbQLLmMS.mjs +0 -286
  60. package/dist/packem_shared/discoverFunctions-COG_Xkyz.mjs +0 -544
  61. package/dist/packem_shared/discoverHttpRoutes-CVXWZkzo.mjs +0 -139
  62. package/dist/packem_shared/discoverInserts-DdWnXmLS.mjs +0 -61
  63. package/dist/packem_shared/discoverMaskProcedures-BG58_fw3.mjs +0 -273
  64. package/dist/packem_shared/discoverMigrations-Bw1U01zr.mjs +0 -97
  65. package/dist/packem_shared/discoverNondeterministicCalls-BbXEFZUT.mjs +0 -122
  66. package/dist/packem_shared/discoverQueries-CDmxu2ET.mjs +0 -62
  67. package/dist/packem_shared/discoverR2sqlCalls-Bn7QVv7J.mjs +0 -80
  68. package/dist/packem_shared/discoverRlsMetadata-D9uN35no.mjs +0 -280
  69. package/dist/packem_shared/discoverSandboxUsage-B5jDU6Bt.mjs +0 -38
  70. package/dist/packem_shared/discoverSchema-BJp5_T4o.mjs +0 -906
  71. package/dist/packem_shared/discoverStorageRulesMetadata-BqMhuEjo.mjs +0 -97
  72. package/dist/packem_shared/emit-DIbr7U31.mjs +0 -3884
  73. package/dist/packem_shared/emitApp-DbMM8iHe.mjs +0 -706
  74. package/dist/packem_shared/formatAdvisories-Cans5TWS.mjs +0 -131
  75. package/dist/packem_shared/parse-validator-BDGV8iue.mjs +0 -144
  76. package/dist/packem_shared/paths-BRd6JHuF.mjs +0 -11
  77. package/dist/packem_shared/redact-oTmsol5A.mjs +0 -33
  78. 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 };