@lunora/codegen 1.0.0-alpha.5 → 1.0.0-alpha.50

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 (42) hide show
  1. package/LICENSE.md +6 -0
  2. package/__assets__/package-og.svg +1 -1
  3. package/dist/index.d.mts +1950 -734
  4. package/dist/index.d.ts +1950 -734
  5. package/dist/index.mjs +32 -24
  6. package/dist/packem_shared/AGENTS_FILENAME-Cxx7wiSs.mjs +158 -0
  7. package/dist/packem_shared/{CONTAINERS_FILENAME-0K-pjNb8.mjs → CONTAINERS_FILENAME-DjpXMqhp.mjs} +1 -1
  8. package/dist/packem_shared/{CodegenDiagnosticError-54jWDxA9.mjs → CodegenDiagnosticError-DyQ5FwkM.mjs} +7 -5
  9. package/dist/packem_shared/FLAGS_FILENAME-Dg4mKUuz.mjs +139 -0
  10. package/dist/packem_shared/GENERATED_HEADER-C5paWNYO.mjs +4 -0
  11. package/dist/packem_shared/{LUNORA_ERROR_CODES-CySpQPD3.mjs → LUNORA_ERROR_CODES-DvTLozCu.mjs} +8 -1
  12. package/dist/packem_shared/MUTATORS_FILENAME-BZOfUhlY.mjs +81 -0
  13. package/dist/packem_shared/{OPENRPC_VERSION-C7zKVeKi.mjs → OPENRPC_VERSION-7A2weq2u.mjs} +2 -2
  14. package/dist/packem_shared/QUEUES_FILENAME-Xv6Vy-Ku.mjs +145 -0
  15. package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-G5dkcxPR.mjs +3502 -0
  16. package/dist/packem_shared/{SCHEMA_SNAPSHOT_VERSION-DzLDbWk3.mjs → SCHEMA_SNAPSHOT_VERSION-D0ARY6rL.mjs} +18 -2
  17. package/dist/packem_shared/SHAPES_FILENAME-DOhPGi-6.mjs +94 -0
  18. package/dist/packem_shared/WORKFLOWS_FILENAME-Dtuzvluc.mjs +158 -0
  19. package/dist/packem_shared/{buildOpenApiDocument-Cns1EkCQ.mjs → buildOpenApiDocument-B7mEdzA2.mjs} +2 -2
  20. package/dist/packem_shared/{discoverAuthApiCalls-C35R6z0T.mjs → discoverAuthApiCalls-Dx3K42rk.mjs} +1 -1
  21. package/dist/packem_shared/{discoverCrons-BL6iGuJ3.mjs → discoverCrons-DvqkEWdx.mjs} +68 -36
  22. package/dist/packem_shared/{discoverFunctions-DEgAcRuD.mjs → discoverFunctions-BJ-qR7Rg.mjs} +72 -14
  23. package/dist/packem_shared/{discoverHttpRoutes-C978pBiG.mjs → discoverHttpRoutes-daCzuTe8.mjs} +10 -2
  24. package/dist/packem_shared/{discoverInserts-CRQdXvHO.mjs → discoverInserts-DI4q5NxE.mjs} +24 -2
  25. package/dist/packem_shared/{discoverMaskProcedures-B64zA740.mjs → discoverMaskProcedures-BcTOEKNU.mjs} +58 -2
  26. package/dist/packem_shared/{discoverMigrations-Doj_-BAA.mjs → discoverMigrations-VNUFvCwr.mjs} +10 -4
  27. package/dist/packem_shared/{discoverNondeterministicCalls-4KiPQxQU.mjs → discoverNondeterministicCalls-S0N2xLCq.mjs} +1 -1
  28. package/dist/packem_shared/{discoverQueries-BkIi0dBD.mjs → discoverQueries-CJnnnLpd.mjs} +1 -1
  29. package/dist/packem_shared/{discoverR2sqlCalls-BpDqvcUn.mjs → discoverR2sqlCalls-pnpicWfz.mjs} +1 -1
  30. package/dist/packem_shared/{discoverRlsMetadata-DpRB1HMe.mjs → discoverRlsMetadata-DppniPUH.mjs} +1 -1
  31. package/dist/packem_shared/discoverSandboxUsage-BUM2r90k.mjs +38 -0
  32. package/dist/packem_shared/{discoverSchema-DB_QrIQe.mjs → discoverSchema-BaSAvooG.mjs} +326 -14
  33. package/dist/packem_shared/{discoverStorageRulesMetadata-DAqJUxUv.mjs → discoverStorageRulesMetadata-CnHl2rXD.mjs} +1 -1
  34. package/dist/packem_shared/{GENERATED_HEADER-DF1hQcix.mjs → emit-ClTbCLy4.mjs} +1458 -154
  35. package/dist/packem_shared/{emitApp-Ci_hcJNO.mjs → emitApp-CzzrjVqH.mjs} +129 -31
  36. package/dist/packem_shared/formatAdvisories-BmF7Mplc.mjs +115 -0
  37. package/dist/packem_shared/{parse-validator-tuQtHrsr.mjs → parse-validator-BSJo1HGP.mjs} +18 -6
  38. package/dist/packem_shared/redact-oTmsol5A.mjs +33 -0
  39. package/package.json +10 -7
  40. package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-BqOtU2lC.mjs +0 -922
  41. package/dist/packem_shared/WORKFLOWS_FILENAME-DRDQdhfq.mjs +0 -84
  42. package/dist/packem_shared/formatAdvisories-8NIv1k0I.mjs +0 -69
@@ -0,0 +1,3502 @@
1
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { performance } from 'node:perf_hooks';
4
+ import { LunoraError } from '@lunora/errors';
5
+ import { Node, SyntaxKind, Project } from 'ts-morph';
6
+ import { lintSchema } from './formatAdvisories-BmF7Mplc.mjs';
7
+ import { listLunoraSourceFiles, lunoraRelativePath, classifyProcedureCall, inlineHandler as inlineHandler$1, procedureHandler, chainUsesWrappedCall, isDatabaseAccessor, chainHasStep, discoverFunctions } from './discoverFunctions-BJ-qR7Rg.mjs';
8
+ import { discoverAgents } from './AGENTS_FILENAME-Cxx7wiSs.mjs';
9
+ import discoverAuthApiCalls from './discoverAuthApiCalls-Dx3K42rk.mjs';
10
+ import { discoverContainers } from './CONTAINERS_FILENAME-DjpXMqhp.mjs';
11
+ import discoverCrons from './discoverCrons-DvqkEWdx.mjs';
12
+ import { diagnosticAt } from './CodegenDiagnosticError-DyQ5FwkM.mjs';
13
+ import { C as CAPABILITIES, n as buildStorageColumns, f as emitDataModel, a as emitApi, i as emitServer, h as emitFunctions, j as emitShard, b as emitCollections, c as emitContainers, l as emitWorkflows, e as emitAgents, o as emitQueues, d as emitCrons, k as emitVectors, g as emitDrizzleSchema, p as emitSeed, m as emitWranglerCronTriggers } from './emit-ClTbCLy4.mjs';
14
+ import { discoverFlagKeys } from './FLAGS_FILENAME-Dg4mKUuz.mjs';
15
+ import discoverHttpRoutes from './discoverHttpRoutes-daCzuTe8.mjs';
16
+ import discoverInserts from './discoverInserts-DI4q5NxE.mjs';
17
+ import discoverMaskProcedures, { discoverMaskStrategies, discoverMaskMetadata } from './discoverMaskProcedures-BcTOEKNU.mjs';
18
+ import discoverMigrations from './discoverMigrations-VNUFvCwr.mjs';
19
+ import { MUTATORS_FILENAME, isDefineMutatorCallee, discoverMutators } from './MUTATORS_FILENAME-BZOfUhlY.mjs';
20
+ import discoverNondeterministicCalls from './discoverNondeterministicCalls-S0N2xLCq.mjs';
21
+ import discoverQueries from './discoverQueries-CJnnnLpd.mjs';
22
+ import { discoverQueues } from './QUEUES_FILENAME-Xv6Vy-Ku.mjs';
23
+ import discoverR2sqlCalls from './discoverR2sqlCalls-pnpicWfz.mjs';
24
+ import discoverRlsProcedures, { discoverRlsMetadata } from './discoverRlsMetadata-DppniPUH.mjs';
25
+ import { discoverSandboxUsage } from './discoverSandboxUsage-BUM2r90k.mjs';
26
+ import discoverSchema from './discoverSchema-BaSAvooG.mjs';
27
+ import { secretKindOf, redact } from './redact-oTmsol5A.mjs';
28
+ import { discoverShapes } from './SHAPES_FILENAME-DOhPGi-6.mjs';
29
+ import discoverStorageRulesMetadata from './discoverStorageRulesMetadata-CnHl2rXD.mjs';
30
+ import { e as enclosingExportName$2 } from './discover-ast-CT6BgBr4.mjs';
31
+ import { discoverWorkflows } from './WORKFLOWS_FILENAME-Dtuzvluc.mjs';
32
+ import { emitApp } from './emitApp-CzzrjVqH.mjs';
33
+ import { buildOpenApiDocument, emitOpenApiModule } from './buildOpenApiDocument-B7mEdzA2.mjs';
34
+ import { buildOpenRpcDocument, emitOpenRpcModule } from './OPENRPC_VERSION-7A2weq2u.mjs';
35
+ import { buildSchemaSnapshot, serializeSchemaSnapshot } from './SCHEMA_SNAPSHOT_VERSION-D0ARY6rL.mjs';
36
+
37
+ const HTTP_VERBS$1 = /* @__PURE__ */ new Set(["delete", "get", "head", "options", "patch", "post", "put"]);
38
+ const TERMINAL_STEPS$1 = /* @__PURE__ */ new Set(["handler", "stream"]);
39
+ const ADMIN_PATH_RE = /\/(?:_|admin|internal|superuser|sudo|root|debug)/iu;
40
+ const GUARD_NAMES = /* @__PURE__ */ new Set([
41
+ "ADMIN_TOKEN",
42
+ "adminToken",
43
+ "assertAdmin",
44
+ "assertAuth",
45
+ "auth",
46
+ "Authorization",
47
+ "getSession",
48
+ "identity",
49
+ "isAdmin",
50
+ "requireAdmin",
51
+ "requireAuth",
52
+ "requireRole",
53
+ "verifyAdmin"
54
+ ]);
55
+ const readRootVerb = (node) => {
56
+ if (!Node.isCallExpression(node)) {
57
+ return void 0;
58
+ }
59
+ const callee = node.getExpression();
60
+ if (!Node.isPropertyAccessExpression(callee) || !HTTP_VERBS$1.has(callee.getName())) {
61
+ return void 0;
62
+ }
63
+ const receiver = callee.getExpression();
64
+ if (!Node.isIdentifier(receiver) || receiver.getText() !== "httpRoute") {
65
+ return void 0;
66
+ }
67
+ const first = node.getArguments()[0];
68
+ if (!first || !Node.isStringLiteral(first)) {
69
+ return void 0;
70
+ }
71
+ return { method: callee.getName().toUpperCase(), path: first.getLiteralValue() };
72
+ };
73
+ const rootOfChain = (terminalCall) => {
74
+ const terminalCallee = terminalCall.getExpression();
75
+ if (!Node.isPropertyAccessExpression(terminalCallee)) {
76
+ return void 0;
77
+ }
78
+ let node = terminalCallee.getExpression();
79
+ while (Node.isCallExpression(node)) {
80
+ const callee = node.getExpression();
81
+ if (!Node.isPropertyAccessExpression(callee)) {
82
+ break;
83
+ }
84
+ if (HTTP_VERBS$1.has(callee.getName())) {
85
+ break;
86
+ }
87
+ node = callee.getExpression();
88
+ }
89
+ return readRootVerb(node);
90
+ };
91
+ const handlerReferencesGuard = (handlerBody) => {
92
+ for (const access of handlerBody.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression)) {
93
+ if (GUARD_NAMES.has(access.getName())) {
94
+ return true;
95
+ }
96
+ }
97
+ for (const call of handlerBody.getDescendantsOfKind(SyntaxKind.CallExpression)) {
98
+ const callee = call.getExpression();
99
+ if (Node.isIdentifier(callee) && GUARD_NAMES.has(callee.getText())) {
100
+ return true;
101
+ }
102
+ }
103
+ return false;
104
+ };
105
+ const adminRouteFromDeclaration = (declaration, relativePath) => {
106
+ const initializer = declaration.getInitializer();
107
+ if (!initializer || !Node.isCallExpression(initializer)) {
108
+ return void 0;
109
+ }
110
+ const callee = initializer.getExpression();
111
+ if (!Node.isPropertyAccessExpression(callee) || !TERMINAL_STEPS$1.has(callee.getName())) {
112
+ return void 0;
113
+ }
114
+ const root = rootOfChain(initializer);
115
+ if (!root || !ADMIN_PATH_RE.test(root.path)) {
116
+ return void 0;
117
+ }
118
+ const handlerArgument = initializer.getArguments()[0];
119
+ const usesGuard = handlerArgument !== void 0 && (Node.isArrowFunction(handlerArgument) || Node.isFunctionExpression(handlerArgument)) && handlerReferencesGuard(handlerArgument);
120
+ return { exportName: declaration.getName(), file: relativePath, method: root.method, path: root.path, usesGuard };
121
+ };
122
+ const adminRoutesInSourceFile = (sourceFile, relativePath) => {
123
+ const found = [];
124
+ for (const statement of sourceFile.getVariableStatements()) {
125
+ if (!statement.isExported()) {
126
+ continue;
127
+ }
128
+ for (const declaration of statement.getDeclarations()) {
129
+ const ir = adminRouteFromDeclaration(declaration, relativePath);
130
+ if (ir) {
131
+ found.push(ir);
132
+ }
133
+ }
134
+ }
135
+ return found;
136
+ };
137
+ const discoverAdminRoutes = (project, lunoraDirectory) => {
138
+ const routes = [];
139
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
140
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
141
+ routes.push(...adminRoutesInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
142
+ }
143
+ return routes;
144
+ };
145
+
146
+ const isArgsValueReference = (identifier) => {
147
+ if (identifier.getText() !== "args") {
148
+ return false;
149
+ }
150
+ const parent = identifier.getParent();
151
+ if (Node.isPropertyAccessExpression(parent) && parent.getNameNode() === identifier) {
152
+ return false;
153
+ }
154
+ return !(Node.isPropertyAssignment(parent) && parent.getNameNode() === identifier);
155
+ };
156
+ const isContextValueReference = (identifier) => {
157
+ if (identifier.getText() !== "ctx") {
158
+ return false;
159
+ }
160
+ const parent = identifier.getParent();
161
+ if (Node.isPropertyAccessExpression(parent) && parent.getNameNode() === identifier) {
162
+ return false;
163
+ }
164
+ return !(Node.isPropertyAssignment(parent) && parent.getNameNode() === identifier);
165
+ };
166
+ const textuallyReferencesContext = (node) => {
167
+ if (Node.isIdentifier(node)) {
168
+ return isContextValueReference(node);
169
+ }
170
+ return node.getDescendantsOfKind(SyntaxKind.Identifier).some((identifier) => isContextValueReference(identifier));
171
+ };
172
+ const isRequestValueReference = (identifier, requestName) => {
173
+ if (identifier.getText() !== requestName) {
174
+ return false;
175
+ }
176
+ const parent = identifier.getParent();
177
+ if (Node.isPropertyAccessExpression(parent) && parent.getNameNode() === identifier) {
178
+ return false;
179
+ }
180
+ return !(Node.isPropertyAssignment(parent) && parent.getNameNode() === identifier);
181
+ };
182
+ const memberAccessRootIdentifier = (node) => {
183
+ let current = node;
184
+ while (Node.isPropertyAccessExpression(current) || Node.isElementAccessExpression(current) || Node.isNonNullExpression(current)) {
185
+ current = current.getExpression();
186
+ }
187
+ return Node.isIdentifier(current) ? current : void 0;
188
+ };
189
+ const calleeName$2 = (expression) => {
190
+ if (Node.isIdentifier(expression)) {
191
+ return expression.getText();
192
+ }
193
+ if (Node.isPropertyAccessExpression(expression)) {
194
+ return expression.getName();
195
+ }
196
+ return void 0;
197
+ };
198
+ const referencesArgs = (node) => {
199
+ if (Node.isIdentifier(node)) {
200
+ return isArgsValueReference(node);
201
+ }
202
+ return node.getDescendantsOfKind(SyntaxKind.Identifier).some((identifier) => isArgsValueReference(identifier));
203
+ };
204
+ const singleHopInitializer = (node) => {
205
+ if (!Node.isIdentifier(node)) {
206
+ return void 0;
207
+ }
208
+ const name = node.getText();
209
+ const enclosingFunction = node.getFirstAncestor(
210
+ (ancestor) => Node.isArrowFunction(ancestor) || Node.isFunctionExpression(ancestor) || Node.isFunctionDeclaration(ancestor)
211
+ );
212
+ if (enclosingFunction === void 0) {
213
+ return void 0;
214
+ }
215
+ const usePosition = node.getStart();
216
+ let nearest;
217
+ let nearestPosition = -1;
218
+ for (const variable of enclosingFunction.getDescendantsOfKind(SyntaxKind.VariableDeclaration)) {
219
+ if (variable.getName() !== name) {
220
+ continue;
221
+ }
222
+ const initializer = variable.getInitializer();
223
+ const declarationPosition = variable.getStart();
224
+ if (initializer !== void 0 && declarationPosition < usePosition && declarationPosition > nearestPosition) {
225
+ nearest = initializer;
226
+ nearestPosition = declarationPosition;
227
+ }
228
+ }
229
+ return nearest;
230
+ };
231
+ const isArgumentDerived = (expression) => {
232
+ if (referencesArgs(expression)) {
233
+ return true;
234
+ }
235
+ const initializer = singleHopInitializer(expression);
236
+ return initializer !== void 0 && referencesArgs(initializer);
237
+ };
238
+ const isScopedByContext = (node) => {
239
+ if (textuallyReferencesContext(node)) {
240
+ return true;
241
+ }
242
+ const initializer = singleHopInitializer(node);
243
+ if (initializer !== void 0 && textuallyReferencesContext(initializer)) {
244
+ return true;
245
+ }
246
+ const composed = initializer ?? node;
247
+ const identifiers = Node.isIdentifier(composed) ? [composed] : composed.getDescendantsOfKind(SyntaxKind.Identifier);
248
+ return identifiers.some((identifier) => {
249
+ const boundInitializer = singleHopInitializer(identifier);
250
+ return boundInitializer !== void 0 && textuallyReferencesContext(boundInitializer);
251
+ });
252
+ };
253
+ const referencesRequestInput = (node, requestName) => {
254
+ if (Node.isIdentifier(node)) {
255
+ return isRequestValueReference(node, requestName);
256
+ }
257
+ return node.getDescendantsOfKind(SyntaxKind.Identifier).some((identifier) => isRequestValueReference(identifier, requestName));
258
+ };
259
+ const isRequestInputDerived = (node, requestName) => {
260
+ if (referencesRequestInput(node, requestName)) {
261
+ return true;
262
+ }
263
+ const initializer = singleHopInitializer(node);
264
+ if (initializer !== void 0 && referencesRequestInput(initializer, requestName)) {
265
+ return true;
266
+ }
267
+ const root = memberAccessRootIdentifier(node);
268
+ if (root !== void 0) {
269
+ const rootInitializer = singleHopInitializer(root);
270
+ return rootInitializer !== void 0 && referencesRequestInput(rootInitializer, requestName);
271
+ }
272
+ return false;
273
+ };
274
+ const enclosingExportName$1 = (node) => {
275
+ for (const ancestor of node.getAncestors()) {
276
+ if (Node.isVariableDeclaration(ancestor) && ancestor.getVariableStatement()?.hasExportKeyword() === true) {
277
+ return ancestor.getName();
278
+ }
279
+ }
280
+ return "<module>";
281
+ };
282
+
283
+ const isContextAiRunCallee = (node) => {
284
+ if (!Node.isPropertyAccessExpression(node) || node.getName() !== "run") {
285
+ return false;
286
+ }
287
+ return node.getExpression().getText() === "ctx.ai";
288
+ };
289
+ const aiRawRunInCall = (call, relativePath) => {
290
+ if (!isContextAiRunCallee(call.getExpression())) {
291
+ return void 0;
292
+ }
293
+ const model = call.getArguments()[0];
294
+ if (!model || !isArgumentDerived(model) || isScopedByContext(model)) {
295
+ return void 0;
296
+ }
297
+ return { exportName: enclosingExportName$1(call), file: relativePath, line: call.getStartLineNumber() };
298
+ };
299
+ const aiRawRunsInSourceFile = (sourceFile, relativePath) => {
300
+ const found = [];
301
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
302
+ const row = aiRawRunInCall(call, relativePath);
303
+ if (row) {
304
+ found.push(row);
305
+ }
306
+ }
307
+ return found;
308
+ };
309
+ const discoverAiRawRuns = (project, lunoraDirectory) => {
310
+ const rows = [];
311
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
312
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
313
+ rows.push(...aiRawRunsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
314
+ }
315
+ return rows;
316
+ };
317
+
318
+ const GENERATION_CALLEES = /* @__PURE__ */ new Set(["generateText", "streamText"]);
319
+ const MODEL_INPUT_KEYS = /* @__PURE__ */ new Set(["messages", "prompt", "system"]);
320
+ const SIDE_EFFECT_SINKS = [
321
+ // Database writes.
322
+ { methods: /* @__PURE__ */ new Set(["delete", "insert", "insertManyUnsafe", "patch", "replace"]), prefixes: ["context.db", "ctx.db"] },
323
+ // Function dispatch (runs another mutation / action with the caller's authority).
324
+ { methods: /* @__PURE__ */ new Set(["run", "runAction", "runMutation"]), prefixes: ["context", "ctx"] },
325
+ // Outbound network / mail / queue sends.
326
+ { methods: /* @__PURE__ */ new Set(["fetch"]), prefixes: ["context", "ctx"] },
327
+ { methods: /* @__PURE__ */ new Set(["queue", "send"]), prefixes: ["context.email", "context.mail", "ctx.email", "ctx.mail"] }
328
+ ];
329
+ const sideEffectLabel = (call) => {
330
+ const callee = call.getExpression();
331
+ if (!Node.isPropertyAccessExpression(callee)) {
332
+ return void 0;
333
+ }
334
+ const method = callee.getName();
335
+ const receiver = callee.getExpression().getText();
336
+ for (const sink of SIDE_EFFECT_SINKS) {
337
+ if (sink.methods.has(method) && sink.prefixes.some((prefix) => receiver === prefix || receiver.startsWith(`${prefix}.`))) {
338
+ return `${receiver}.${method}`;
339
+ }
340
+ }
341
+ return void 0;
342
+ };
343
+ const toolSideEffect = (toolCall) => {
344
+ for (const call of toolCall.getDescendantsOfKind(SyntaxKind.CallExpression)) {
345
+ const label = sideEffectLabel(call);
346
+ if (label !== void 0) {
347
+ return label;
348
+ }
349
+ }
350
+ return void 0;
351
+ };
352
+ const destructuredArgumentNames = (node) => {
353
+ const names = /* @__PURE__ */ new Set();
354
+ const enclosingFunction = node.getFirstAncestor(
355
+ (ancestor) => Node.isArrowFunction(ancestor) || Node.isFunctionExpression(ancestor) || Node.isFunctionDeclaration(ancestor)
356
+ );
357
+ const [firstParameter] = enclosingFunction?.getParameters() ?? [];
358
+ const pattern = firstParameter?.getNameNode();
359
+ if (pattern === void 0 || !Node.isObjectBindingPattern(pattern)) {
360
+ return names;
361
+ }
362
+ for (const element of pattern.getElements()) {
363
+ const propertyName = element.getPropertyNameNode()?.getText() ?? element.getName();
364
+ const valueNode = element.getNameNode();
365
+ if (propertyName === "args" && Node.isObjectBindingPattern(valueNode)) {
366
+ for (const nested of valueNode.getElements()) {
367
+ names.add(nested.getName());
368
+ }
369
+ }
370
+ }
371
+ return names;
372
+ };
373
+ const isUserInputDerived = (inputNode) => {
374
+ if (referencesArgs(inputNode)) {
375
+ return true;
376
+ }
377
+ const names = destructuredArgumentNames(inputNode);
378
+ if (names.size === 0) {
379
+ return false;
380
+ }
381
+ return inputNode.getDescendantsOfKind(SyntaxKind.Identifier).some((identifier) => names.has(identifier.getText()));
382
+ };
383
+ const hasUserDerivedInput = (optionsObject) => {
384
+ for (const property of optionsObject.getProperties()) {
385
+ if (!Node.isPropertyAssignment(property) || !MODEL_INPUT_KEYS.has(property.getName())) {
386
+ continue;
387
+ }
388
+ const initializer = property.getInitializer();
389
+ if (initializer !== void 0 && isUserInputDerived(initializer)) {
390
+ return true;
391
+ }
392
+ }
393
+ return false;
394
+ };
395
+ const generationsInSourceFile = (sourceFile, relativePath) => {
396
+ const rows = [];
397
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
398
+ const callee = calleeName$2(call.getExpression());
399
+ if (callee === void 0 || !GENERATION_CALLEES.has(callee)) {
400
+ continue;
401
+ }
402
+ const [optionsArgument] = call.getArguments();
403
+ if (optionsArgument === void 0 || !Node.isObjectLiteralExpression(optionsArgument)) {
404
+ continue;
405
+ }
406
+ let sideEffect;
407
+ for (const toolCall of optionsArgument.getDescendantsOfKind(SyntaxKind.CallExpression)) {
408
+ if (calleeName$2(toolCall.getExpression()) !== "tool") {
409
+ continue;
410
+ }
411
+ sideEffect = toolSideEffect(toolCall);
412
+ if (sideEffect !== void 0) {
413
+ break;
414
+ }
415
+ }
416
+ if (sideEffect === void 0) {
417
+ continue;
418
+ }
419
+ rows.push({
420
+ exportName: enclosingExportName$1(call),
421
+ file: relativePath,
422
+ line: call.getStartLineNumber(),
423
+ method: callee,
424
+ sideEffect,
425
+ userInputDerived: hasUserDerivedInput(optionsArgument)
426
+ });
427
+ }
428
+ return rows;
429
+ };
430
+ const discoverAiToolSideEffects = (project, lunoraDirectory) => {
431
+ const rows = [];
432
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
433
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
434
+ rows.push(...generationsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
435
+ }
436
+ return rows;
437
+ };
438
+
439
+ const isContextFetchCallee = (node) => {
440
+ if (!Node.isPropertyAccessExpression(node) || node.getName() !== "fetch") {
441
+ return false;
442
+ }
443
+ const receiver = node.getExpression();
444
+ return Node.isIdentifier(receiver) && receiver.getText() === "ctx";
445
+ };
446
+ const fetchInCall = (call, relativePath) => {
447
+ if (!isContextFetchCallee(call.getExpression())) {
448
+ return void 0;
449
+ }
450
+ const url = call.getArguments()[0];
451
+ if (!url || !isArgumentDerived(url)) {
452
+ return void 0;
453
+ }
454
+ return { exportName: enclosingExportName$1(call), file: relativePath, line: call.getStartLineNumber() };
455
+ };
456
+ const fetchesInSourceFile = (sourceFile, relativePath) => {
457
+ const found = [];
458
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
459
+ const fetchCall = fetchInCall(call, relativePath);
460
+ if (fetchCall) {
461
+ found.push(fetchCall);
462
+ }
463
+ }
464
+ return found;
465
+ };
466
+ const discoverArgumentDerivedFetches = (project, lunoraDirectory) => {
467
+ const fetches = [];
468
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
469
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
470
+ fetches.push(...fetchesInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
471
+ }
472
+ return fetches;
473
+ };
474
+
475
+ const BOUND_RE = /\.check\(|\.meta\(|length|max/iu;
476
+ const ANY_VALIDATOR_RE = /\bv\.any\s*\(/u;
477
+ const STRING_VALIDATOR_RE = /\bv\.string\s*\(/u;
478
+ const isAnyValidator = (text) => ANY_VALIDATOR_RE.test(text);
479
+ const isUnboundedString = (text) => STRING_VALIDATOR_RE.test(text) && !BOUND_RE.test(text);
480
+ const argsObjectOfFactory = (call) => {
481
+ const first = call.getArguments()[0];
482
+ if (!first || !Node.isObjectLiteralExpression(first)) {
483
+ return void 0;
484
+ }
485
+ const argsProperty = first.getProperty("args");
486
+ if (!argsProperty || !Node.isPropertyAssignment(argsProperty)) {
487
+ return void 0;
488
+ }
489
+ const initializer = argsProperty.getInitializer();
490
+ return initializer && Node.isObjectLiteralExpression(initializer) ? initializer : void 0;
491
+ };
492
+ const inputObjectsInChain = (receiver) => {
493
+ const objects = [];
494
+ let node = receiver;
495
+ while (Node.isCallExpression(node)) {
496
+ const chainCallee = node.getExpression();
497
+ if (!Node.isPropertyAccessExpression(chainCallee)) {
498
+ break;
499
+ }
500
+ if (chainCallee.getName() === "input") {
501
+ const argument = node.getArguments()[0];
502
+ if (argument && Node.isObjectLiteralExpression(argument)) {
503
+ objects.push(argument);
504
+ }
505
+ }
506
+ node = chainCallee.getExpression();
507
+ }
508
+ return objects;
509
+ };
510
+ const classifyArgs = (objects) => {
511
+ const anyArgs = [];
512
+ const unboundedStringArgs = [];
513
+ for (const object of objects) {
514
+ for (const property of object.getProperties()) {
515
+ if (!Node.isPropertyAssignment(property)) {
516
+ continue;
517
+ }
518
+ const initializer = property.getInitializer();
519
+ if (!initializer) {
520
+ continue;
521
+ }
522
+ const text = initializer.getText();
523
+ const name = property.getName();
524
+ if (isAnyValidator(text)) {
525
+ anyArgs.push(name);
526
+ } else if (isUnboundedString(text)) {
527
+ unboundedStringArgs.push(name);
528
+ }
529
+ }
530
+ }
531
+ return { anyArgs, unboundedStringArgs };
532
+ };
533
+ const argumentValidatorIrFromDeclaration = (declaration, relativePath) => {
534
+ const initializer = declaration.getInitializer();
535
+ if (!initializer || !Node.isCallExpression(initializer)) {
536
+ return void 0;
537
+ }
538
+ const classified = classifyProcedureCall(initializer);
539
+ if (classified?.visibility !== "public") {
540
+ return void 0;
541
+ }
542
+ const objects = classified.receiver ? inputObjectsInChain(classified.receiver) : [argsObjectOfFactory(initializer)].filter((object) => object !== void 0);
543
+ const { anyArgs, unboundedStringArgs } = classifyArgs(objects);
544
+ if (anyArgs.length === 0 && unboundedStringArgs.length === 0) {
545
+ return void 0;
546
+ }
547
+ return {
548
+ anyArgs,
549
+ exportName: declaration.getName(),
550
+ file: relativePath,
551
+ line: initializer.getStartLineNumber(),
552
+ unboundedStringArgs
553
+ };
554
+ };
555
+ const argumentValidatorsInSourceFile = (sourceFile, relativePath) => {
556
+ const found = [];
557
+ for (const statement of sourceFile.getVariableStatements()) {
558
+ if (!statement.isExported()) {
559
+ continue;
560
+ }
561
+ for (const declaration of statement.getDeclarations()) {
562
+ const ir = argumentValidatorIrFromDeclaration(declaration, relativePath);
563
+ if (ir) {
564
+ found.push(ir);
565
+ }
566
+ }
567
+ }
568
+ return found;
569
+ };
570
+ const discoverArgumentValidators = (project, lunoraDirectory) => {
571
+ const procedures = [];
572
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
573
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
574
+ procedures.push(...argumentValidatorsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
575
+ }
576
+ return procedures;
577
+ };
578
+
579
+ const propertyInitializer = (object, name) => {
580
+ if (!object || !Node.isObjectLiteralExpression(object)) {
581
+ return void 0;
582
+ }
583
+ const property = object.getProperty(name);
584
+ return property && Node.isPropertyAssignment(property) ? property.getInitializer() : void 0;
585
+ };
586
+ const isTrueLiteral = (node) => node?.getKind() === SyntaxKind.TrueKeyword;
587
+ const isFalseLiteral = (node) => node?.getKind() === SyntaxKind.FalseKeyword;
588
+ const isZeroLiteral = (node) => node !== void 0 && Node.isNumericLiteral(node) && node.getLiteralValue() === 0;
589
+ const hasWildcardOrigin = (node) => {
590
+ if (!node || !Node.isArrayLiteralExpression(node)) {
591
+ return false;
592
+ }
593
+ return node.getElements().some((element) => Node.isStringLiteral(element) && element.getLiteralText() === "*");
594
+ };
595
+ const readAuthConfig = (config) => {
596
+ const advanced = propertyInitializer(config, "advanced");
597
+ const emailAndPassword = propertyInitializer(config, "emailAndPassword");
598
+ const session = propertyInitializer(config, "session");
599
+ return {
600
+ analyzable: true,
601
+ disableCsrfCheck: isTrueLiteral(propertyInitializer(advanced, "disableCSRFCheck")),
602
+ emailPasswordEnabled: isTrueLiteral(propertyInitializer(emailAndPassword, "enabled")),
603
+ requireEmailVerification: isTrueLiteral(propertyInitializer(emailAndPassword, "requireEmailVerification")),
604
+ // gitleaks:allow -- ts-morph property-name lookup, not a secret
605
+ secureCookiesDisabled: isFalseLiteral(propertyInitializer(advanced, "useSecureCookies")),
606
+ sessionFreshAgeZero: isZeroLiteral(propertyInitializer(session, "freshAge")),
607
+ trustedOriginsWildcard: hasWildcardOrigin(propertyInitializer(config, "trustedOrigins"))
608
+ };
609
+ };
610
+ const unanalyzableAuthConfig = () => {
611
+ return {
612
+ analyzable: false,
613
+ disableCsrfCheck: false,
614
+ emailPasswordEnabled: false,
615
+ requireEmailVerification: false,
616
+ secureCookiesDisabled: false,
617
+ sessionFreshAgeZero: false,
618
+ trustedOriginsWildcard: false
619
+ };
620
+ };
621
+ const authConfigInCall = (call, relativePath) => {
622
+ if (calleeName$2(call.getExpression()) !== "createAuth") {
623
+ return void 0;
624
+ }
625
+ const argument = call.getArguments()[0];
626
+ const hasSpread = argument !== void 0 && Node.isObjectLiteralExpression(argument) && argument.getProperties().some((property) => Node.isSpreadAssignment(property));
627
+ const facts = argument !== void 0 && Node.isObjectLiteralExpression(argument) && !hasSpread ? readAuthConfig(argument) : unanalyzableAuthConfig();
628
+ return { exportName: enclosingExportName$1(call), file: relativePath, line: call.getStartLineNumber(), ...facts };
629
+ };
630
+ const authConfigsInSourceFile = (sourceFile, relativePath) => {
631
+ const found = [];
632
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
633
+ const row = authConfigInCall(call, relativePath);
634
+ if (row) {
635
+ found.push(row);
636
+ }
637
+ }
638
+ return found;
639
+ };
640
+ const discoverAuthConfig = (project, lunoraDirectory) => {
641
+ const rows = [];
642
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
643
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
644
+ rows.push(...authConfigsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
645
+ }
646
+ return rows;
647
+ };
648
+
649
+ const sinkMethod = (node, config) => {
650
+ if (!Node.isPropertyAccessExpression(node)) {
651
+ return void 0;
652
+ }
653
+ const method = node.getName();
654
+ if (!config.methods.has(method)) {
655
+ return void 0;
656
+ }
657
+ return config.matchReceiver(node.getExpression().getText()) ? method : void 0;
658
+ };
659
+ const accessInCall = (call, relativePath, config) => {
660
+ const method = sinkMethod(call.getExpression(), config);
661
+ if (method === void 0) {
662
+ return void 0;
663
+ }
664
+ const key = call.getArguments()[config.argIndex];
665
+ if (!key || !isArgumentDerived(key) || isScopedByContext(key)) {
666
+ return void 0;
667
+ }
668
+ return { exportName: enclosingExportName$1(call), file: relativePath, line: call.getStartLineNumber(), method };
669
+ };
670
+ const accessesInSourceFile = (sourceFile, relativePath, config) => {
671
+ const found = [];
672
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
673
+ const access = accessInCall(call, relativePath, config);
674
+ if (access) {
675
+ found.push(access);
676
+ }
677
+ }
678
+ return found;
679
+ };
680
+ const discoverArgumentDerivedAccesses = (project, lunoraDirectory, config) => {
681
+ const accesses = [];
682
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
683
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
684
+ accesses.push(...accessesInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath), config));
685
+ }
686
+ return accesses;
687
+ };
688
+
689
+ const BROWSER_URL_METHODS = /* @__PURE__ */ new Set(["content", "pdf", "scrape", "screenshot"]);
690
+ const discoverBrowserUrlAccesses = (project, lunoraDirectory) => discoverArgumentDerivedAccesses(project, lunoraDirectory, {
691
+ argIndex: 0,
692
+ matchReceiver: (receiver) => receiver === "ctx.browser",
693
+ methods: BROWSER_URL_METHODS
694
+ });
695
+
696
+ const FUNCTION_CALLEES = /* @__PURE__ */ new Set(["createBrowser", "createInboundEmailHandler", "createPayment"]);
697
+ const CONSTRUCTOR_CALLEES = /* @__PURE__ */ new Set(["RateLimiter"]);
698
+ const CALLBACK_CALLEES = /* @__PURE__ */ new Set(["extend"]);
699
+ const keysFromObjectLiteral = (objectLiteral) => {
700
+ const presentKeys = [];
701
+ const trueKeys = [];
702
+ let hasSpread = false;
703
+ for (const property of objectLiteral.getProperties()) {
704
+ if (Node.isSpreadAssignment(property)) {
705
+ hasSpread = true;
706
+ continue;
707
+ }
708
+ if (Node.isPropertyAssignment(property)) {
709
+ const name = property.getName();
710
+ presentKeys.push(name);
711
+ const initializer = property.getInitializer();
712
+ if (initializer?.getKind() === SyntaxKind.TrueKeyword) {
713
+ trueKeys.push(name);
714
+ }
715
+ continue;
716
+ }
717
+ if (Node.isShorthandPropertyAssignment(property) || Node.isMethodDeclaration(property)) {
718
+ presentKeys.push(property.getName());
719
+ }
720
+ }
721
+ return { analyzable: !hasSpread, presentKeys, trueKeys };
722
+ };
723
+ const readConfigArgument = (argument) => argument && Node.isObjectLiteralExpression(argument) ? keysFromObjectLiteral(argument) : { analyzable: false, presentKeys: [], trueKeys: [] };
724
+ const objectLiteralFromReturnBlock = (block) => {
725
+ const statements = block.getStatements();
726
+ const [statement] = statements;
727
+ if (statements.length !== 1 || statement === void 0 || !Node.isReturnStatement(statement)) {
728
+ return void 0;
729
+ }
730
+ const expression = statement.getExpression();
731
+ return expression !== void 0 && Node.isObjectLiteralExpression(expression) ? expression : void 0;
732
+ };
733
+ const objectLiteralFromCallbackBody = (body) => {
734
+ if (Node.isObjectLiteralExpression(body)) {
735
+ return body;
736
+ }
737
+ if (Node.isParenthesizedExpression(body)) {
738
+ const inner = body.getExpression();
739
+ return Node.isObjectLiteralExpression(inner) ? inner : void 0;
740
+ }
741
+ return Node.isBlock(body) ? objectLiteralFromReturnBlock(body) : void 0;
742
+ };
743
+ const readCallbackArgument = (argument) => {
744
+ const callback = argument && (Node.isArrowFunction(argument) || Node.isFunctionExpression(argument)) ? argument : void 0;
745
+ const objectLiteral = callback && objectLiteralFromCallbackBody(callback.getBody());
746
+ return objectLiteral ? keysFromObjectLiteral(objectLiteral) : { analyzable: false, presentKeys: [], trueKeys: [] };
747
+ };
748
+ const configCallsInSourceFile = (sourceFile, relativePath) => {
749
+ const found = [];
750
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
751
+ const name = calleeName$2(call.getExpression());
752
+ if (name === void 0) {
753
+ continue;
754
+ }
755
+ if (FUNCTION_CALLEES.has(name)) {
756
+ found.push({ callee: name, file: relativePath, line: call.getStartLineNumber(), ...readConfigArgument(call.getArguments()[0]) });
757
+ } else if (CALLBACK_CALLEES.has(name)) {
758
+ found.push({ callee: name, file: relativePath, line: call.getStartLineNumber(), ...readCallbackArgument(call.getArguments()[0]) });
759
+ }
760
+ }
761
+ for (const construction of sourceFile.getDescendantsOfKind(SyntaxKind.NewExpression)) {
762
+ const name = calleeName$2(construction.getExpression());
763
+ if (name === void 0 || !CONSTRUCTOR_CALLEES.has(name)) {
764
+ continue;
765
+ }
766
+ found.push({
767
+ callee: name,
768
+ file: relativePath,
769
+ line: construction.getStartLineNumber(),
770
+ ...readConfigArgument(construction.getArguments()[0])
771
+ });
772
+ }
773
+ return found;
774
+ };
775
+ const discoverConfigCalls = (project, lunoraDirectory) => {
776
+ const calls = [];
777
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
778
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
779
+ calls.push(...configCallsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
780
+ }
781
+ return calls;
782
+ };
783
+
784
+ const CONTAINER_ACCESSOR_PREFIX = "ctx.containers.";
785
+ const isContainerInstanceReceiver = (receiver) => {
786
+ if (!receiver.startsWith(CONTAINER_ACCESSOR_PREFIX)) {
787
+ return false;
788
+ }
789
+ const remainder = receiver.slice(CONTAINER_ACCESSOR_PREFIX.length);
790
+ return remainder.length > 0 && !remainder.includes(".");
791
+ };
792
+ const discoverContainerKeyAccesses = (project, lunoraDirectory) => discoverArgumentDerivedAccesses(project, lunoraDirectory, {
793
+ argIndex: 0,
794
+ matchReceiver: isContainerInstanceReceiver,
795
+ methods: /* @__PURE__ */ new Set(["get"])
796
+ });
797
+
798
+ const EGRESS_MUTATING_METHODS = /* @__PURE__ */ new Set(["allow", "deny", "setAllowed"]);
799
+ const hasEnableInternetTrue = (call) => {
800
+ const argument = call.getArguments()[0];
801
+ if (!argument || !Node.isObjectLiteralExpression(argument)) {
802
+ return false;
803
+ }
804
+ const property = argument.getProperty("enableInternet");
805
+ return property !== void 0 && Node.isPropertyAssignment(property) && Node.isTrueLiteral(property.getInitializerOrThrow());
806
+ };
807
+ const enableInternetOverrideInCall = (call, relativePath) => {
808
+ const callee = call.getExpression();
809
+ if (!Node.isPropertyAccessExpression(callee) || callee.getName() !== "start" || !hasEnableInternetTrue(call)) {
810
+ return void 0;
811
+ }
812
+ return {
813
+ detail: "enableInternet: true",
814
+ exportName: enclosingExportName$1(call),
815
+ file: relativePath,
816
+ kind: "enable_internet",
817
+ line: call.getStartLineNumber()
818
+ };
819
+ };
820
+ const egressRelaxationInCall = (call, relativePath) => {
821
+ const callee = call.getExpression();
822
+ if (!Node.isPropertyAccessExpression(callee) || !EGRESS_MUTATING_METHODS.has(callee.getName())) {
823
+ return void 0;
824
+ }
825
+ const receiver = callee.getExpression();
826
+ if (!Node.isPropertyAccessExpression(receiver) || receiver.getName() !== "egress") {
827
+ return void 0;
828
+ }
829
+ return {
830
+ detail: callee.getName(),
831
+ exportName: enclosingExportName$1(call),
832
+ file: relativePath,
833
+ kind: "egress_relaxation",
834
+ line: call.getStartLineNumber()
835
+ };
836
+ };
837
+ const containerOverrideInCall = (call, relativePath) => enableInternetOverrideInCall(call, relativePath) ?? egressRelaxationInCall(call, relativePath);
838
+ const containerOverridesInSourceFile = (sourceFile, relativePath) => {
839
+ const found = [];
840
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
841
+ const row = containerOverrideInCall(call, relativePath);
842
+ if (row) {
843
+ found.push(row);
844
+ }
845
+ }
846
+ return found;
847
+ };
848
+ const discoverContainerOverrides = (project, lunoraDirectory) => {
849
+ const rows = [];
850
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
851
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
852
+ rows.push(...containerOverridesInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
853
+ }
854
+ return rows;
855
+ };
856
+
857
+ const ENV_FILENAME = "env.ts";
858
+ const ENV_MODULE_SPECIFIERS = /* @__PURE__ */ new Set(["@lunora/server", "lunorash/server"]);
859
+ const isDefineEnv = (identifier) => {
860
+ const symbol = identifier.getSymbol();
861
+ if (!symbol) {
862
+ return identifier.getText() === "defineEnv";
863
+ }
864
+ for (const declaration of symbol.getDeclarations()) {
865
+ if (!Node.isImportSpecifier(declaration)) {
866
+ continue;
867
+ }
868
+ if (!ENV_MODULE_SPECIFIERS.has(declaration.getImportDeclaration().getModuleSpecifierValue())) {
869
+ return false;
870
+ }
871
+ return declaration.getNameNode().getText() === "defineEnv";
872
+ }
873
+ return false;
874
+ };
875
+ const isEnvNamespaceImport = (identifier) => {
876
+ const symbol = identifier.getSymbol();
877
+ if (!symbol) {
878
+ return false;
879
+ }
880
+ for (const declaration of symbol.getDeclarations()) {
881
+ if (!Node.isNamespaceImport(declaration)) {
882
+ continue;
883
+ }
884
+ const importDeclaration = declaration.getFirstAncestorByKind(SyntaxKind.ImportDeclaration);
885
+ return importDeclaration !== void 0 && ENV_MODULE_SPECIFIERS.has(importDeclaration.getModuleSpecifierValue());
886
+ }
887
+ return false;
888
+ };
889
+ const isDefineEnvCallee = (callee) => {
890
+ if (Node.isIdentifier(callee)) {
891
+ return isDefineEnv(callee);
892
+ }
893
+ if (Node.isPropertyAccessExpression(callee)) {
894
+ const object = callee.getExpression();
895
+ return callee.getName() === "defineEnv" && Node.isIdentifier(object) && isEnvNamespaceImport(object);
896
+ }
897
+ return false;
898
+ };
899
+ const environmentsFromSource = (source) => {
900
+ const environments = [];
901
+ for (const declaration of source.getVariableDeclarations()) {
902
+ if (!declaration.isExported()) {
903
+ continue;
904
+ }
905
+ const initializer = declaration.getInitializer();
906
+ if (initializer?.getKind() !== SyntaxKind.CallExpression) {
907
+ continue;
908
+ }
909
+ const callExpression = initializer;
910
+ if (!isDefineEnvCallee(callExpression.getExpression())) {
911
+ continue;
912
+ }
913
+ const nameNode = declaration.getNameNode();
914
+ if (!Node.isIdentifier(nameNode)) {
915
+ throw diagnosticAt(nameNode, "defineEnv exports must be plain named exports (no destructuring)");
916
+ }
917
+ environments.push({ exportName: nameNode.getText() });
918
+ }
919
+ return environments;
920
+ };
921
+ const discoverEnv = (project, lunoraDirectory) => {
922
+ const envPath = join(lunoraDirectory, ENV_FILENAME);
923
+ if (!existsSync(envPath)) {
924
+ return void 0;
925
+ }
926
+ const source = project.getSourceFile(envPath) ?? project.addSourceFileAtPath(envPath);
927
+ const environments = environmentsFromSource(source);
928
+ if (environments.length === 0) {
929
+ return void 0;
930
+ }
931
+ if (environments.length > 1) {
932
+ throw diagnosticAt(
933
+ source,
934
+ `lunora/env.ts declares ${environments.length.toString()} defineEnv() contracts (${environments.map((env) => env.exportName).join(", ")}); exactly one is allowed`
935
+ );
936
+ }
937
+ return environments[0];
938
+ };
939
+
940
+ const OPTIONS_ARGUMENT_INDEX$1 = /* @__PURE__ */ new Map([
941
+ ["dbRateLimit", 2],
942
+ ["rateLimit", 2],
943
+ ["verifyTurnstileMiddleware", 0]
944
+ ]);
945
+ const RATE_LIMIT_CALLEES = /* @__PURE__ */ new Set(["dbRateLimit", "rateLimit"]);
946
+ const setsFailOpenTrue = (options) => {
947
+ if (!options || !Node.isObjectLiteralExpression(options)) {
948
+ return false;
949
+ }
950
+ const property = options.getProperty("failOpen");
951
+ return property !== void 0 && Node.isPropertyAssignment(property) && property.getInitializer()?.getKind() === SyntaxKind.TrueKeyword;
952
+ };
953
+ const limitNameOf$1 = (call) => {
954
+ const argument = call.getArguments()[1];
955
+ return argument && Node.isStringLiteral(argument) ? argument.getLiteralValue() : "";
956
+ };
957
+ const failOpenGuardInCall = (call, relativePath) => {
958
+ const callee = calleeName$2(call.getExpression());
959
+ if (callee === void 0) {
960
+ return void 0;
961
+ }
962
+ const optionsIndex = OPTIONS_ARGUMENT_INDEX$1.get(callee);
963
+ if (optionsIndex === void 0) {
964
+ return void 0;
965
+ }
966
+ return {
967
+ callee,
968
+ exportName: enclosingExportName$1(call),
969
+ failOpen: setsFailOpenTrue(call.getArguments()[optionsIndex]),
970
+ file: relativePath,
971
+ limitName: RATE_LIMIT_CALLEES.has(callee) ? limitNameOf$1(call) : "",
972
+ line: call.getStartLineNumber()
973
+ };
974
+ };
975
+ const failOpenGuardsInSourceFile = (sourceFile, relativePath) => {
976
+ const found = [];
977
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
978
+ const guard = failOpenGuardInCall(call, relativePath);
979
+ if (guard) {
980
+ found.push(guard);
981
+ }
982
+ }
983
+ return found;
984
+ };
985
+ const discoverFailOpenGuards = (project, lunoraDirectory) => {
986
+ const guards = [];
987
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
988
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
989
+ guards.push(...failOpenGuardsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
990
+ }
991
+ return guards;
992
+ };
993
+
994
+ const contextPropertiesRead = (sourceFile) => {
995
+ const reachesContext = (receiver) => Node.isIdentifier(receiver) && receiver.getText() === "ctx";
996
+ const names = /* @__PURE__ */ new Set();
997
+ for (const access of sourceFile.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression)) {
998
+ if (reachesContext(access.getExpression())) {
999
+ names.add(access.getName());
1000
+ }
1001
+ }
1002
+ for (const declaration of sourceFile.getDescendantsOfKind(SyntaxKind.VariableDeclaration)) {
1003
+ const initializer = declaration.getInitializer();
1004
+ const nameNode = declaration.getNameNode();
1005
+ if (initializer === void 0 || !reachesContext(initializer) || !Node.isObjectBindingPattern(nameNode)) {
1006
+ continue;
1007
+ }
1008
+ for (const element of nameNode.getElements()) {
1009
+ const name = element.getPropertyNameNode()?.getText() ?? element.getName();
1010
+ if (name) {
1011
+ names.add(name);
1012
+ }
1013
+ }
1014
+ }
1015
+ return names;
1016
+ };
1017
+ const discoverFeatureUsage = (project, lunoraDirectory) => {
1018
+ const usage = Object.fromEntries(CAPABILITIES.map((capability) => [capability.key, false]));
1019
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
1020
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
1021
+ const importSpecifiers = new Set(sourceFile.getImportDeclarations().map((declaration) => declaration.getModuleSpecifierValue()));
1022
+ const contextProperties = contextPropertiesRead(sourceFile);
1023
+ for (const capability of CAPABILITIES) {
1024
+ if (usage[capability.key]) {
1025
+ continue;
1026
+ }
1027
+ if (importSpecifiers.has(capability.moduleSpecifier)) {
1028
+ usage[capability.key] = true;
1029
+ continue;
1030
+ }
1031
+ if (capability.contextProperty !== void 0 && contextProperties.has(capability.contextProperty)) {
1032
+ usage[capability.key] = true;
1033
+ }
1034
+ }
1035
+ if (CAPABILITIES.every((capability) => usage[capability.key])) {
1036
+ break;
1037
+ }
1038
+ }
1039
+ return usage;
1040
+ };
1041
+ const buildStudioFeatures = (usage, signals) => {
1042
+ return {
1043
+ analytics: usage.analytics || signals.dependencies.has("@lunora/bindings/analytics"),
1044
+ auth: signals.dependencies.has("@lunora/auth"),
1045
+ containers: usage.container || signals.containerCount > 0 || signals.dependencies.has("@lunora/container"),
1046
+ flags: usage.flags || signals.dependencies.has("@lunora/flags"),
1047
+ kv: usage.kv || signals.dependencies.has("@lunora/bindings/kv"),
1048
+ mail: usage.mail || signals.dependencies.has("@lunora/mail"),
1049
+ payments: usage.payments || signals.dependencies.has("@lunora/payment"),
1050
+ queues: signals.queueCount > 0 || signals.dependencies.has("@lunora/queue"),
1051
+ scheduler: usage.scheduler || signals.cronCount > 0 || signals.dependencies.has("@lunora/scheduler"),
1052
+ storage: usage.storage || signals.storageRuleCount > 0 || signals.storageColumnCount > 0 || signals.dependencies.has("@lunora/storage"),
1053
+ vectors: usage.vectors || signals.vectorIndexCount > 0 || signals.dependencies.has("@lunora/bindings/vectors"),
1054
+ workflows: usage.workflows || signals.workflowCount > 0 || signals.dependencies.has("@lunora/workflow")
1055
+ };
1056
+ };
1057
+
1058
+ const isContextIdentifier = (node) => Node.isIdentifier(node) && node.getText() === "ctx";
1059
+ const isFlagBooleanRead = (callee) => {
1060
+ if (!Node.isPropertyAccessExpression(callee) || callee.getName() !== "boolean") {
1061
+ return false;
1062
+ }
1063
+ const receiver = callee.getExpression();
1064
+ return Node.isPropertyAccessExpression(receiver) && receiver.getName() === "flags" && isContextIdentifier(receiver.getExpression());
1065
+ };
1066
+ const booleanLiteralValue = (node) => {
1067
+ if (node?.getKind() === SyntaxKind.TrueKeyword) {
1068
+ return true;
1069
+ }
1070
+ if (node?.getKind() === SyntaxKind.FalseKeyword) {
1071
+ return false;
1072
+ }
1073
+ return void 0;
1074
+ };
1075
+ const flagSecurityDefaultInCall = (call, relativePath) => {
1076
+ if (!isFlagBooleanRead(call.getExpression())) {
1077
+ return void 0;
1078
+ }
1079
+ const [keyArgument, defaultArgument] = call.getArguments();
1080
+ if (!keyArgument || !(Node.isStringLiteral(keyArgument) || Node.isNoSubstitutionTemplateLiteral(keyArgument))) {
1081
+ return void 0;
1082
+ }
1083
+ const key = keyArgument.getLiteralValue();
1084
+ const defaultValue = booleanLiteralValue(defaultArgument);
1085
+ if (key.length === 0 || defaultValue === void 0) {
1086
+ return void 0;
1087
+ }
1088
+ return { defaultValue, exportName: enclosingExportName$1(call), file: relativePath, key, line: call.getStartLineNumber() };
1089
+ };
1090
+ const flagSecurityDefaultsInSourceFile = (sourceFile, relativePath) => {
1091
+ const found = [];
1092
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
1093
+ const flag = flagSecurityDefaultInCall(call, relativePath);
1094
+ if (flag) {
1095
+ found.push(flag);
1096
+ }
1097
+ }
1098
+ return found;
1099
+ };
1100
+ const discoverFlagSecurityDefaults = (project, lunoraDirectory) => {
1101
+ const flags = [];
1102
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
1103
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
1104
+ flags.push(...flagSecurityDefaultsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
1105
+ }
1106
+ return flags;
1107
+ };
1108
+
1109
+ const HTTP_VERBS = /* @__PURE__ */ new Set(["delete", "get", "head", "options", "patch", "post", "put"]);
1110
+ const TERMINAL_STEPS = /* @__PURE__ */ new Set(["handler", "stream"]);
1111
+ const RUN_SIDE_EFFECTS = /* @__PURE__ */ new Set(["runAction", "runMutation"]);
1112
+ const DB_WRITE_METHODS = /* @__PURE__ */ new Set(["delete", "insert", "insertManyUnsafe", "patch", "replace"]);
1113
+ const isIdentifierNamed = (node, name) => node !== void 0 && Node.isIdentifier(node) && node.getText() === name;
1114
+ const inlineHandler = (argument) => argument !== void 0 && (Node.isArrowFunction(argument) || Node.isFunctionExpression(argument)) ? argument : void 0;
1115
+ const contextBinding = (handler, isHttpAction) => {
1116
+ const parameter = handler.getParameters()[0];
1117
+ if (parameter === void 0) {
1118
+ return void 0;
1119
+ }
1120
+ const nameNode = parameter.getNameNode();
1121
+ if (isHttpAction) {
1122
+ return Node.isIdentifier(nameNode) ? nameNode.getText() : void 0;
1123
+ }
1124
+ if (!Node.isObjectBindingPattern(nameNode)) {
1125
+ return void 0;
1126
+ }
1127
+ for (const element of nameNode.getElements()) {
1128
+ const property = element.getPropertyNameNode()?.getText() ?? element.getNameNode().getText();
1129
+ if (property === "ctx") {
1130
+ const local = element.getNameNode();
1131
+ return Node.isIdentifier(local) ? local.getText() : void 0;
1132
+ }
1133
+ }
1134
+ return void 0;
1135
+ };
1136
+ const firstSideEffect = (handler, contextName) => {
1137
+ const body = handler.getBody();
1138
+ const calls = body.getDescendantsOfKind(SyntaxKind.CallExpression);
1139
+ if (Node.isCallExpression(body)) {
1140
+ calls.unshift(body);
1141
+ }
1142
+ for (const call of calls) {
1143
+ const callee = call.getExpression();
1144
+ if (!Node.isPropertyAccessExpression(callee)) {
1145
+ continue;
1146
+ }
1147
+ const method = callee.getName();
1148
+ const receiver = callee.getExpression();
1149
+ if (RUN_SIDE_EFFECTS.has(method) && isIdentifierNamed(receiver, contextName)) {
1150
+ return method;
1151
+ }
1152
+ if (DB_WRITE_METHODS.has(method) && Node.isPropertyAccessExpression(receiver) && receiver.getName() === "db" && isIdentifierNamed(receiver.getExpression(), contextName)) {
1153
+ return `db.${method}`;
1154
+ }
1155
+ }
1156
+ return void 0;
1157
+ };
1158
+ const readsContextAuth = (handler, contextName) => {
1159
+ const body = handler.getBody();
1160
+ for (const access of body.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression)) {
1161
+ if (access.getName() === "auth" && isIdentifierNamed(access.getExpression(), contextName)) {
1162
+ return true;
1163
+ }
1164
+ }
1165
+ for (const declaration of body.getDescendantsOfKind(SyntaxKind.VariableDeclaration)) {
1166
+ const nameNode = declaration.getNameNode();
1167
+ if (!Node.isObjectBindingPattern(nameNode) || !isIdentifierNamed(declaration.getInitializer(), contextName)) {
1168
+ continue;
1169
+ }
1170
+ for (const element of nameNode.getElements()) {
1171
+ const property = element.getPropertyNameNode()?.getText() ?? element.getNameNode().getText();
1172
+ if (property === "auth") {
1173
+ return true;
1174
+ }
1175
+ }
1176
+ }
1177
+ return false;
1178
+ };
1179
+ const httpRouteVerbOfTerminal = (terminalCall) => {
1180
+ const terminalCallee = terminalCall.getExpression();
1181
+ if (!Node.isPropertyAccessExpression(terminalCallee) || !TERMINAL_STEPS.has(terminalCallee.getName())) {
1182
+ return void 0;
1183
+ }
1184
+ let node = terminalCallee.getExpression();
1185
+ while (Node.isCallExpression(node)) {
1186
+ const callee = node.getExpression();
1187
+ if (!Node.isPropertyAccessExpression(callee)) {
1188
+ return void 0;
1189
+ }
1190
+ const step = callee.getName();
1191
+ if (HTTP_VERBS.has(step)) {
1192
+ const receiver = callee.getExpression();
1193
+ return Node.isIdentifier(receiver) && receiver.getText() === "httpRoute" ? step.toUpperCase() : void 0;
1194
+ }
1195
+ node = callee.getExpression();
1196
+ }
1197
+ return void 0;
1198
+ };
1199
+ const guardRowFromCall = (call, relativePath) => {
1200
+ const callee = call.getExpression();
1201
+ if (Node.isIdentifier(callee) && callee.getText() === "httpAction") {
1202
+ const handler2 = inlineHandler(call.getArguments()[0]);
1203
+ const contextName2 = handler2 && contextBinding(handler2, true);
1204
+ if (!handler2 || contextName2 === void 0) {
1205
+ return void 0;
1206
+ }
1207
+ const sideEffect2 = firstSideEffect(handler2, contextName2);
1208
+ return sideEffect2 === void 0 ? void 0 : {
1209
+ exportName: enclosingExportName$1(call),
1210
+ file: relativePath,
1211
+ kind: "httpAction",
1212
+ line: call.getStartLineNumber(),
1213
+ readsAuth: readsContextAuth(handler2, contextName2),
1214
+ sideEffect: sideEffect2
1215
+ };
1216
+ }
1217
+ const method = httpRouteVerbOfTerminal(call);
1218
+ if (method === void 0) {
1219
+ return void 0;
1220
+ }
1221
+ const handler = inlineHandler(call.getArguments()[0]);
1222
+ const contextName = handler && contextBinding(handler, false);
1223
+ if (!handler || contextName === void 0) {
1224
+ return void 0;
1225
+ }
1226
+ const sideEffect = firstSideEffect(handler, contextName);
1227
+ return sideEffect === void 0 ? void 0 : {
1228
+ exportName: enclosingExportName$1(call),
1229
+ file: relativePath,
1230
+ kind: "httpRoute",
1231
+ line: call.getStartLineNumber(),
1232
+ method,
1233
+ readsAuth: readsContextAuth(handler, contextName),
1234
+ sideEffect
1235
+ };
1236
+ };
1237
+ const guardsInSourceFile = (sourceFile, relativePath) => {
1238
+ const rows = [];
1239
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
1240
+ const row = guardRowFromCall(call, relativePath);
1241
+ if (row) {
1242
+ rows.push(row);
1243
+ }
1244
+ }
1245
+ return rows;
1246
+ };
1247
+ const discoverHttpActionGuards = (project, lunoraDirectory) => {
1248
+ const rows = [];
1249
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
1250
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
1251
+ rows.push(...guardsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
1252
+ }
1253
+ return rows;
1254
+ };
1255
+
1256
+ const SANITIZER_CALLEES = /* @__PURE__ */ new Set(["btoa", "encodeURI", "encodeURIComponent", "isSafeHeaderValue", "Number", "parseFloat", "parseInt"]);
1257
+ const HEADER_MUTATORS = /* @__PURE__ */ new Set(["append", "set"]);
1258
+ const calleeName$1 = (callee) => {
1259
+ if (Node.isIdentifier(callee)) {
1260
+ return callee.getText();
1261
+ }
1262
+ return Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
1263
+ };
1264
+ const staticHeaderName = (node) => {
1265
+ if (node !== void 0 && (Node.isStringLiteral(node) || Node.isNoSubstitutionTemplateLiteral(node))) {
1266
+ return node.getLiteralText();
1267
+ }
1268
+ return "";
1269
+ };
1270
+ const sanitizesRequestInput = (root, requestName) => {
1271
+ const calls = Node.isCallExpression(root) ? [root, ...root.getDescendantsOfKind(SyntaxKind.CallExpression)] : root.getDescendantsOfKind(SyntaxKind.CallExpression);
1272
+ return calls.some((call) => SANITIZER_CALLEES.has(calleeName$1(call.getExpression())) && referencesRequestInput(call, requestName));
1273
+ };
1274
+ const isSanitized = (valueNode, requestName) => {
1275
+ if (sanitizesRequestInput(valueNode, requestName)) {
1276
+ return true;
1277
+ }
1278
+ const hop = singleHopInitializer(valueNode);
1279
+ return hop !== void 0 && sanitizesRequestInput(hop, requestName);
1280
+ };
1281
+ const isUnsafe = (valueNode, requestName) => isRequestInputDerived(valueNode, requestName) && !isSanitized(valueNode, requestName);
1282
+ const isHeadersReceiver = (receiver) => {
1283
+ if (Node.isPropertyAccessExpression(receiver) && receiver.getName() === "headers") {
1284
+ return true;
1285
+ }
1286
+ const hop = singleHopInitializer(receiver);
1287
+ return hop !== void 0 && Node.isNewExpression(hop) && calleeName$1(hop.getExpression()) === "Headers";
1288
+ };
1289
+ const resolveObjectLiteral = (node) => {
1290
+ if (node === void 0) {
1291
+ return void 0;
1292
+ }
1293
+ if (Node.isObjectLiteralExpression(node)) {
1294
+ return node;
1295
+ }
1296
+ const hop = singleHopInitializer(node);
1297
+ return hop !== void 0 && Node.isObjectLiteralExpression(hop) ? hop : void 0;
1298
+ };
1299
+ const collectFromHeadersObject = (headersObject, via, context) => {
1300
+ for (const property of headersObject.getProperties()) {
1301
+ if (Node.isPropertyAssignment(property)) {
1302
+ const valueNode = property.getInitializer();
1303
+ if (valueNode !== void 0 && isUnsafe(valueNode, context.requestName)) {
1304
+ context.rows.push({
1305
+ exportName: context.exportName,
1306
+ file: context.relativePath,
1307
+ headerName: staticHeaderName(property.getNameNode()),
1308
+ line: valueNode.getStartLineNumber(),
1309
+ via
1310
+ });
1311
+ }
1312
+ } else if (Node.isShorthandPropertyAssignment(property)) {
1313
+ const valueNode = property.getNameNode();
1314
+ if (isUnsafe(valueNode, context.requestName)) {
1315
+ context.rows.push({
1316
+ exportName: context.exportName,
1317
+ file: context.relativePath,
1318
+ headerName: property.getName(),
1319
+ line: valueNode.getStartLineNumber(),
1320
+ via
1321
+ });
1322
+ }
1323
+ } else if (Node.isSpreadAssignment(property)) {
1324
+ const nested = resolveObjectLiteral(property.getExpression());
1325
+ if (nested !== void 0) {
1326
+ collectFromHeadersObject(nested, via, context);
1327
+ }
1328
+ }
1329
+ }
1330
+ };
1331
+ const collectFromResponseInit = (init, context) => {
1332
+ const initObject = resolveObjectLiteral(init);
1333
+ if (initObject === void 0) {
1334
+ return;
1335
+ }
1336
+ const headersProperty = initObject.getProperty("headers");
1337
+ if (headersProperty === void 0 || !Node.isPropertyAssignment(headersProperty)) {
1338
+ return;
1339
+ }
1340
+ const headersObject = resolveObjectLiteral(headersProperty.getInitializer());
1341
+ if (headersObject !== void 0) {
1342
+ collectFromHeadersObject(headersObject, "response-init", context);
1343
+ }
1344
+ };
1345
+ const collectFromNewExpression = (newExpression, context) => {
1346
+ const name = calleeName$1(newExpression.getExpression());
1347
+ if (name === "Response") {
1348
+ collectFromResponseInit(newExpression.getArguments()[1], context);
1349
+ } else if (name === "Headers") {
1350
+ const headersObject = resolveObjectLiteral(newExpression.getArguments()[0]);
1351
+ if (headersObject !== void 0) {
1352
+ collectFromHeadersObject(headersObject, "headers-ctor", context);
1353
+ }
1354
+ }
1355
+ };
1356
+ const collectFromCallExpression = (call, context) => {
1357
+ const callee = call.getExpression();
1358
+ if (!Node.isPropertyAccessExpression(callee)) {
1359
+ return;
1360
+ }
1361
+ const method = callee.getName();
1362
+ const receiver = callee.getExpression();
1363
+ if (method === "json" && Node.isIdentifier(receiver) && receiver.getText() === "Response") {
1364
+ collectFromResponseInit(call.getArguments()[1], context);
1365
+ return;
1366
+ }
1367
+ if (HEADER_MUTATORS.has(method) && isHeadersReceiver(receiver)) {
1368
+ const valueNode = call.getArguments()[1];
1369
+ if (valueNode !== void 0 && isUnsafe(valueNode, context.requestName)) {
1370
+ context.rows.push({
1371
+ exportName: context.exportName,
1372
+ file: context.relativePath,
1373
+ headerName: staticHeaderName(call.getArguments()[0]),
1374
+ line: valueNode.getStartLineNumber(),
1375
+ // `method` is guarded to the two `HEADER_MUTATORS` above, so this covers every case.
1376
+ via: method === "set" ? "headers-set" : "headers-append"
1377
+ });
1378
+ }
1379
+ }
1380
+ };
1381
+ const headerWritesInHandler = (handler, context) => {
1382
+ for (const newExpression of handler.getDescendantsOfKind(SyntaxKind.NewExpression)) {
1383
+ collectFromNewExpression(newExpression, context);
1384
+ }
1385
+ for (const call of handler.getDescendantsOfKind(SyntaxKind.CallExpression)) {
1386
+ collectFromCallExpression(call, context);
1387
+ }
1388
+ };
1389
+ const headerWritesFromCall = (call, relativePath) => {
1390
+ const callee = call.getExpression();
1391
+ if (!Node.isIdentifier(callee) || callee.getText() !== "httpAction") {
1392
+ return [];
1393
+ }
1394
+ const handler = inlineHandler$1(call.getArguments()[0]);
1395
+ if (handler === void 0) {
1396
+ return [];
1397
+ }
1398
+ const requestParameter = handler.getParameters()[1];
1399
+ if (requestParameter === void 0) {
1400
+ return [];
1401
+ }
1402
+ const nameNode = requestParameter.getNameNode();
1403
+ if (!Node.isIdentifier(nameNode)) {
1404
+ return [];
1405
+ }
1406
+ const rows = [];
1407
+ headerWritesInHandler(handler, { exportName: enclosingExportName$1(call), relativePath, requestName: nameNode.getText(), rows });
1408
+ return rows;
1409
+ };
1410
+ const headerWritesInSourceFile = (sourceFile, relativePath) => {
1411
+ const rows = [];
1412
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
1413
+ rows.push(...headerWritesFromCall(call, relativePath));
1414
+ }
1415
+ return rows;
1416
+ };
1417
+ const discoverHttpHeaderWrites = (project, lunoraDirectory) => {
1418
+ const rows = [];
1419
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
1420
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
1421
+ rows.push(...headerWritesInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
1422
+ }
1423
+ return rows;
1424
+ };
1425
+
1426
+ const IDENTITY_FILENAME = "identity.ts";
1427
+ const IDENTITY_MODULE_SPECIFIERS = /* @__PURE__ */ new Set(["@lunora/server", "lunorash/server"]);
1428
+ const isDefineIdentity = (identifier) => {
1429
+ const symbol = identifier.getSymbol();
1430
+ if (!symbol) {
1431
+ return identifier.getText() === "defineIdentity";
1432
+ }
1433
+ for (const declaration of symbol.getDeclarations()) {
1434
+ if (!Node.isImportSpecifier(declaration)) {
1435
+ continue;
1436
+ }
1437
+ if (!IDENTITY_MODULE_SPECIFIERS.has(declaration.getImportDeclaration().getModuleSpecifierValue())) {
1438
+ return false;
1439
+ }
1440
+ return declaration.getNameNode().getText() === "defineIdentity";
1441
+ }
1442
+ return false;
1443
+ };
1444
+ const isIdentityNamespaceImport = (identifier) => {
1445
+ const symbol = identifier.getSymbol();
1446
+ if (!symbol) {
1447
+ return false;
1448
+ }
1449
+ for (const declaration of symbol.getDeclarations()) {
1450
+ if (!Node.isNamespaceImport(declaration)) {
1451
+ continue;
1452
+ }
1453
+ const importDeclaration = declaration.getFirstAncestorByKind(SyntaxKind.ImportDeclaration);
1454
+ return importDeclaration !== void 0 && IDENTITY_MODULE_SPECIFIERS.has(importDeclaration.getModuleSpecifierValue());
1455
+ }
1456
+ return false;
1457
+ };
1458
+ const isDefineIdentityCallee = (callee) => {
1459
+ if (Node.isIdentifier(callee)) {
1460
+ return isDefineIdentity(callee);
1461
+ }
1462
+ if (Node.isPropertyAccessExpression(callee)) {
1463
+ const object = callee.getExpression();
1464
+ return callee.getName() === "defineIdentity" && Node.isIdentifier(object) && isIdentityNamespaceImport(object);
1465
+ }
1466
+ return false;
1467
+ };
1468
+ const identitiesFromSource = (source) => {
1469
+ const identities = [];
1470
+ for (const declaration of source.getVariableDeclarations()) {
1471
+ if (!declaration.isExported()) {
1472
+ continue;
1473
+ }
1474
+ const initializer = declaration.getInitializer();
1475
+ if (initializer?.getKind() !== SyntaxKind.CallExpression) {
1476
+ continue;
1477
+ }
1478
+ const callExpression = initializer;
1479
+ if (!isDefineIdentityCallee(callExpression.getExpression())) {
1480
+ continue;
1481
+ }
1482
+ const nameNode = declaration.getNameNode();
1483
+ if (!Node.isIdentifier(nameNode)) {
1484
+ throw diagnosticAt(nameNode, "defineIdentity exports must be plain named exports (no destructuring)");
1485
+ }
1486
+ identities.push({ exportName: nameNode.getText() });
1487
+ }
1488
+ return identities;
1489
+ };
1490
+ const discoverIdentity = (project, lunoraDirectory) => {
1491
+ const identityPath = join(lunoraDirectory, IDENTITY_FILENAME);
1492
+ if (!existsSync(identityPath)) {
1493
+ return void 0;
1494
+ }
1495
+ const source = project.getSourceFile(identityPath) ?? project.addSourceFileAtPath(identityPath);
1496
+ const identities = identitiesFromSource(source);
1497
+ if (identities.length === 0) {
1498
+ return void 0;
1499
+ }
1500
+ if (identities.length > 1) {
1501
+ throw diagnosticAt(
1502
+ source,
1503
+ `lunora/identity.ts declares ${identities.length.toString()} defineIdentity() contracts (${identities.map((identity) => identity.exportName).join(", ")}); exactly one is allowed`
1504
+ );
1505
+ }
1506
+ return identities[0];
1507
+ };
1508
+
1509
+ const IDENTITY_RECEIVERS = /* @__PURE__ */ new Set(["auth", "context.auth", "ctx.auth"]);
1510
+ const ALWAYS_DECLARED_CLAIM = "userId";
1511
+ const declaredClaimKeys = (objectLiteral) => {
1512
+ const keys = /* @__PURE__ */ new Set();
1513
+ for (const property of objectLiteral.getProperties()) {
1514
+ if (Node.isSpreadAssignment(property)) {
1515
+ return void 0;
1516
+ }
1517
+ if (Node.isPropertyAssignment(property) || Node.isShorthandPropertyAssignment(property) || Node.isMethodDeclaration(property)) {
1518
+ keys.add(property.getName());
1519
+ }
1520
+ }
1521
+ return keys;
1522
+ };
1523
+ const resolveDeclaredClaims = (project, lunoraDirectory) => {
1524
+ const identityPath = join(lunoraDirectory, IDENTITY_FILENAME);
1525
+ if (!existsSync(identityPath)) {
1526
+ return void 0;
1527
+ }
1528
+ const source = project.getSourceFile(identityPath) ?? project.addSourceFileAtPath(identityPath);
1529
+ for (const call of source.getDescendantsOfKind(SyntaxKind.CallExpression)) {
1530
+ if (calleeName$2(call.getExpression()) !== "defineIdentity") {
1531
+ continue;
1532
+ }
1533
+ const [argument] = call.getArguments();
1534
+ return argument && Node.isObjectLiteralExpression(argument) ? declaredClaimKeys(argument) : void 0;
1535
+ }
1536
+ return void 0;
1537
+ };
1538
+ const identityBagAccess = (node) => {
1539
+ if (node === void 0 || !Node.isPropertyAccessExpression(node) || node.getName() !== "identity") {
1540
+ return void 0;
1541
+ }
1542
+ return IDENTITY_RECEIVERS.has(node.getExpression().getText()) ? node : void 0;
1543
+ };
1544
+ const claimKeyRead = (node) => {
1545
+ if (Node.isPropertyAccessExpression(node) && identityBagAccess(node.getExpression()) !== void 0) {
1546
+ return node.getName();
1547
+ }
1548
+ if (Node.isElementAccessExpression(node) && identityBagAccess(node.getExpression()) !== void 0) {
1549
+ const argument = node.getArgumentExpression();
1550
+ return argument && Node.isStringLiteral(argument) ? argument.getLiteralValue() : void 0;
1551
+ }
1552
+ return void 0;
1553
+ };
1554
+ const claimReadsInSourceFile = (sourceFile, relativePath, declared) => {
1555
+ const found = [];
1556
+ for (const node of sourceFile.getDescendants()) {
1557
+ const key = claimKeyRead(node);
1558
+ if (key === void 0) {
1559
+ continue;
1560
+ }
1561
+ found.push({
1562
+ declared: key === ALWAYS_DECLARED_CLAIM || declared.has(key),
1563
+ exportName: enclosingExportName$1(node),
1564
+ file: relativePath,
1565
+ key,
1566
+ line: node.getStartLineNumber()
1567
+ });
1568
+ }
1569
+ return found;
1570
+ };
1571
+ const discoverIdentityClaimReads = (project, lunoraDirectory) => {
1572
+ const declared = resolveDeclaredClaims(project, lunoraDirectory);
1573
+ if (declared === void 0) {
1574
+ return [];
1575
+ }
1576
+ const rows = [];
1577
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
1578
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
1579
+ rows.push(...claimReadsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath), declared));
1580
+ }
1581
+ return rows;
1582
+ };
1583
+
1584
+ const keyInitializer$1 = (options) => {
1585
+ if (!Node.isObjectLiteralExpression(options)) {
1586
+ return void 0;
1587
+ }
1588
+ const property = options.getProperty("key");
1589
+ return property && Node.isPropertyAssignment(property) ? property.getInitializer() : void 0;
1590
+ };
1591
+ const imageDeliveryUrlAccessInCall = (call, relativePath) => {
1592
+ if (calleeName$2(call.getExpression()) !== "buildImageDeliveryUrl") {
1593
+ return void 0;
1594
+ }
1595
+ const options = call.getArguments()[0];
1596
+ if (!options) {
1597
+ return void 0;
1598
+ }
1599
+ const key = keyInitializer$1(options);
1600
+ if (!key || !isArgumentDerived(key) || isScopedByContext(key)) {
1601
+ return void 0;
1602
+ }
1603
+ return { exportName: enclosingExportName$1(call), file: relativePath, line: call.getStartLineNumber() };
1604
+ };
1605
+ const imageDeliveryUrlAccessesInSourceFile = (sourceFile, relativePath) => {
1606
+ const found = [];
1607
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
1608
+ const access = imageDeliveryUrlAccessInCall(call, relativePath);
1609
+ if (access) {
1610
+ found.push(access);
1611
+ }
1612
+ }
1613
+ return found;
1614
+ };
1615
+ const discoverImageDeliveryUrlAccesses = (project, lunoraDirectory) => {
1616
+ const accesses = [];
1617
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
1618
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
1619
+ accesses.push(...imageDeliveryUrlAccessesInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
1620
+ }
1621
+ return accesses;
1622
+ };
1623
+
1624
+ const KV_KEY_METHODS = /* @__PURE__ */ new Set(["delete", "get", "getRaw", "getWithMetadata", "put"]);
1625
+ const discoverKvKeyAccesses = (project, lunoraDirectory) => discoverArgumentDerivedAccesses(project, lunoraDirectory, {
1626
+ argIndex: 0,
1627
+ matchReceiver: (receiver) => receiver === "ctx.kv" || receiver.startsWith("ctx.kv."),
1628
+ methods: KV_KEY_METHODS
1629
+ });
1630
+
1631
+ const MAIL_METHODS = /* @__PURE__ */ new Set(["queue", "send"]);
1632
+ const RECIPIENT_FIELDS = /* @__PURE__ */ new Set(["bcc", "cc", "to"]);
1633
+ const mailRecipientMethod = (node) => {
1634
+ if (!Node.isPropertyAccessExpression(node)) {
1635
+ return void 0;
1636
+ }
1637
+ const method = node.getName();
1638
+ if (!MAIL_METHODS.has(method)) {
1639
+ return void 0;
1640
+ }
1641
+ const receiver = node.getExpression().getText();
1642
+ return receiver === "ctx.mail" || receiver === "ctx.email" ? method : void 0;
1643
+ };
1644
+ const hasUnscopedArgumentDerivedRecipient = (argument) => {
1645
+ if (!Node.isObjectLiteralExpression(argument)) {
1646
+ return false;
1647
+ }
1648
+ return argument.getProperties().some((property) => {
1649
+ if (Node.isShorthandPropertyAssignment(property)) {
1650
+ const value2 = property.getNameNode();
1651
+ return RECIPIENT_FIELDS.has(property.getName()) && isArgumentDerived(value2) && !isScopedByContext(value2);
1652
+ }
1653
+ if (!Node.isPropertyAssignment(property) || !RECIPIENT_FIELDS.has(property.getName())) {
1654
+ return false;
1655
+ }
1656
+ const value = property.getInitializer();
1657
+ return value !== void 0 && isArgumentDerived(value) && !isScopedByContext(value);
1658
+ });
1659
+ };
1660
+ const mailAccessInCall = (call, relativePath) => {
1661
+ const method = mailRecipientMethod(call.getExpression());
1662
+ if (method === void 0) {
1663
+ return void 0;
1664
+ }
1665
+ const options = call.getArguments()[0];
1666
+ if (!options || !hasUnscopedArgumentDerivedRecipient(options)) {
1667
+ return void 0;
1668
+ }
1669
+ return { exportName: enclosingExportName$1(call), file: relativePath, line: call.getStartLineNumber(), method };
1670
+ };
1671
+ const mailAccessesInSourceFile = (sourceFile, relativePath) => {
1672
+ const found = [];
1673
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
1674
+ const access = mailAccessInCall(call, relativePath);
1675
+ if (access) {
1676
+ found.push(access);
1677
+ }
1678
+ }
1679
+ return found;
1680
+ };
1681
+ const discoverMailRecipientAccesses = (project, lunoraDirectory) => {
1682
+ const accesses = [];
1683
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
1684
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
1685
+ accesses.push(...mailAccessesInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
1686
+ }
1687
+ return accesses;
1688
+ };
1689
+
1690
+ const isDatabaseReplaceCall = (call) => {
1691
+ const callee = call.getExpression();
1692
+ if (!Node.isPropertyAccessExpression(callee) || callee.getName() !== "replace") {
1693
+ return false;
1694
+ }
1695
+ const receiver = callee.getExpression();
1696
+ if (Node.isPropertyAccessExpression(receiver)) {
1697
+ return receiver.getName() === "db";
1698
+ }
1699
+ return Node.isIdentifier(receiver) && receiver.getText() === "db";
1700
+ };
1701
+ const serverImplNode = (call) => {
1702
+ const argument = call.getArguments()[0];
1703
+ if (argument === void 0 || !Node.isObjectLiteralExpression(argument)) {
1704
+ return void 0;
1705
+ }
1706
+ const property = argument.getProperty("server");
1707
+ if (property === void 0) {
1708
+ return void 0;
1709
+ }
1710
+ return Node.isPropertyAssignment(property) ? property.getInitializer() : property;
1711
+ };
1712
+ const exportedDefineMutatorCall = (declaration) => {
1713
+ if (!declaration.isExported()) {
1714
+ return void 0;
1715
+ }
1716
+ const initializer = declaration.getInitializer();
1717
+ if (initializer?.getKind() !== SyntaxKind.CallExpression) {
1718
+ return void 0;
1719
+ }
1720
+ const call = initializer;
1721
+ return isDefineMutatorCallee(call.getExpression()) ? call : void 0;
1722
+ };
1723
+ const writesFromDeclaration = (declaration) => {
1724
+ const call = exportedDefineMutatorCall(declaration);
1725
+ const serverImpl = call === void 0 ? void 0 : serverImplNode(call);
1726
+ if (serverImpl === void 0) {
1727
+ return [];
1728
+ }
1729
+ const nameNode = declaration.getNameNode();
1730
+ const exportName = Node.isIdentifier(nameNode) ? nameNode.getText() : "";
1731
+ return serverImpl.getDescendantsOfKind(SyntaxKind.CallExpression).filter((replaceCall) => isDatabaseReplaceCall(replaceCall)).map((replaceCall) => {
1732
+ return { exportName, file: "lunora/mutators.ts", line: replaceCall.getStartLineNumber() };
1733
+ });
1734
+ };
1735
+ const mutatorWritesFromSource = (source) => source.getVariableDeclarations().flatMap((declaration) => writesFromDeclaration(declaration));
1736
+ const discoverMutatorWrites = (project, lunoraDirectory) => {
1737
+ const mutatorsPath = join(lunoraDirectory, MUTATORS_FILENAME);
1738
+ if (!existsSync(mutatorsPath)) {
1739
+ return [];
1740
+ }
1741
+ const source = project.getSourceFile(mutatorsPath) ?? project.addSourceFileAtPath(mutatorsPath);
1742
+ return mutatorWritesFromSource(source);
1743
+ };
1744
+
1745
+ const SINK_METHODS = /* @__PURE__ */ new Set(["delete", "get", "patch"]);
1746
+ const OWNERSHIP_IDENTIFIER_NAMES = /* @__PURE__ */ new Set([
1747
+ "accountId",
1748
+ "authorId",
1749
+ "companyId",
1750
+ "createdBy",
1751
+ "createdById",
1752
+ "customerId",
1753
+ "groupId",
1754
+ "memberId",
1755
+ "organizationId",
1756
+ "orgId",
1757
+ "ownerId",
1758
+ "projectId",
1759
+ "teamId",
1760
+ "tenantId",
1761
+ "userId",
1762
+ "workspaceId"
1763
+ ]);
1764
+ const IDENTITY_ACCESSORS = /* @__PURE__ */ new Set(["auth", "identity", "session", "user"]);
1765
+ const EQUALITY_OPERATORS = /* @__PURE__ */ new Set([
1766
+ SyntaxKind.EqualsEqualsEqualsToken,
1767
+ SyntaxKind.EqualsEqualsToken,
1768
+ SyntaxKind.ExclamationEqualsEqualsToken,
1769
+ SyntaxKind.ExclamationEqualsToken
1770
+ ]);
1771
+ const normalizeIdTable = (call) => {
1772
+ const callee = call.getExpression();
1773
+ if (!Node.isPropertyAccessExpression(callee) || callee.getName() !== "normalizeId" || !isDatabaseAccessor(callee.getExpression())) {
1774
+ return void 0;
1775
+ }
1776
+ const first = call.getArguments()[0];
1777
+ return first && Node.isStringLiteral(first) ? first.getLiteralText() : "";
1778
+ };
1779
+ const bindingNameOf = (call) => {
1780
+ let node = call;
1781
+ let parent = node.getParent();
1782
+ while (parent !== void 0 && (Node.isAsExpression(parent) || Node.isParenthesizedExpression(parent) || Node.isNonNullExpression(parent))) {
1783
+ node = parent;
1784
+ parent = node.getParent();
1785
+ }
1786
+ if (parent === void 0 || !Node.isVariableDeclaration(parent)) {
1787
+ return void 0;
1788
+ }
1789
+ const nameNode = parent.getNameNode();
1790
+ return Node.isIdentifier(nameNode) ? nameNode.getText() : void 0;
1791
+ };
1792
+ const conditionReferences = (condition, name) => condition.getDescendantsOfKind(SyntaxKind.Identifier).some((identifier) => identifier.getText() === name);
1793
+ const exitsControlFlow = (node) => Node.isThrowStatement(node) || Node.isReturnStatement(node) || node.getDescendantsOfKind(SyntaxKind.ThrowStatement).length > 0 || node.getDescendantsOfKind(SyntaxKind.ReturnStatement).length > 0;
1794
+ const hasNullGate = (handler, name) => handler.getDescendantsOfKind(SyntaxKind.IfStatement).some((ifStatement) => conditionReferences(ifStatement.getExpression(), name) && exitsControlFlow(ifStatement.getThenStatement()));
1795
+ const idSinkMethod = (handler, name) => {
1796
+ for (const call of handler.getDescendantsOfKind(SyntaxKind.CallExpression)) {
1797
+ const callee = call.getExpression();
1798
+ if (!Node.isPropertyAccessExpression(callee)) {
1799
+ continue;
1800
+ }
1801
+ const method = callee.getName();
1802
+ if (!SINK_METHODS.has(method)) {
1803
+ continue;
1804
+ }
1805
+ const receiver = callee.getExpression();
1806
+ const onDatabase = isDatabaseAccessor(receiver) || Node.isPropertyAccessExpression(receiver) && isDatabaseAccessor(receiver.getExpression());
1807
+ if (!onDatabase) {
1808
+ continue;
1809
+ }
1810
+ const first = call.getArguments()[0];
1811
+ if (first !== void 0 && Node.isIdentifier(first) && first.getText() === name) {
1812
+ return method;
1813
+ }
1814
+ }
1815
+ return void 0;
1816
+ };
1817
+ const rootIdentifierName = (node) => {
1818
+ let current = node;
1819
+ while (Node.isPropertyAccessExpression(current) || Node.isElementAccessExpression(current) || Node.isNonNullExpression(current) || Node.isParenthesizedExpression(current) || Node.isAsExpression(current) || Node.isAwaitExpression(current)) {
1820
+ current = current.getExpression();
1821
+ }
1822
+ return Node.isIdentifier(current) ? current.getText() : void 0;
1823
+ };
1824
+ const delegatesContextToHelper = (handler) => handler.getDescendantsOfKind(SyntaxKind.CallExpression).some((call) => call.getArguments().some((argument) => rootIdentifierName(argument) === "ctx"));
1825
+ const comparesRowProperty = (handler) => handler.getDescendantsOfKind(SyntaxKind.BinaryExpression).some((binary) => {
1826
+ if (!EQUALITY_OPERATORS.has(binary.getOperatorToken().getKind())) {
1827
+ return false;
1828
+ }
1829
+ return Node.isPropertyAccessExpression(binary.getLeft()) || Node.isPropertyAccessExpression(binary.getRight());
1830
+ });
1831
+ const handlerMentionsOwnership = (handler) => {
1832
+ for (const identifier of handler.getDescendantsOfKind(SyntaxKind.Identifier)) {
1833
+ if (OWNERSHIP_IDENTIFIER_NAMES.has(identifier.getText())) {
1834
+ return true;
1835
+ }
1836
+ }
1837
+ for (const access of handler.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression)) {
1838
+ const root = access.getExpression();
1839
+ if (Node.isIdentifier(root) && root.getText() === "ctx" && IDENTITY_ACCESSORS.has(access.getName())) {
1840
+ return true;
1841
+ }
1842
+ }
1843
+ return delegatesContextToHelper(handler) || comparesRowProperty(handler);
1844
+ };
1845
+ const normalizeIdAuthorizationsInDeclaration = (declaration, relativePath) => {
1846
+ if (!Node.isVariableDeclaration(declaration)) {
1847
+ return [];
1848
+ }
1849
+ const initializer = declaration.getInitializer();
1850
+ if (initializer === void 0 || !Node.isCallExpression(initializer)) {
1851
+ return [];
1852
+ }
1853
+ const classified = classifyProcedureCall(initializer);
1854
+ if (classified?.kind !== "query" && classified?.kind !== "mutation") {
1855
+ return [];
1856
+ }
1857
+ const handler = procedureHandler(initializer);
1858
+ if (handler === void 0) {
1859
+ return [];
1860
+ }
1861
+ const usesRls = classified.receiver !== void 0 && chainUsesWrappedCall(classified.receiver, "use", "rls");
1862
+ const mentionsOwnership = handlerMentionsOwnership(handler);
1863
+ const seen = /* @__PURE__ */ new Set();
1864
+ const rows = [];
1865
+ for (const call of handler.getDescendantsOfKind(SyntaxKind.CallExpression)) {
1866
+ const table = normalizeIdTable(call);
1867
+ if (table === void 0) {
1868
+ continue;
1869
+ }
1870
+ const name = bindingNameOf(call);
1871
+ if (name === void 0 || seen.has(name) || !hasNullGate(handler, name)) {
1872
+ continue;
1873
+ }
1874
+ const sinkMethod = idSinkMethod(handler, name);
1875
+ if (sinkMethod === void 0) {
1876
+ continue;
1877
+ }
1878
+ seen.add(name);
1879
+ rows.push({
1880
+ exportName: declaration.getName(),
1881
+ file: relativePath,
1882
+ line: call.getStartLineNumber(),
1883
+ mentionsOwnership,
1884
+ sinkMethod,
1885
+ table,
1886
+ usesRls,
1887
+ visibility: classified.visibility
1888
+ });
1889
+ }
1890
+ return rows;
1891
+ };
1892
+ const discoverNormalizeIdAuthorization = (project, lunoraDirectory) => {
1893
+ const rows = [];
1894
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
1895
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
1896
+ const relativePath = lunoraRelativePath(lunoraDirectory, filePath);
1897
+ for (const statement of sourceFile.getVariableStatements()) {
1898
+ if (!statement.isExported()) {
1899
+ continue;
1900
+ }
1901
+ for (const declaration of statement.getDeclarations()) {
1902
+ rows.push(...normalizeIdAuthorizationsInDeclaration(declaration, relativePath));
1903
+ }
1904
+ }
1905
+ }
1906
+ return rows;
1907
+ };
1908
+
1909
+ const IDENTITY_FIELDS = /* @__PURE__ */ new Set([
1910
+ "accountId",
1911
+ "authorId",
1912
+ "createdBy",
1913
+ "createdById",
1914
+ "organizationId",
1915
+ "orgId",
1916
+ "ownerId",
1917
+ "tenantId",
1918
+ "updatedBy",
1919
+ "userId",
1920
+ "workspaceId"
1921
+ ]);
1922
+ const IDENTITY_WRITE_METHODS = /* @__PURE__ */ new Set(["insert", "insertManyUnsafe", "patch", "replace"]);
1923
+ const contextDatabaseWriteMethod = (node) => {
1924
+ if (!Node.isPropertyAccessExpression(node)) {
1925
+ return void 0;
1926
+ }
1927
+ const method = node.getName();
1928
+ if (!IDENTITY_WRITE_METHODS.has(method)) {
1929
+ return void 0;
1930
+ }
1931
+ const database = node.getExpression();
1932
+ if (!Node.isPropertyAccessExpression(database) || database.getName() !== "db") {
1933
+ return void 0;
1934
+ }
1935
+ const context = database.getExpression();
1936
+ return Node.isIdentifier(context) && context.getText() === "ctx" ? method : void 0;
1937
+ };
1938
+ const documentObjectLiterals = (documentArgument, method) => {
1939
+ if (method !== "insertManyUnsafe") {
1940
+ return Node.isObjectLiteralExpression(documentArgument) ? [documentArgument] : [];
1941
+ }
1942
+ if (!Node.isArrayLiteralExpression(documentArgument)) {
1943
+ return [];
1944
+ }
1945
+ const objectLiterals = [];
1946
+ for (const element of documentArgument.getElements()) {
1947
+ if (Node.isObjectLiteralExpression(element)) {
1948
+ objectLiterals.push(element);
1949
+ }
1950
+ }
1951
+ return objectLiterals;
1952
+ };
1953
+ const identityWritesInObjectLiteral = (objectLiteral, method, call, relativePath) => {
1954
+ const rows = [];
1955
+ for (const property of objectLiteral.getProperties()) {
1956
+ let name;
1957
+ let value;
1958
+ if (Node.isPropertyAssignment(property)) {
1959
+ name = property.getName();
1960
+ value = property.getInitializer();
1961
+ } else if (Node.isShorthandPropertyAssignment(property)) {
1962
+ name = property.getName();
1963
+ value = property.getNameNode();
1964
+ }
1965
+ if (name === void 0 || value === void 0 || !IDENTITY_FIELDS.has(name)) {
1966
+ continue;
1967
+ }
1968
+ if (isArgumentDerived(value) && !isScopedByContext(value)) {
1969
+ rows.push({ exportName: enclosingExportName$1(call), field: name, file: relativePath, line: call.getStartLineNumber(), method });
1970
+ }
1971
+ }
1972
+ return rows;
1973
+ };
1974
+ const ownerFieldWritesInCall = (call, relativePath) => {
1975
+ const method = contextDatabaseWriteMethod(call.getExpression());
1976
+ if (method === void 0) {
1977
+ return [];
1978
+ }
1979
+ const documentArgument = call.getArguments()[1];
1980
+ if (!documentArgument) {
1981
+ return [];
1982
+ }
1983
+ return documentObjectLiterals(documentArgument, method).flatMap(
1984
+ (objectLiteral) => identityWritesInObjectLiteral(objectLiteral, method, call, relativePath)
1985
+ );
1986
+ };
1987
+ const ownerFieldWritesInSourceFile = (sourceFile, relativePath) => {
1988
+ const found = [];
1989
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
1990
+ found.push(...ownerFieldWritesInCall(call, relativePath));
1991
+ }
1992
+ return found;
1993
+ };
1994
+ const discoverOwnerFieldWrites = (project, lunoraDirectory) => {
1995
+ const writes = [];
1996
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
1997
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
1998
+ writes.push(...ownerFieldWritesInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
1999
+ }
2000
+ return writes;
2001
+ };
2002
+
2003
+ const discoverPackageDependencies = (projectRoot) => {
2004
+ const manifestPath = join(projectRoot, "package.json");
2005
+ if (!existsSync(manifestPath)) {
2006
+ return /* @__PURE__ */ new Set();
2007
+ }
2008
+ try {
2009
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
2010
+ const names = /* @__PURE__ */ new Set();
2011
+ for (const field of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
2012
+ const section = manifest[field];
2013
+ if (section !== null && typeof section === "object") {
2014
+ for (const name of Object.keys(section)) {
2015
+ names.add(name);
2016
+ }
2017
+ }
2018
+ }
2019
+ return names;
2020
+ } catch {
2021
+ return /* @__PURE__ */ new Set();
2022
+ }
2023
+ };
2024
+
2025
+ const WEBHOOK_ADAPTER_CALLEES = /* @__PURE__ */ new Set(["createAutumnAdapter", "createDodoPaymentsAdapter", "createPolarAdapter", "createStripeAdapter"]);
2026
+ const numericLiteralValue$1 = (node) => node && Node.isNumericLiteral(node) ? Number(node.getText()) : void 0;
2027
+ const toleranceFromOptions = (objectLiteral) => {
2028
+ const property = objectLiteral.getProperty("webhookToleranceSeconds");
2029
+ return property && Node.isPropertyAssignment(property) ? numericLiteralValue$1(property.getInitializer()) : void 0;
2030
+ };
2031
+ const paymentWebhooksInSourceFile = (sourceFile, relativePath) => {
2032
+ const rows = [];
2033
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
2034
+ const callee = calleeName$2(call.getExpression());
2035
+ if (callee === void 0 || !WEBHOOK_ADAPTER_CALLEES.has(callee)) {
2036
+ continue;
2037
+ }
2038
+ const [argument] = call.getArguments();
2039
+ const toleranceSeconds = argument && Node.isObjectLiteralExpression(argument) ? toleranceFromOptions(argument) : void 0;
2040
+ rows.push({
2041
+ callee,
2042
+ exportName: enclosingExportName$1(call),
2043
+ file: relativePath,
2044
+ line: call.getStartLineNumber(),
2045
+ ...toleranceSeconds === void 0 ? {} : { toleranceSeconds }
2046
+ });
2047
+ }
2048
+ return rows;
2049
+ };
2050
+ const discoverPaymentWebhooks = (project, lunoraDirectory) => {
2051
+ const rows = [];
2052
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
2053
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
2054
+ rows.push(...paymentWebhooksInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
2055
+ }
2056
+ return rows;
2057
+ };
2058
+
2059
+ const HANDLER_FACTORIES = /* @__PURE__ */ new Set(["defineQueue", "defineWorkflow"]);
2060
+ const DISPATCH_METHODS = /* @__PURE__ */ new Set(["run", "runAction", "runMutation", "runQuery"]);
2061
+ const REFERENCE_ROOTS = /* @__PURE__ */ new Set(["api", "internal"]);
2062
+ const calleeName = (call) => {
2063
+ const expression = call.getExpression();
2064
+ return Node.isIdentifier(expression) ? expression.getText() : void 0;
2065
+ };
2066
+ const handlerFunctionOf = (call) => {
2067
+ const config = call.getArguments()[0];
2068
+ if (!config || !Node.isObjectLiteralExpression(config)) {
2069
+ return void 0;
2070
+ }
2071
+ const property = config.getProperty("handler");
2072
+ if (property === void 0 || !Node.isPropertyAssignment(property)) {
2073
+ return void 0;
2074
+ }
2075
+ const initializer = property.getInitializer();
2076
+ if (initializer && (Node.isArrowFunction(initializer) || Node.isFunctionExpression(initializer))) {
2077
+ return initializer;
2078
+ }
2079
+ return void 0;
2080
+ };
2081
+ const parameterName = (handler, index) => {
2082
+ const nameNode = handler.getParameters()[index]?.getNameNode();
2083
+ return nameNode && Node.isIdentifier(nameNode) ? nameNode.getText() : void 0;
2084
+ };
2085
+ const isUnderPath = (text, prefix) => text === prefix || text.startsWith(`${prefix}.`);
2086
+ const propertyAccessesIn = (node) => {
2087
+ const accesses = node.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression);
2088
+ if (Node.isPropertyAccessExpression(node)) {
2089
+ accesses.push(node);
2090
+ }
2091
+ return accesses;
2092
+ };
2093
+ const isReferenceIdentifier = (identifier) => {
2094
+ const parent = identifier.getParent();
2095
+ if (Node.isPropertyAccessExpression(parent) && parent.getNameNode() === identifier) {
2096
+ return false;
2097
+ }
2098
+ if (Node.isPropertyAssignment(parent) && parent.getNameNode() === identifier) {
2099
+ return false;
2100
+ }
2101
+ return !(Node.isBindingElement(parent) && parent.getNameNode() === identifier);
2102
+ };
2103
+ const identifiersIn = (node) => {
2104
+ const identifiers = node.getDescendantsOfKind(SyntaxKind.Identifier).filter((identifier) => isReferenceIdentifier(identifier));
2105
+ if (Node.isIdentifier(node) && isReferenceIdentifier(node)) {
2106
+ identifiers.push(node);
2107
+ }
2108
+ return identifiers;
2109
+ };
2110
+ const declaredNames = (declaration) => {
2111
+ if (!Node.isVariableDeclaration(declaration)) {
2112
+ return [];
2113
+ }
2114
+ const nameNode = declaration.getNameNode();
2115
+ if (Node.isIdentifier(nameNode)) {
2116
+ return [nameNode.getText()];
2117
+ }
2118
+ return nameNode.getDescendantsOfKind(SyntaxKind.BindingElement).map((element) => element.getName());
2119
+ };
2120
+ const queuePayloadPrefixes = (handler) => {
2121
+ const batchParameter = parameterName(handler, 1);
2122
+ if (batchParameter === void 0) {
2123
+ return [];
2124
+ }
2125
+ const prefixes = [`${batchParameter}.messages`];
2126
+ for (const loop of handler.getDescendantsOfKind(SyntaxKind.ForOfStatement)) {
2127
+ if (loop.getExpression().getText() !== `${batchParameter}.messages`) {
2128
+ continue;
2129
+ }
2130
+ const declarations = loop.getInitializer();
2131
+ const binding = Node.isVariableDeclarationList(declarations) ? declarations.getDeclarations()[0]?.getNameNode() : void 0;
2132
+ if (binding && Node.isIdentifier(binding)) {
2133
+ prefixes.push(`${binding.getText()}.body`);
2134
+ }
2135
+ }
2136
+ return prefixes;
2137
+ };
2138
+ const collectPayload = (handler, kind, contextParameter) => {
2139
+ const prefixes = kind === "workflow" ? [`${contextParameter}.params`] : queuePayloadPrefixes(handler);
2140
+ const names = /* @__PURE__ */ new Set();
2141
+ const referencesPayload = (node) => {
2142
+ if (propertyAccessesIn(node).some((access) => prefixes.some((prefix) => isUnderPath(access.getText(), prefix)))) {
2143
+ return true;
2144
+ }
2145
+ return identifiersIn(node).some((identifier) => names.has(identifier.getText()));
2146
+ };
2147
+ for (const declaration of handler.getDescendantsOfKind(SyntaxKind.VariableDeclaration)) {
2148
+ const initializer = declaration.getInitializer();
2149
+ if (initializer && referencesPayload(initializer)) {
2150
+ for (const name of declaredNames(declaration)) {
2151
+ names.add(name);
2152
+ }
2153
+ }
2154
+ }
2155
+ return { names, prefixes };
2156
+ };
2157
+ const argumentsReferencePayload = (argsNode, payload) => {
2158
+ if (propertyAccessesIn(argsNode).some((access) => payload.prefixes.some((prefix) => isUnderPath(access.getText(), prefix)))) {
2159
+ return true;
2160
+ }
2161
+ return identifiersIn(argsNode).some((identifier) => payload.names.has(identifier.getText()));
2162
+ };
2163
+ const resolveTarget = (node) => {
2164
+ if (node === void 0 || !Node.isPropertyAccessExpression(node)) {
2165
+ return void 0;
2166
+ }
2167
+ const segments = [];
2168
+ let current = node;
2169
+ while (Node.isPropertyAccessExpression(current)) {
2170
+ segments.unshift(current.getName());
2171
+ current = current.getExpression();
2172
+ }
2173
+ const exportName = segments.at(-1);
2174
+ if (exportName === void 0 || !Node.isIdentifier(current) || !REFERENCE_ROOTS.has(current.getText()) || segments.length < 2) {
2175
+ return void 0;
2176
+ }
2177
+ return { exportName, file: segments.slice(0, -1).join("/") };
2178
+ };
2179
+ const dispatchesInHandler = (handler, kind, relativePath) => {
2180
+ const contextParameter = parameterName(handler, 0);
2181
+ if (contextParameter === void 0) {
2182
+ return [];
2183
+ }
2184
+ const payload = collectPayload(handler, kind, contextParameter);
2185
+ const found = [];
2186
+ for (const call of handler.getDescendantsOfKind(SyntaxKind.CallExpression)) {
2187
+ const receiver = call.getExpression();
2188
+ if (!Node.isPropertyAccessExpression(receiver) || !DISPATCH_METHODS.has(receiver.getName()) || receiver.getExpression().getText() !== contextParameter) {
2189
+ continue;
2190
+ }
2191
+ const argumentsObject = call.getArguments()[1];
2192
+ if (!argumentsObject || !argumentsReferencePayload(argumentsObject, payload)) {
2193
+ continue;
2194
+ }
2195
+ const target = resolveTarget(call.getArguments()[0]);
2196
+ if (target === void 0) {
2197
+ continue;
2198
+ }
2199
+ found.push({
2200
+ dispatchKind: kind,
2201
+ file: relativePath,
2202
+ handlerExport: enclosingExportName$1(call),
2203
+ line: call.getStartLineNumber(),
2204
+ targetExport: target.exportName,
2205
+ targetFile: target.file
2206
+ });
2207
+ }
2208
+ return found;
2209
+ };
2210
+ const dispatchesInSourceFile = (sourceFile, relativePath) => {
2211
+ const found = [];
2212
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
2213
+ const name = calleeName(call);
2214
+ if (name === void 0 || !HANDLER_FACTORIES.has(name)) {
2215
+ continue;
2216
+ }
2217
+ const handler = handlerFunctionOf(call);
2218
+ if (handler === void 0) {
2219
+ continue;
2220
+ }
2221
+ found.push(...dispatchesInHandler(handler, name === "defineQueue" ? "queue" : "workflow", relativePath));
2222
+ }
2223
+ return found;
2224
+ };
2225
+ const discoverPrivilegedDispatches = (project, lunoraDirectory) => {
2226
+ const rows = [];
2227
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
2228
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
2229
+ rows.push(...dispatchesInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
2230
+ }
2231
+ return rows;
2232
+ };
2233
+
2234
+ const MIDDLEWARE_FLAGS = {
2235
+ dbRateLimit: "usesRateLimit",
2236
+ mask: "usesMask",
2237
+ rateLimit: "usesRateLimit",
2238
+ rls: "usesRls",
2239
+ verifyTurnstile: "usesCaptcha",
2240
+ verifyTurnstileMiddleware: "usesCaptcha"
2241
+ };
2242
+ const USER_TABLE_RE = /account|credential|member|passkey|session|user/iu;
2243
+ const calleeNameOf = (call) => {
2244
+ const callee = call.getExpression();
2245
+ if (Node.isIdentifier(callee)) {
2246
+ return callee.getText();
2247
+ }
2248
+ if (Node.isPropertyAccessExpression(callee)) {
2249
+ return callee.getName();
2250
+ }
2251
+ return void 0;
2252
+ };
2253
+ const protectPublicFlags = (call) => {
2254
+ const argument = call.getArguments()[0];
2255
+ if (!argument || !Node.isObjectLiteralExpression(argument)) {
2256
+ return { usesCaptcha: false, usesRateLimit: false };
2257
+ }
2258
+ return { usesCaptcha: Boolean(argument.getProperty("captcha")), usesRateLimit: Boolean(argument.getProperty("rateLimit")) };
2259
+ };
2260
+ const resolveUseArgumentCall = (argument) => {
2261
+ if (Node.isCallExpression(argument)) {
2262
+ return argument;
2263
+ }
2264
+ if (!Node.isIdentifier(argument)) {
2265
+ return void 0;
2266
+ }
2267
+ const declaration = argument.getSourceFile().getVariableDeclaration(argument.getText());
2268
+ const initializer = declaration?.getInitializer();
2269
+ return initializer && Node.isCallExpression(initializer) ? initializer : void 0;
2270
+ };
2271
+ const NO_PROTECTIONS = { usesCaptcha: false, usesMask: false, usesRateLimit: false, usesRls: false };
2272
+ const useStepProtections = (useArgument) => {
2273
+ const argument = resolveUseArgumentCall(useArgument);
2274
+ const name = argument ? calleeNameOf(argument) : void 0;
2275
+ if (argument && name === "protectPublic") {
2276
+ const bundle = protectPublicFlags(argument);
2277
+ return { ...NO_PROTECTIONS, usesCaptcha: bundle.usesCaptcha, usesRateLimit: bundle.usesRateLimit };
2278
+ }
2279
+ if (name !== void 0 && name in MIDDLEWARE_FLAGS) {
2280
+ return { ...NO_PROTECTIONS, [MIDDLEWARE_FLAGS[name]]: true };
2281
+ }
2282
+ return NO_PROTECTIONS;
2283
+ };
2284
+ const protectionsInChain = (receiver) => {
2285
+ const protections = { ...NO_PROTECTIONS };
2286
+ let node = receiver;
2287
+ while (Node.isCallExpression(node)) {
2288
+ const chainCallee = node.getExpression();
2289
+ if (!Node.isPropertyAccessExpression(chainCallee)) {
2290
+ break;
2291
+ }
2292
+ const useArgument = chainCallee.getName() === "use" ? node.getArguments()[0] : void 0;
2293
+ if (useArgument) {
2294
+ const step = useStepProtections(useArgument);
2295
+ protections.usesCaptcha ||= step.usesCaptcha;
2296
+ protections.usesMask ||= step.usesMask;
2297
+ protections.usesRateLimit ||= step.usesRateLimit;
2298
+ protections.usesRls ||= step.usesRls;
2299
+ }
2300
+ node = chainCallee.getExpression();
2301
+ }
2302
+ return protections;
2303
+ };
2304
+ const isUserTableInsert = (call) => {
2305
+ const callee = call.getExpression();
2306
+ if (!Node.isPropertyAccessExpression(callee) || callee.getName() !== "insert") {
2307
+ return false;
2308
+ }
2309
+ const receiver = callee.getExpression();
2310
+ const onDatabase = Node.isPropertyAccessExpression(receiver) ? receiver.getName() === "db" : Node.isIdentifier(receiver) && receiver.getText() === "db";
2311
+ if (!onDatabase) {
2312
+ return false;
2313
+ }
2314
+ const tableArgument = call.getArguments()[0];
2315
+ return Boolean(tableArgument && Node.isStringLiteral(tableArgument) && USER_TABLE_RE.test(tableArgument.getLiteralText()));
2316
+ };
2317
+ const FANOUT_METHODS = /* @__PURE__ */ new Set(["create", "runAfter", "runAt", "send", "sendBatch"]);
2318
+ const FANOUT_SURFACES = /* @__PURE__ */ new Set(["queues", "scheduler", "workflows"]);
2319
+ const isFanOutCall = (call) => {
2320
+ const callee = call.getExpression();
2321
+ if (!Node.isPropertyAccessExpression(callee) || !FANOUT_METHODS.has(callee.getName())) {
2322
+ return false;
2323
+ }
2324
+ let node = callee.getExpression();
2325
+ while (Node.isCallExpression(node) || Node.isElementAccessExpression(node) || Node.isPropertyAccessExpression(node)) {
2326
+ if (Node.isPropertyAccessExpression(node) && FANOUT_SURFACES.has(node.getName())) {
2327
+ const receiver = node.getExpression();
2328
+ if (Node.isIdentifier(receiver) && receiver.getText() === "ctx") {
2329
+ return true;
2330
+ }
2331
+ }
2332
+ node = node.getExpression();
2333
+ }
2334
+ return false;
2335
+ };
2336
+ const isUnsafeInsert = (call) => {
2337
+ const callee = call.getExpression();
2338
+ if (!Node.isPropertyAccessExpression(callee) || callee.getName() !== "insertManyUnsafe") {
2339
+ return false;
2340
+ }
2341
+ const receiver = callee.getExpression();
2342
+ return Node.isPropertyAccessExpression(receiver) ? receiver.getName() === "db" : Node.isIdentifier(receiver) && receiver.getText() === "db";
2343
+ };
2344
+ const AI_GENERATION_CALLEES = /* @__PURE__ */ new Set(["generateObject", "generateText", "streamObject", "streamText"]);
2345
+ const isUnboundedAiGeneration = (call) => {
2346
+ const name = calleeNameOf(call);
2347
+ if (name === void 0 || !AI_GENERATION_CALLEES.has(name)) {
2348
+ return false;
2349
+ }
2350
+ const argument = call.getArguments()[0];
2351
+ if (!argument || !Node.isObjectLiteralExpression(argument)) {
2352
+ return false;
2353
+ }
2354
+ if (argument.getProperties().some((property) => Node.isSpreadAssignment(property))) {
2355
+ return false;
2356
+ }
2357
+ return !argument.getProperty("maxOutputTokens");
2358
+ };
2359
+ const referencesMail = (declaration) => declaration.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression).some((access) => {
2360
+ const name = access.getName();
2361
+ if (name !== "mail" && name !== "email") {
2362
+ return false;
2363
+ }
2364
+ const receiver = access.getExpression();
2365
+ return Node.isIdentifier(receiver) && receiver.getText() === "ctx";
2366
+ });
2367
+ const behaviourOf = (declaration) => {
2368
+ let fanOut = false;
2369
+ let unboundedAiGeneration = false;
2370
+ let usesInsertManyUnsafe = false;
2371
+ let writesUserTable = false;
2372
+ for (const call of declaration.getDescendantsOfKind(SyntaxKind.CallExpression)) {
2373
+ if (isUserTableInsert(call)) {
2374
+ writesUserTable = true;
2375
+ }
2376
+ if (isFanOutCall(call)) {
2377
+ fanOut = true;
2378
+ }
2379
+ if (isUnsafeInsert(call)) {
2380
+ usesInsertManyUnsafe = true;
2381
+ }
2382
+ if (isUnboundedAiGeneration(call)) {
2383
+ unboundedAiGeneration = true;
2384
+ }
2385
+ if (writesUserTable && fanOut && usesInsertManyUnsafe && unboundedAiGeneration) {
2386
+ break;
2387
+ }
2388
+ }
2389
+ return { callsMail: referencesMail(declaration), fanOut, unboundedAiGeneration, usesInsertManyUnsafe, writesUserTable };
2390
+ };
2391
+ const middlewareIrFromDeclaration = (declaration, relativePath) => {
2392
+ const initializer = declaration.getInitializer();
2393
+ if (!initializer || !Node.isCallExpression(initializer)) {
2394
+ return void 0;
2395
+ }
2396
+ const classified = classifyProcedureCall(initializer);
2397
+ if (!classified || classified.kind !== "query" && classified.kind !== "mutation" && classified.kind !== "action") {
2398
+ return void 0;
2399
+ }
2400
+ const protections = classified.receiver ? protectionsInChain(classified.receiver) : { usesCaptcha: false, usesMask: false, usesRateLimit: false, usesRls: false };
2401
+ const { callsMail, fanOut, unboundedAiGeneration, usesInsertManyUnsafe, writesUserTable } = behaviourOf(declaration);
2402
+ return {
2403
+ callsMail,
2404
+ exportName: declaration.getName(),
2405
+ fanOut,
2406
+ file: relativePath,
2407
+ kind: classified.kind,
2408
+ unboundedAiGeneration,
2409
+ usesCaptcha: protections.usesCaptcha,
2410
+ usesInsertManyUnsafe,
2411
+ usesMask: protections.usesMask,
2412
+ usesRateLimit: protections.usesRateLimit,
2413
+ usesRls: protections.usesRls,
2414
+ visibility: classified.visibility,
2415
+ writesUserTable
2416
+ };
2417
+ };
2418
+ const middlewareInSourceFile = (sourceFile, relativePath) => {
2419
+ const found = [];
2420
+ for (const statement of sourceFile.getVariableStatements()) {
2421
+ if (!statement.isExported()) {
2422
+ continue;
2423
+ }
2424
+ for (const declaration of statement.getDeclarations()) {
2425
+ const ir = middlewareIrFromDeclaration(declaration, relativePath);
2426
+ if (ir) {
2427
+ found.push(ir);
2428
+ }
2429
+ }
2430
+ }
2431
+ return found;
2432
+ };
2433
+ const discoverProcedureMiddleware = (project, lunoraDirectory) => {
2434
+ const procedures = [];
2435
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
2436
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
2437
+ procedures.push(...middlewareInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
2438
+ }
2439
+ return procedures;
2440
+ };
2441
+
2442
+ const RATELIMIT_CALLEES = /* @__PURE__ */ new Set(["dbRateLimit", "rateLimit"]);
2443
+ const keyInitializer = (options) => {
2444
+ if (!Node.isObjectLiteralExpression(options)) {
2445
+ return void 0;
2446
+ }
2447
+ const property = options.getProperty("key");
2448
+ return property && Node.isPropertyAssignment(property) ? property.getInitializer() : void 0;
2449
+ };
2450
+ const limitNameOf = (call) => {
2451
+ const argument = call.getArguments()[1];
2452
+ return argument && Node.isStringLiteral(argument) ? argument.getLiteralValue() : "";
2453
+ };
2454
+ const selectorBody = (key) => Node.isArrowFunction(key) ? key.getBody() : key;
2455
+ const ratelimitKeySelectorInCall = (call, relativePath) => {
2456
+ const callee = calleeName$2(call.getExpression());
2457
+ if (callee === void 0 || !RATELIMIT_CALLEES.has(callee)) {
2458
+ return void 0;
2459
+ }
2460
+ const options = call.getArguments()[2];
2461
+ if (!options) {
2462
+ return void 0;
2463
+ }
2464
+ const key = keyInitializer(options);
2465
+ if (!key) {
2466
+ return void 0;
2467
+ }
2468
+ const body = selectorBody(key);
2469
+ if (!isArgumentDerived(body) || isScopedByContext(body)) {
2470
+ return void 0;
2471
+ }
2472
+ return { callee, exportName: enclosingExportName$1(call), file: relativePath, limitName: limitNameOf(call), line: call.getStartLineNumber() };
2473
+ };
2474
+ const ratelimitKeySelectorsInSourceFile = (sourceFile, relativePath) => {
2475
+ const found = [];
2476
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
2477
+ const selector = ratelimitKeySelectorInCall(call, relativePath);
2478
+ if (selector) {
2479
+ found.push(selector);
2480
+ }
2481
+ }
2482
+ return found;
2483
+ };
2484
+ const discoverRatelimitKeySelectors = (project, lunoraDirectory) => {
2485
+ const selectors = [];
2486
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
2487
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
2488
+ selectors.push(...ratelimitKeySelectorsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
2489
+ }
2490
+ return selectors;
2491
+ };
2492
+
2493
+ const ROW_READ_METHODS = /* @__PURE__ */ new Set(["findFirst", "findFirstOrThrow", "findMany", "get"]);
2494
+ const directRowReadTable = (call) => {
2495
+ const callee = call.getExpression();
2496
+ if (!Node.isPropertyAccessExpression(callee) || !ROW_READ_METHODS.has(callee.getName())) {
2497
+ return void 0;
2498
+ }
2499
+ const receiver = callee.getExpression();
2500
+ if (isDatabaseAccessor(receiver)) {
2501
+ const first = call.getArguments()[0];
2502
+ return first && Node.isStringLiteral(first) ? first.getLiteralText() : "";
2503
+ }
2504
+ if (Node.isPropertyAccessExpression(receiver) && isDatabaseAccessor(receiver.getExpression())) {
2505
+ return receiver.getName();
2506
+ }
2507
+ return void 0;
2508
+ };
2509
+ const fluentReaderTable = (call) => {
2510
+ let node = call;
2511
+ while (Node.isCallExpression(node)) {
2512
+ const callee = node.getExpression();
2513
+ if (!Node.isPropertyAccessExpression(callee)) {
2514
+ return void 0;
2515
+ }
2516
+ if (callee.getName() === "query" && isDatabaseAccessor(callee.getExpression())) {
2517
+ const first = node.getArguments()[0];
2518
+ return first && Node.isStringLiteral(first) ? first.getLiteralText() : "";
2519
+ }
2520
+ node = callee.getExpression();
2521
+ }
2522
+ return void 0;
2523
+ };
2524
+ const unwrapExpression = (node) => {
2525
+ let current = node;
2526
+ while (Node.isAwaitExpression(current) || Node.isParenthesizedExpression(current) || Node.isNonNullExpression(current) || Node.isAsExpression(current)) {
2527
+ current = current.getExpression();
2528
+ }
2529
+ return current;
2530
+ };
2531
+ const rawRowReadTable = (expression, hopped = false) => {
2532
+ const node = unwrapExpression(expression);
2533
+ if (Node.isIdentifier(node)) {
2534
+ if (hopped) {
2535
+ return void 0;
2536
+ }
2537
+ const initializer = singleHopInitializer(node);
2538
+ return initializer === void 0 ? void 0 : rawRowReadTable(initializer, true);
2539
+ }
2540
+ if (!Node.isCallExpression(node)) {
2541
+ return void 0;
2542
+ }
2543
+ return directRowReadTable(node) ?? fluentReaderTable(node);
2544
+ };
2545
+ const handlerReturnExpressions = (handler) => {
2546
+ const body = handler.getBody();
2547
+ if (!Node.isBlock(body)) {
2548
+ return [body];
2549
+ }
2550
+ const expressions = [];
2551
+ for (const statement of handler.getDescendantsOfKind(SyntaxKind.ReturnStatement)) {
2552
+ const enclosing = statement.getFirstAncestor(
2553
+ (ancestor) => Node.isArrowFunction(ancestor) || Node.isFunctionExpression(ancestor) || Node.isFunctionDeclaration(ancestor)
2554
+ );
2555
+ const expression = statement.getExpression();
2556
+ if (enclosing === handler && expression !== void 0) {
2557
+ expressions.push(expression);
2558
+ }
2559
+ }
2560
+ return expressions;
2561
+ };
2562
+ const rawRowReturnsInDeclaration = (declaration, relativePath) => {
2563
+ if (!Node.isVariableDeclaration(declaration)) {
2564
+ return [];
2565
+ }
2566
+ const initializer = declaration.getInitializer();
2567
+ if (initializer === void 0 || !Node.isCallExpression(initializer)) {
2568
+ return [];
2569
+ }
2570
+ const classified = classifyProcedureCall(initializer);
2571
+ if (classified?.kind !== "query") {
2572
+ return [];
2573
+ }
2574
+ const handler = procedureHandler(initializer);
2575
+ if (handler === void 0) {
2576
+ return [];
2577
+ }
2578
+ const usesOutput = classified.receiver !== void 0 && chainHasStep(classified.receiver, "output");
2579
+ const usesMask = classified.receiver !== void 0 && chainUsesWrappedCall(classified.receiver, "use", "mask");
2580
+ const seen = /* @__PURE__ */ new Set();
2581
+ const rows = [];
2582
+ for (const expression of handlerReturnExpressions(handler)) {
2583
+ const table = rawRowReadTable(expression);
2584
+ if (table === void 0 || seen.has(table)) {
2585
+ continue;
2586
+ }
2587
+ seen.add(table);
2588
+ rows.push({
2589
+ exportName: declaration.getName(),
2590
+ file: relativePath,
2591
+ line: expression.getStartLineNumber(),
2592
+ table,
2593
+ usesMask,
2594
+ usesOutput,
2595
+ visibility: classified.visibility
2596
+ });
2597
+ }
2598
+ return rows;
2599
+ };
2600
+ const discoverRawRowReturns = (project, lunoraDirectory) => {
2601
+ const rows = [];
2602
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
2603
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
2604
+ const relativePath = lunoraRelativePath(lunoraDirectory, filePath);
2605
+ for (const statement of sourceFile.getVariableStatements()) {
2606
+ if (!statement.isExported()) {
2607
+ continue;
2608
+ }
2609
+ for (const declaration of statement.getDeclarations()) {
2610
+ rows.push(...rawRowReturnsInDeclaration(declaration, relativePath));
2611
+ }
2612
+ }
2613
+ }
2614
+ return rows;
2615
+ };
2616
+
2617
+ const READ_METHODS$1 = /* @__PURE__ */ new Set(["findFirst", "findFirstOrThrow", "findMany"]);
2618
+ const readTargetOf$1 = (call) => {
2619
+ const callee = call.getExpression();
2620
+ if (!Node.isPropertyAccessExpression(callee) || !READ_METHODS$1.has(callee.getName())) {
2621
+ return void 0;
2622
+ }
2623
+ const receiver = callee.getExpression();
2624
+ if (Node.isPropertyAccessExpression(receiver) && receiver.getName() === "db" || Node.isIdentifier(receiver) && receiver.getText() === "db") {
2625
+ const first = call.getArguments()[0];
2626
+ return { options: call.getArguments()[1], table: first && Node.isStringLiteral(first) ? first.getLiteralText() : "" };
2627
+ }
2628
+ if (Node.isPropertyAccessExpression(receiver)) {
2629
+ const inner = receiver.getExpression();
2630
+ const onDatabase = Node.isPropertyAccessExpression(inner) && inner.getName() === "db" || Node.isIdentifier(inner) && inner.getText() === "db";
2631
+ if (onDatabase) {
2632
+ return { options: call.getArguments()[0], table: receiver.getName() };
2633
+ }
2634
+ }
2635
+ return void 0;
2636
+ };
2637
+ const relationNamesOf = (withValue) => {
2638
+ if (!withValue || !Node.isObjectLiteralExpression(withValue)) {
2639
+ return [];
2640
+ }
2641
+ const names = [];
2642
+ for (const member of withValue.getProperties()) {
2643
+ if (Node.isPropertyAssignment(member) || Node.isShorthandPropertyAssignment(member) || Node.isMethodDeclaration(member) || Node.isGetAccessorDeclaration(member)) {
2644
+ names.push(member.getName());
2645
+ }
2646
+ }
2647
+ return names;
2648
+ };
2649
+ const optionValue$1 = (options, key) => {
2650
+ if (!options || !Node.isObjectLiteralExpression(options)) {
2651
+ return void 0;
2652
+ }
2653
+ const property = options.getProperty(key);
2654
+ return property && Node.isPropertyAssignment(property) ? property.getInitializer() : void 0;
2655
+ };
2656
+ const relationLoadsInDeclaration = (declaration, relativePath) => {
2657
+ if (!Node.isVariableDeclaration(declaration)) {
2658
+ return [];
2659
+ }
2660
+ const initializer = declaration.getInitializer();
2661
+ const classified = initializer && Node.isCallExpression(initializer) ? classifyProcedureCall(initializer) : void 0;
2662
+ if (!classified) {
2663
+ return [];
2664
+ }
2665
+ const rows = [];
2666
+ for (const call of declaration.getDescendantsOfKind(SyntaxKind.CallExpression)) {
2667
+ const target = readTargetOf$1(call);
2668
+ if (target === void 0) {
2669
+ continue;
2670
+ }
2671
+ const relations = relationNamesOf(optionValue$1(target.options, "with"));
2672
+ if (relations.length === 0) {
2673
+ continue;
2674
+ }
2675
+ rows.push({
2676
+ exportName: declaration.getName(),
2677
+ file: relativePath,
2678
+ line: call.getStartLineNumber(),
2679
+ parentTable: target.table,
2680
+ relations,
2681
+ visibility: classified.visibility
2682
+ });
2683
+ }
2684
+ return rows;
2685
+ };
2686
+ const discoverRelationLoads = (project, lunoraDirectory) => {
2687
+ const rows = [];
2688
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
2689
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
2690
+ const relativePath = lunoraRelativePath(lunoraDirectory, filePath);
2691
+ for (const statement of sourceFile.getVariableStatements()) {
2692
+ if (!statement.isExported()) {
2693
+ continue;
2694
+ }
2695
+ for (const declaration of statement.getDeclarations()) {
2696
+ rows.push(...relationLoadsInDeclaration(declaration, relativePath));
2697
+ }
2698
+ }
2699
+ }
2700
+ return rows;
2701
+ };
2702
+
2703
+ const literalValueOf = (node) => {
2704
+ if (Node.isStringLiteral(node) || Node.isNoSubstitutionTemplateLiteral(node)) {
2705
+ return node.getLiteralText();
2706
+ }
2707
+ if (Node.isBinaryExpression(node) && node.getOperatorToken().getKind() === SyntaxKind.PlusToken) {
2708
+ const left = literalValueOf(node.getLeft());
2709
+ const right = literalValueOf(node.getRight());
2710
+ return left !== void 0 && right !== void 0 ? left + right : void 0;
2711
+ }
2712
+ return void 0;
2713
+ };
2714
+ const isFoldedConcatenationOperand = (node) => {
2715
+ let root = node;
2716
+ let parent = root.getParent();
2717
+ while (parent !== void 0 && Node.isBinaryExpression(parent) && parent.getOperatorToken().getKind() === SyntaxKind.PlusToken) {
2718
+ root = parent;
2719
+ parent = root.getParent();
2720
+ }
2721
+ return root !== node && literalValueOf(root) !== void 0;
2722
+ };
2723
+ const secretsInSourceFile = (sourceFile, relativePath) => {
2724
+ const found = [];
2725
+ const nodes = [
2726
+ // Top-level `+` concatenations are folded; their string-literal operands are
2727
+ // skipped below so a secret split across literals is reported once, at the root.
2728
+ ...sourceFile.getDescendantsOfKind(SyntaxKind.BinaryExpression),
2729
+ ...sourceFile.getDescendantsOfKind(SyntaxKind.StringLiteral),
2730
+ ...sourceFile.getDescendantsOfKind(SyntaxKind.NoSubstitutionTemplateLiteral)
2731
+ ];
2732
+ const seen = /* @__PURE__ */ new Set();
2733
+ for (const node of nodes) {
2734
+ if (isFoldedConcatenationOperand(node)) {
2735
+ continue;
2736
+ }
2737
+ const value = literalValueOf(node);
2738
+ if (value === void 0) {
2739
+ continue;
2740
+ }
2741
+ const kind = secretKindOf(value);
2742
+ if (kind === void 0) {
2743
+ continue;
2744
+ }
2745
+ const line = node.getStartLineNumber();
2746
+ const dedupeKey = `${String(line)}:${kind}`;
2747
+ if (seen.has(dedupeKey)) {
2748
+ continue;
2749
+ }
2750
+ seen.add(dedupeKey);
2751
+ found.push({ file: relativePath, kind, line, preview: redact(value) });
2752
+ }
2753
+ return found;
2754
+ };
2755
+ const discoverSecrets = (project, lunoraDirectory) => {
2756
+ const secrets = [];
2757
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
2758
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
2759
+ secrets.push(...secretsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
2760
+ }
2761
+ return secrets;
2762
+ };
2763
+
2764
+ const READ_METHODS = /* @__PURE__ */ new Set(["findFirst", "findFirstOrThrow", "findMany"]);
2765
+ const readTargetOf = (call) => {
2766
+ const callee = call.getExpression();
2767
+ if (!Node.isPropertyAccessExpression(callee) || !READ_METHODS.has(callee.getName())) {
2768
+ return void 0;
2769
+ }
2770
+ const receiver = callee.getExpression();
2771
+ if (Node.isPropertyAccessExpression(receiver) && receiver.getName() === "db" || Node.isIdentifier(receiver) && receiver.getText() === "db") {
2772
+ const first = call.getArguments()[0];
2773
+ return { options: call.getArguments()[1], table: first && Node.isStringLiteral(first) ? first.getLiteralText() : "" };
2774
+ }
2775
+ if (Node.isPropertyAccessExpression(receiver)) {
2776
+ const inner = receiver.getExpression();
2777
+ const onDatabase = Node.isPropertyAccessExpression(inner) && inner.getName() === "db" || Node.isIdentifier(inner) && inner.getText() === "db";
2778
+ if (onDatabase) {
2779
+ return { options: call.getArguments()[0], table: receiver.getName() };
2780
+ }
2781
+ }
2782
+ return void 0;
2783
+ };
2784
+ const optionValue = (options, key) => {
2785
+ if (!options || !Node.isObjectLiteralExpression(options)) {
2786
+ return void 0;
2787
+ }
2788
+ const property = options.getProperty(key);
2789
+ return property && Node.isPropertyAssignment(property) ? property.getInitializer() : void 0;
2790
+ };
2791
+ const softDeleteReadsInDeclaration = (declaration, relativePath) => {
2792
+ if (!Node.isVariableDeclaration(declaration)) {
2793
+ return [];
2794
+ }
2795
+ const initializer = declaration.getInitializer();
2796
+ const classified = initializer && Node.isCallExpression(initializer) ? classifyProcedureCall(initializer) : void 0;
2797
+ if (!classified) {
2798
+ return [];
2799
+ }
2800
+ const rows = [];
2801
+ for (const call of declaration.getDescendantsOfKind(SyntaxKind.CallExpression)) {
2802
+ const target = readTargetOf(call);
2803
+ if (target === void 0) {
2804
+ continue;
2805
+ }
2806
+ const value = optionValue(target.options, "includeDeleted");
2807
+ if (value === void 0) {
2808
+ continue;
2809
+ }
2810
+ const hardcodedTrue = Node.isTrueLiteral(value);
2811
+ const fromArgs = !hardcodedTrue && isArgumentDerived(value);
2812
+ if (!hardcodedTrue && !fromArgs) {
2813
+ continue;
2814
+ }
2815
+ rows.push({
2816
+ exportName: declaration.getName(),
2817
+ file: relativePath,
2818
+ fromArgs,
2819
+ hardcodedTrue,
2820
+ line: call.getStartLineNumber(),
2821
+ table: target.table,
2822
+ visibility: classified.visibility
2823
+ });
2824
+ }
2825
+ return rows;
2826
+ };
2827
+ const discoverSoftDeleteReads = (project, lunoraDirectory) => {
2828
+ const rows = [];
2829
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
2830
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
2831
+ const relativePath = lunoraRelativePath(lunoraDirectory, filePath);
2832
+ for (const statement of sourceFile.getVariableStatements()) {
2833
+ if (!statement.isExported()) {
2834
+ continue;
2835
+ }
2836
+ for (const declaration of statement.getDeclarations()) {
2837
+ rows.push(...softDeleteReadsInDeclaration(declaration, relativePath));
2838
+ }
2839
+ }
2840
+ }
2841
+ return rows;
2842
+ };
2843
+
2844
+ const SQL_TEXT_METHODS = /* @__PURE__ */ new Set(["query", "unsafe"]);
2845
+ const isContextSqlTextCallee = (node) => {
2846
+ if (!Node.isPropertyAccessExpression(node) || !SQL_TEXT_METHODS.has(node.getName())) {
2847
+ return false;
2848
+ }
2849
+ const sqlAccess = node.getExpression();
2850
+ if (!Node.isPropertyAccessExpression(sqlAccess) || sqlAccess.getName() !== "sql") {
2851
+ return false;
2852
+ }
2853
+ const receiver = sqlAccess.getExpression();
2854
+ return Node.isIdentifier(receiver) && receiver.getText() === "ctx";
2855
+ };
2856
+ const isStringBuildingText = (expression) => Node.isBinaryExpression(expression) || Node.isTemplateExpression(expression);
2857
+ const enclosingExportName = (node) => {
2858
+ const declaration = node.getFirstAncestorByKind(SyntaxKind.VariableDeclaration);
2859
+ return declaration?.getName() ?? "<module>";
2860
+ };
2861
+ const interpolationInCall = (call, relativePath) => {
2862
+ if (!isContextSqlTextCallee(call.getExpression())) {
2863
+ return void 0;
2864
+ }
2865
+ const text = call.getArguments()[0];
2866
+ if (!text || !isStringBuildingText(text)) {
2867
+ return void 0;
2868
+ }
2869
+ return { exportName: enclosingExportName(call), file: relativePath, line: text.getStartLineNumber() };
2870
+ };
2871
+ const interpolationsInSourceFile = (sourceFile, relativePath) => {
2872
+ const found = [];
2873
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
2874
+ const interpolation = interpolationInCall(call, relativePath);
2875
+ if (interpolation) {
2876
+ found.push(interpolation);
2877
+ }
2878
+ }
2879
+ return found;
2880
+ };
2881
+ const discoverSqlInterpolation = (project, lunoraDirectory) => {
2882
+ const interpolations = [];
2883
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
2884
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
2885
+ interpolations.push(...interpolationsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
2886
+ }
2887
+ return interpolations;
2888
+ };
2889
+
2890
+ const KEY_TAKING_METHODS = /* @__PURE__ */ new Set([
2891
+ "createMultipartUpload",
2892
+ "delete",
2893
+ "download",
2894
+ "generateUploadUrl",
2895
+ "get",
2896
+ "getMetadata",
2897
+ "getPresignedUrl",
2898
+ "getSignedUrl",
2899
+ "getUrl",
2900
+ "head",
2901
+ "put",
2902
+ "resumeMultipartUpload",
2903
+ "store",
2904
+ "upload"
2905
+ ]);
2906
+ const discoverStorageKeyAccesses = (project, lunoraDirectory) => discoverArgumentDerivedAccesses(project, lunoraDirectory, {
2907
+ argIndex: 0,
2908
+ matchReceiver: (receiver) => receiver === "ctx.storage" || receiver.startsWith("ctx.storage."),
2909
+ methods: KEY_TAKING_METHODS
2910
+ });
2911
+
2912
+ const OPTIONS_ARGUMENT_INDEX = /* @__PURE__ */ new Map([
2913
+ ["generateUploadUrl", 1],
2914
+ ["getPresignedUrl", 1],
2915
+ ["getSignedUrl", 1],
2916
+ ["store", 2],
2917
+ ["upload", 2]
2918
+ ]);
2919
+ const numericLiteralValue = (node) => node && Node.isNumericLiteral(node) ? Number(node.getText()) : void 0;
2920
+ const readOptionsArgument = (argument) => {
2921
+ if (argument === void 0) {
2922
+ return { analyzable: true, presentKeys: [] };
2923
+ }
2924
+ if (!Node.isObjectLiteralExpression(argument)) {
2925
+ return { analyzable: false, presentKeys: [] };
2926
+ }
2927
+ const presentKeys = [];
2928
+ let expiresInSeconds;
2929
+ let hasSpread = false;
2930
+ for (const property of argument.getProperties()) {
2931
+ if (Node.isSpreadAssignment(property)) {
2932
+ hasSpread = true;
2933
+ continue;
2934
+ }
2935
+ if (Node.isPropertyAssignment(property)) {
2936
+ const name = property.getName();
2937
+ presentKeys.push(name);
2938
+ if (name === "expiresInSeconds") {
2939
+ expiresInSeconds = numericLiteralValue(property.getInitializer());
2940
+ }
2941
+ continue;
2942
+ }
2943
+ if (Node.isShorthandPropertyAssignment(property) || Node.isMethodDeclaration(property)) {
2944
+ presentKeys.push(property.getName());
2945
+ }
2946
+ }
2947
+ return { analyzable: !hasSpread, expiresInSeconds, presentKeys };
2948
+ };
2949
+ const storageUploadMethod = (node) => {
2950
+ if (!Node.isPropertyAccessExpression(node)) {
2951
+ return void 0;
2952
+ }
2953
+ const method = node.getName();
2954
+ const optionsIndex = OPTIONS_ARGUMENT_INDEX.get(method);
2955
+ if (optionsIndex === void 0) {
2956
+ return void 0;
2957
+ }
2958
+ const receiver = node.getExpression().getText();
2959
+ return receiver === "ctx.storage" || receiver.startsWith("ctx.storage.") ? { method, optionsIndex } : void 0;
2960
+ };
2961
+ const storageUploadInCall = (call, relativePath) => {
2962
+ const matched = storageUploadMethod(call.getExpression());
2963
+ if (matched === void 0) {
2964
+ return void 0;
2965
+ }
2966
+ return {
2967
+ exportName: enclosingExportName$1(call),
2968
+ file: relativePath,
2969
+ line: call.getStartLineNumber(),
2970
+ method: matched.method,
2971
+ ...readOptionsArgument(call.getArguments()[matched.optionsIndex])
2972
+ };
2973
+ };
2974
+ const storageUploadsInSourceFile = (sourceFile, relativePath) => {
2975
+ const found = [];
2976
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
2977
+ const row = storageUploadInCall(call, relativePath);
2978
+ if (row) {
2979
+ found.push(row);
2980
+ }
2981
+ }
2982
+ return found;
2983
+ };
2984
+ const discoverStorageUploads = (project, lunoraDirectory) => {
2985
+ const rows = [];
2986
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
2987
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
2988
+ rows.push(...storageUploadsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
2989
+ }
2990
+ return rows;
2991
+ };
2992
+
2993
+ const VECTOR_NAMESPACE_METHODS = /* @__PURE__ */ new Set(["query", "upsert", "upsertMany"]);
2994
+ const vectorsNamespaceMethod = (node) => {
2995
+ if (!Node.isPropertyAccessExpression(node)) {
2996
+ return void 0;
2997
+ }
2998
+ const method = node.getName();
2999
+ if (!VECTOR_NAMESPACE_METHODS.has(method)) {
3000
+ return void 0;
3001
+ }
3002
+ return node.getExpression().getText() === "ctx.vectors" ? method : void 0;
3003
+ };
3004
+ const namespaceInitializer = (input) => {
3005
+ if (!Node.isObjectLiteralExpression(input)) {
3006
+ return void 0;
3007
+ }
3008
+ const property = input.getProperty("namespace");
3009
+ return property && Node.isPropertyAssignment(property) ? property.getInitializer() : void 0;
3010
+ };
3011
+ const vectorNamespaceAccessInCall = (call, relativePath) => {
3012
+ const method = vectorsNamespaceMethod(call.getExpression());
3013
+ if (method === void 0) {
3014
+ return void 0;
3015
+ }
3016
+ const input = call.getArguments()[1];
3017
+ if (!input) {
3018
+ return void 0;
3019
+ }
3020
+ const namespace = namespaceInitializer(input);
3021
+ if (!namespace || !isArgumentDerived(namespace) || isScopedByContext(namespace)) {
3022
+ return void 0;
3023
+ }
3024
+ return { exportName: enclosingExportName$1(call), file: relativePath, line: call.getStartLineNumber(), method };
3025
+ };
3026
+ const vectorNamespaceAccessesInSourceFile = (sourceFile, relativePath) => {
3027
+ const found = [];
3028
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
3029
+ const access = vectorNamespaceAccessInCall(call, relativePath);
3030
+ if (access) {
3031
+ found.push(access);
3032
+ }
3033
+ }
3034
+ return found;
3035
+ };
3036
+ const discoverVectorNamespaceAccesses = (project, lunoraDirectory) => {
3037
+ const accesses = [];
3038
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
3039
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
3040
+ accesses.push(...vectorNamespaceAccessesInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
3041
+ }
3042
+ return accesses;
3043
+ };
3044
+
3045
+ const isWorkflowGetCall = (call) => {
3046
+ const callee = call.getExpression();
3047
+ if (!Node.isPropertyAccessExpression(callee) || callee.getName() !== "get") {
3048
+ return false;
3049
+ }
3050
+ const receiver = callee.getExpression();
3051
+ if (Node.isPropertyAccessExpression(receiver)) {
3052
+ return receiver.getName() === "workflows";
3053
+ }
3054
+ return Node.isIdentifier(receiver) && receiver.getText() === "workflows";
3055
+ };
3056
+ const workflowOf = (call) => {
3057
+ const argument = call.getArguments()[0];
3058
+ return argument && Node.isStringLiteral(argument) ? argument.getLiteralText() : "";
3059
+ };
3060
+ const discoverWorkflowCalls = (project, lunoraDirectory) => {
3061
+ const calls = [];
3062
+ for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
3063
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
3064
+ const relativePath = lunoraRelativePath(lunoraDirectory, filePath);
3065
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
3066
+ if (!isWorkflowGetCall(call)) {
3067
+ continue;
3068
+ }
3069
+ const exportName = enclosingExportName$2(call);
3070
+ if (exportName === "") {
3071
+ continue;
3072
+ }
3073
+ calls.push({ exportName, file: relativePath, line: call.getStartLineNumber(), workflow: workflowOf(call) });
3074
+ }
3075
+ }
3076
+ return calls;
3077
+ };
3078
+
3079
+ const SCHEMA_SNAPSHOT_FILENAME = ".lunora-schema.json";
3080
+ const writeIfChanged = (filePath, content) => {
3081
+ if (existsSync(filePath)) {
3082
+ const existing = readFileSync(filePath, "utf8");
3083
+ if (existing === content) {
3084
+ return;
3085
+ }
3086
+ }
3087
+ writeFileSync(filePath, content, "utf8");
3088
+ };
3089
+ const writeIfPresent = (filePath, content) => {
3090
+ if (content === "") {
3091
+ rmSync(filePath, { force: true });
3092
+ return;
3093
+ }
3094
+ writeIfChanged(filePath, content);
3095
+ };
3096
+ const readProjectVersion = (projectRoot) => {
3097
+ const manifestPath = join(projectRoot, "package.json");
3098
+ if (!existsSync(manifestPath)) {
3099
+ return void 0;
3100
+ }
3101
+ try {
3102
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
3103
+ return typeof manifest["version"] === "string" && manifest["version"] !== "" ? manifest["version"] : void 0;
3104
+ } catch {
3105
+ return void 0;
3106
+ }
3107
+ };
3108
+ const isTimingEnabled = () => {
3109
+ const flag = process.env["LUNORA_CODEGEN_TIMING"];
3110
+ return flag !== void 0 && flag !== "";
3111
+ };
3112
+ const findTsconfig = (startPath) => {
3113
+ let directory = existsSync(startPath) ? startPath : dirname(startPath);
3114
+ while (directory && directory !== dirname(directory)) {
3115
+ const candidate = join(directory, "tsconfig.json");
3116
+ if (existsSync(candidate)) {
3117
+ return candidate;
3118
+ }
3119
+ directory = dirname(directory);
3120
+ }
3121
+ return void 0;
3122
+ };
3123
+ const toPosixPath = (path) => path.replaceAll("\\", "/");
3124
+ const assertNoWorkflowAgentCollision = (workflows, agents) => {
3125
+ const namesByLabel = /* @__PURE__ */ new Map();
3126
+ const bindingsByLabel = /* @__PURE__ */ new Map();
3127
+ const classesByLabel = /* @__PURE__ */ new Map();
3128
+ for (const workflow of workflows) {
3129
+ namesByLabel.set(workflow.name, `workflow "${workflow.exportName}"`);
3130
+ bindingsByLabel.set(workflow.bindingName, `workflow "${workflow.exportName}"`);
3131
+ classesByLabel.set(workflow.className, `workflow "${workflow.exportName}"`);
3132
+ }
3133
+ for (const agent of agents) {
3134
+ const priorName = namesByLabel.get(agent.name);
3135
+ if (priorName !== void 0) {
3136
+ throw new LunoraError(
3137
+ // eslint-disable-next-line no-secrets/no-secrets -- an error code, not a secret
3138
+ "DUPLICATE_WORKFLOW_NAME",
3139
+ `Duplicate deployed name "${agent.name}": produced by both ${priorName} and agent "${agent.exportName}". Workflow and agent names share the same wrangler workflows[] array and must be unique together.`,
3140
+ { status: 500 }
3141
+ );
3142
+ }
3143
+ const priorBinding = bindingsByLabel.get(agent.bindingName);
3144
+ if (priorBinding !== void 0) {
3145
+ throw new LunoraError(
3146
+ // eslint-disable-next-line no-secrets/no-secrets -- an error code, not a secret
3147
+ "DUPLICATE_WORKFLOW_BINDING",
3148
+ `Duplicate binding "${agent.bindingName}": produced by both ${priorBinding} and agent "${agent.exportName}". Workflow and agent bindings share the same wrangler workflows[] array and must be unique together.`,
3149
+ { status: 500 }
3150
+ );
3151
+ }
3152
+ const priorClass = classesByLabel.get(agent.className);
3153
+ if (priorClass !== void 0) {
3154
+ throw new LunoraError(
3155
+ "DUPLICATE_WORKFLOW_CLASS",
3156
+ `Duplicate generated class "${agent.className}": produced by both ${priorClass} and agent "${agent.exportName}". Workflow and agent export names must yield unique generated class names.`,
3157
+ { status: 500 }
3158
+ );
3159
+ }
3160
+ }
3161
+ };
3162
+ const createCodegenProject = (lunoraDirectory) => {
3163
+ const tsconfigPath = findTsconfig(lunoraDirectory);
3164
+ return tsconfigPath ? new Project({ skipAddingFilesFromTsConfig: false, tsConfigFilePath: tsconfigPath, useInMemoryFileSystem: false }) : new Project({ skipAddingFilesFromTsConfig: true, useInMemoryFileSystem: false });
3165
+ };
3166
+ const refreshCodegenProject = (project, lunoraDirectory) => {
3167
+ const diskPaths = listLunoraSourceFiles(lunoraDirectory);
3168
+ const schemaPath = join(lunoraDirectory, "schema.ts");
3169
+ if (existsSync(schemaPath)) {
3170
+ diskPaths.push(schemaPath);
3171
+ }
3172
+ for (const path of diskPaths) {
3173
+ const existing = project.getSourceFile(path);
3174
+ if (existing === void 0) {
3175
+ project.addSourceFileAtPath(path);
3176
+ } else {
3177
+ existing.refreshFromFileSystemSync();
3178
+ }
3179
+ }
3180
+ const onDisk = new Set(diskPaths.map((path) => toPosixPath(path)));
3181
+ const lunoraRoot = toPosixPath(lunoraDirectory);
3182
+ const lunoraPrefix = `${lunoraRoot}/`;
3183
+ for (const sourceFile of project.getSourceFiles()) {
3184
+ const filePath = sourceFile.getFilePath();
3185
+ if ((filePath === lunoraRoot || filePath.startsWith(lunoraPrefix)) && !onDisk.has(filePath)) {
3186
+ project.removeSourceFile(sourceFile);
3187
+ }
3188
+ }
3189
+ };
3190
+ const runCodegen = (options) => {
3191
+ const timingEnabled = isTimingEnabled();
3192
+ const startedAt = timingEnabled ? performance.now() : 0;
3193
+ const lunoraDirectory = join(options.projectRoot, options.lunoraDirectory ?? "lunora");
3194
+ const schemaPath = join(lunoraDirectory, "schema.ts");
3195
+ if (!existsSync(schemaPath)) {
3196
+ throw new LunoraError("INTERNAL", `schema.ts not found at ${schemaPath}`);
3197
+ }
3198
+ const project = options.project ?? createCodegenProject(lunoraDirectory);
3199
+ const schema = discoverSchema(project, schemaPath, options.projectRoot);
3200
+ const functions = discoverFunctions(project, lunoraDirectory);
3201
+ const httpRoutes = discoverHttpRoutes(project, lunoraDirectory);
3202
+ const migrations = discoverMigrations(project, lunoraDirectory);
3203
+ const shapes = discoverShapes(project, lunoraDirectory);
3204
+ const mutators = discoverMutators(project, lunoraDirectory);
3205
+ const identity = discoverIdentity(project, lunoraDirectory);
3206
+ const env = discoverEnv(project, lunoraDirectory);
3207
+ const workflows = discoverWorkflows(project, lunoraDirectory);
3208
+ const queues = discoverQueues(project, lunoraDirectory);
3209
+ const agents = discoverAgents(project, lunoraDirectory);
3210
+ assertNoWorkflowAgentCollision(workflows, agents);
3211
+ const crons = discoverCrons(project, lunoraDirectory, workflows, agents);
3212
+ const containers = discoverContainers(project, lunoraDirectory);
3213
+ const advisories = options.lint === false ? [] : lintSchema({
3214
+ adminRoutes: discoverAdminRoutes(project, lunoraDirectory),
3215
+ aiRawRuns: discoverAiRawRuns(project, lunoraDirectory),
3216
+ aiToolSideEffects: discoverAiToolSideEffects(project, lunoraDirectory),
3217
+ argumentDerivedFetches: discoverArgumentDerivedFetches(project, lunoraDirectory),
3218
+ argumentValidators: discoverArgumentValidators(project, lunoraDirectory),
3219
+ authApiCalls: discoverAuthApiCalls(project, lunoraDirectory),
3220
+ authConfigs: discoverAuthConfig(project, lunoraDirectory),
3221
+ browserUrlAccesses: discoverBrowserUrlAccesses(project, lunoraDirectory),
3222
+ configCalls: discoverConfigCalls(project, lunoraDirectory),
3223
+ containerKeyAccesses: discoverContainerKeyAccesses(project, lunoraDirectory),
3224
+ containerOverrides: discoverContainerOverrides(project, lunoraDirectory),
3225
+ containers,
3226
+ failOpenGuards: discoverFailOpenGuards(project, lunoraDirectory),
3227
+ flagSecurityDefaults: discoverFlagSecurityDefaults(project, lunoraDirectory),
3228
+ httpActionGuards: discoverHttpActionGuards(project, lunoraDirectory),
3229
+ httpHeaderWrites: discoverHttpHeaderWrites(project, lunoraDirectory),
3230
+ identityClaimReads: discoverIdentityClaimReads(project, lunoraDirectory),
3231
+ imageDeliveryUrlAccesses: discoverImageDeliveryUrlAccesses(project, lunoraDirectory),
3232
+ inserts: discoverInserts(project, lunoraDirectory),
3233
+ kvKeyAccesses: discoverKvKeyAccesses(project, lunoraDirectory),
3234
+ mailRecipientAccesses: discoverMailRecipientAccesses(project, lunoraDirectory),
3235
+ maskProcedures: discoverMaskProcedures(project, lunoraDirectory),
3236
+ maskStrategies: discoverMaskStrategies(project, lunoraDirectory),
3237
+ mutatorWrites: discoverMutatorWrites(project, lunoraDirectory),
3238
+ nondeterministicCalls: discoverNondeterministicCalls(project, lunoraDirectory),
3239
+ normalizeIdAuthorizations: discoverNormalizeIdAuthorization(project, lunoraDirectory),
3240
+ ownerFieldWrites: discoverOwnerFieldWrites(project, lunoraDirectory),
3241
+ paymentWebhooks: discoverPaymentWebhooks(project, lunoraDirectory),
3242
+ privilegedDispatches: discoverPrivilegedDispatches(project, lunoraDirectory),
3243
+ procedureProtections: discoverProcedureMiddleware(project, lunoraDirectory),
3244
+ queries: discoverQueries(project, lunoraDirectory),
3245
+ queues,
3246
+ r2sqlCalls: discoverR2sqlCalls(project, lunoraDirectory),
3247
+ ratelimitKeySelectors: discoverRatelimitKeySelectors(project, lunoraDirectory),
3248
+ rawRowReturns: discoverRawRowReturns(project, lunoraDirectory),
3249
+ relationLoads: discoverRelationLoads(project, lunoraDirectory),
3250
+ rlsProcedures: discoverRlsProcedures(project, lunoraDirectory),
3251
+ schema,
3252
+ secretLiterals: discoverSecrets(project, lunoraDirectory),
3253
+ shapes,
3254
+ softDeleteReads: discoverSoftDeleteReads(project, lunoraDirectory),
3255
+ sqlInterpolations: discoverSqlInterpolation(project, lunoraDirectory),
3256
+ storageKeyAccesses: discoverStorageKeyAccesses(project, lunoraDirectory),
3257
+ storageUploads: discoverStorageUploads(project, lunoraDirectory),
3258
+ vectorNamespaceAccesses: discoverVectorNamespaceAccesses(project, lunoraDirectory),
3259
+ workflowCalls: discoverWorkflowCalls(project, lunoraDirectory),
3260
+ workflows,
3261
+ wranglerVariables: options.wranglerVariables
3262
+ });
3263
+ const rlsMetadata = discoverRlsMetadata(project, lunoraDirectory);
3264
+ const maskMetadata = discoverMaskMetadata(project, lunoraDirectory);
3265
+ const storageRulesMetadata = discoverStorageRulesMetadata(project, lunoraDirectory);
3266
+ const featureUsage = discoverFeatureUsage(project, lunoraDirectory);
3267
+ const hasAi = featureUsage.ai;
3268
+ const hasPayments = featureUsage.payments;
3269
+ const hasKv = featureUsage.kv;
3270
+ const hasAccessFacade = featureUsage.access;
3271
+ const hasFlags = existsSync(join(lunoraDirectory, "flags.ts"));
3272
+ const flagKeys = hasFlags ? discoverFlagKeys(project, lunoraDirectory) : [];
3273
+ const hasHyperdrive = featureUsage.hyperdrive;
3274
+ const sandboxUsage = discoverSandboxUsage(project, lunoraDirectory);
3275
+ const usesSandbox = sandboxUsage.usesSandboxBrowser || sandboxUsage.usesSandboxContainer;
3276
+ const hasBrowser = featureUsage.browser || sandboxUsage.usesSandboxBrowser;
3277
+ const hasImages = featureUsage.images;
3278
+ const hasAnalytics = featureUsage.analytics;
3279
+ const hasPipelines = featureUsage.pipelines;
3280
+ const hasR2sql = featureUsage.r2sql;
3281
+ const hasX402 = featureUsage.x402;
3282
+ const dependencies = discoverPackageDependencies(options.projectRoot);
3283
+ const studioFeatures = buildStudioFeatures(featureUsage, {
3284
+ containerCount: containers.length,
3285
+ cronCount: crons.length,
3286
+ dependencies,
3287
+ queueCount: queues.length,
3288
+ storageColumnCount: Object.keys(buildStorageColumns(schema)).length,
3289
+ storageRuleCount: storageRulesMetadata.rules.length,
3290
+ vectorIndexCount: schema.vectorIndexes.length,
3291
+ workflowCount: workflows.length
3292
+ });
3293
+ const useUmbrella = dependencies.has("lunorash");
3294
+ const emitStartedAt = timingEnabled ? performance.now() : 0;
3295
+ const dataModelContent = emitDataModel(schema, useUmbrella);
3296
+ const apiContent = emitApi({ agents, functions, httpRoutes, useUmbrella, workflows });
3297
+ const serverContent = emitServer({
3298
+ agents,
3299
+ containers,
3300
+ env,
3301
+ hasAccessFacade,
3302
+ hasAi,
3303
+ hasAnalytics,
3304
+ hasBrowser,
3305
+ hasFlags,
3306
+ hasHyperdrive,
3307
+ hasImages,
3308
+ hasKv,
3309
+ hasPayments,
3310
+ hasPipelines,
3311
+ hasR2sql,
3312
+ hasX402,
3313
+ identity,
3314
+ queues,
3315
+ schema,
3316
+ storageRuleBuckets: storageRulesMetadata.rules.map((rule) => rule.bucket),
3317
+ useUmbrella,
3318
+ workflows
3319
+ });
3320
+ const functionsContent = emitFunctions({ agents, functions, migrations, mutators, shapes, useUmbrella, usesSandbox });
3321
+ const shardContent = emitShard({
3322
+ advisories,
3323
+ agents,
3324
+ containers,
3325
+ env,
3326
+ flagKeys,
3327
+ hasAccessFacade,
3328
+ hasAi,
3329
+ hasAnalytics,
3330
+ hasBrowser,
3331
+ hasFlags,
3332
+ hasHyperdrive,
3333
+ hasImages,
3334
+ hasKv,
3335
+ hasPayments,
3336
+ hasPipelines,
3337
+ hasR2sql,
3338
+ hasX402,
3339
+ maskMetadata,
3340
+ mutators,
3341
+ queues,
3342
+ rlsMetadata,
3343
+ schema,
3344
+ shapes,
3345
+ storageRules: storageRulesMetadata,
3346
+ studioFeatures,
3347
+ useUmbrella,
3348
+ workflows
3349
+ });
3350
+ const collectionsContent = emitCollections(shapes, dependencies.has("@lunora/db"), useUmbrella);
3351
+ const containersContent = emitContainers(containers, schema.jurisdiction);
3352
+ const workflowsContent = emitWorkflows(workflows);
3353
+ const agentsContent = emitAgents(agents);
3354
+ const queuesContent = emitQueues(queues);
3355
+ const cronsContent = emitCrons(crons);
3356
+ const vectorsContent = emitVectors(schema.vectorIndexes);
3357
+ const drizzleFiles = emitDrizzleSchema(schema, useUmbrella);
3358
+ const seedContent = emitSeed(dependencies.has("@lunora/seed"));
3359
+ const apiSpec = options.apiSpec ?? "openapi";
3360
+ const wantsOpenApi = apiSpec === "openapi" || apiSpec === "both";
3361
+ const wantsOpenRpc = apiSpec === "openrpc" || apiSpec === "both";
3362
+ const appContent = emitApp({
3363
+ // Inbound-email agents (`defineAgent({ onEmail })`) → wire the worker's
3364
+ // top-level `email()` handler to each agent's `AGENT_*` Workflow binding so
3365
+ // received mail starts a durable run. Empty for email-free (and agent-free)
3366
+ // projects, so the emitted app.ts stays byte-identical.
3367
+ emailAgents: agents.filter((agent) => agent.onEmail === true).map((agent) => {
3368
+ return { bindingName: agent.bindingName, exportName: agent.exportName };
3369
+ }),
3370
+ hasAccess: dependencies.has("@lunora/cloudflare-access"),
3371
+ hasAi,
3372
+ hasAnalytics,
3373
+ hasAuth: dependencies.has("@lunora/auth"),
3374
+ hasBrowser,
3375
+ // Worker-composition framework adapters expose a `withLunora` over
3376
+ // `withFrameworkWorker`; when one is installed, surface `.buildFrameworkWorker()`.
3377
+ hasFramework: dependencies.has("@lunora/astro") || dependencies.has("@lunora/svelte") || dependencies.has("@lunora/vue"),
3378
+ // `hasGlobal` means **D1-backed** global tables (the `.global()` / D1
3379
+ // app-builder wiring); Hyperdrive-backed globals are gated separately by
3380
+ // `hasHyperdriveGlobal` so an app picks the right binding+package.
3381
+ hasGlobal: schema.tables.some((table) => table.shardMode === "global" && table.globalBackend !== "hyperdrive"),
3382
+ hasHyperdrive,
3383
+ hasHyperdriveGlobal: schema.tables.some((table) => table.shardMode === "global" && table.globalBackend === "hyperdrive"),
3384
+ hasImages,
3385
+ // Auto-wire the studio's KV introspector on the SAME condition the nav
3386
+ // gates its tab on (`studioFeatures.kv` = ctx.kv usage OR a declared
3387
+ // `@lunora/bindings/kv` dep), so a visible KV tab always has a working
3388
+ // backend — never the reverse. The `ctx.kv` type-seam stays usage-only.
3389
+ hasKv: studioFeatures.kv,
3390
+ hasPayments,
3391
+ hasR2sql,
3392
+ hasQueue: queues.some((queue) => queue.mode === "push"),
3393
+ hasScheduler: studioFeatures.scheduler,
3394
+ hasStorage: studioFeatures.storage,
3395
+ hasVectors: schema.vectorIndexes.length > 0,
3396
+ hasWorkflow: workflows.length > 0,
3397
+ hasX402,
3398
+ // The single `defineIdentity(...)` contract (Plan 080). Wires
3399
+ // `options.identity` so the runtime trust boundary validates every
3400
+ // resolved identity before it becomes `ctx.auth`; `undefined` keeps the
3401
+ // emitted app.ts byte-identical to before this feature.
3402
+ identity,
3403
+ // Schema `.jurisdiction("…")` → pin the generated worker's DOs to the region.
3404
+ jurisdiction: schema.jurisdiction,
3405
+ useUmbrella,
3406
+ // Voice-enabled agents (`defineAgent({ voice: … })`) → wire the worker's
3407
+ // `/_lunora/voice/<exportName>` route to each agent's `VOICE_*` DO
3408
+ // namespace. Empty for voice-free (and agent-free) projects, so the
3409
+ // emitted app.ts stays byte-identical.
3410
+ voiceAgents: agents.filter((agent) => agent.voice === true && agent.voiceBindingName !== void 0).map((agent) => {
3411
+ return { bindingName: agent.voiceBindingName, exportName: agent.exportName };
3412
+ }),
3413
+ wantsOpenApi,
3414
+ wantsOpenRpc
3415
+ });
3416
+ const projectVersion = readProjectVersion(options.projectRoot);
3417
+ const openApiDocument = buildOpenApiDocument({ functions, httpRoutes, version: projectVersion });
3418
+ const openRpcDocument = buildOpenRpcDocument({ functions, version: projectVersion });
3419
+ const openApiContent = `${JSON.stringify(openApiDocument, void 0, 2)}
3420
+ `;
3421
+ const openRpcContent = `${JSON.stringify(openRpcDocument, void 0, 2)}
3422
+ `;
3423
+ const openApiModuleContent = emitOpenApiModule(openApiDocument);
3424
+ const openRpcModuleContent = emitOpenRpcModule(openRpcDocument);
3425
+ const schemaSnapshot = buildSchemaSnapshot(
3426
+ schema,
3427
+ migrations.map((migration) => migration.id)
3428
+ );
3429
+ const schemaSnapshotPath = join(lunoraDirectory, SCHEMA_SNAPSHOT_FILENAME);
3430
+ const schemaSnapshotExists = existsSync(schemaSnapshotPath);
3431
+ const outputDirectory = join(lunoraDirectory, "_generated");
3432
+ if (!options.dryRun) {
3433
+ if (!existsSync(outputDirectory)) {
3434
+ mkdirSync(outputDirectory, { recursive: true });
3435
+ }
3436
+ writeIfChanged(join(outputDirectory, "app.ts"), appContent);
3437
+ writeIfChanged(join(outputDirectory, "dataModel.ts"), dataModelContent);
3438
+ writeIfChanged(join(outputDirectory, "api.ts"), apiContent);
3439
+ writeIfChanged(join(outputDirectory, "server.ts"), serverContent);
3440
+ writeIfChanged(join(outputDirectory, "functions.ts"), functionsContent);
3441
+ writeIfChanged(join(outputDirectory, "shard.ts"), shardContent);
3442
+ writeIfChanged(join(outputDirectory, "crons.ts"), cronsContent);
3443
+ writeIfChanged(join(outputDirectory, "vectors.ts"), vectorsContent);
3444
+ writeIfChanged(join(outputDirectory, "drizzle.global.ts"), drizzleFiles.global);
3445
+ writeIfChanged(join(outputDirectory, "drizzle.shard.ts"), drizzleFiles.shard);
3446
+ writeIfPresent(join(outputDirectory, "containers.ts"), containersContent);
3447
+ writeIfPresent(join(outputDirectory, "workflows.ts"), workflowsContent);
3448
+ writeIfPresent(join(outputDirectory, "agents.ts"), agentsContent);
3449
+ writeIfPresent(join(outputDirectory, "queues.ts"), queuesContent);
3450
+ writeIfPresent(join(outputDirectory, "seed.ts"), seedContent);
3451
+ writeIfPresent(join(outputDirectory, "collections.ts"), collectionsContent);
3452
+ writeIfPresent(join(outputDirectory, "openapi.json"), wantsOpenApi ? openApiContent : "");
3453
+ writeIfPresent(join(outputDirectory, "openapi.ts"), wantsOpenApi ? openApiModuleContent : "");
3454
+ writeIfPresent(join(outputDirectory, "openrpc.json"), wantsOpenRpc ? openRpcContent : "");
3455
+ writeIfPresent(join(outputDirectory, "openrpc.ts"), wantsOpenRpc ? openRpcModuleContent : "");
3456
+ if (!schemaSnapshotExists || options.updateSchemaBaseline === true) {
3457
+ writeIfChanged(schemaSnapshotPath, serializeSchemaSnapshot(schemaSnapshot));
3458
+ }
3459
+ }
3460
+ if (timingEnabled) {
3461
+ const finishedAt = performance.now();
3462
+ const total = Math.round(finishedAt - startedAt);
3463
+ const discovery = Math.round(emitStartedAt - startedAt);
3464
+ const emit = Math.round(finishedAt - emitStartedAt);
3465
+ console.error(`@lunora/codegen: codegen took ${total.toString()}ms (discovery ${discovery.toString()}ms, emit ${emit.toString()}ms)`);
3466
+ }
3467
+ return {
3468
+ advisories,
3469
+ agents,
3470
+ containers,
3471
+ cronTriggers: emitWranglerCronTriggers(crons),
3472
+ generated: {
3473
+ agents: agentsContent,
3474
+ api: apiContent,
3475
+ app: appContent,
3476
+ collections: collectionsContent,
3477
+ containers: containersContent,
3478
+ crons: cronsContent,
3479
+ dataModel: dataModelContent,
3480
+ drizzleGlobal: drizzleFiles.global,
3481
+ drizzleShard: drizzleFiles.shard,
3482
+ functions: functionsContent,
3483
+ openApi: openApiContent,
3484
+ openApiModule: openApiModuleContent,
3485
+ openRpc: openRpcContent,
3486
+ openRpcModule: openRpcModuleContent,
3487
+ queues: queuesContent,
3488
+ seed: seedContent,
3489
+ server: serverContent,
3490
+ shard: shardContent,
3491
+ vectors: vectorsContent,
3492
+ workflows: workflowsContent
3493
+ },
3494
+ outputDirectory,
3495
+ queues,
3496
+ schemaSnapshot,
3497
+ schemaSnapshotPath,
3498
+ workflows
3499
+ };
3500
+ };
3501
+
3502
+ export { SCHEMA_SNAPSHOT_FILENAME, createCodegenProject, refreshCodegenProject, runCodegen };