@lunora/codegen 0.0.0 → 1.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +117 -9
  3. package/__assets__/package-og.svg +14 -0
  4. package/dist/index.d.mts +1473 -0
  5. package/dist/index.d.ts +1473 -0
  6. package/dist/index.mjs +28 -0
  7. package/dist/packem_shared/CONTAINERS_FILENAME-0K-pjNb8.mjs +224 -0
  8. package/dist/packem_shared/CodegenDiagnosticError-54jWDxA9.mjs +22 -0
  9. package/dist/packem_shared/LUNORA_ERROR_CODES-CySpQPD3.mjs +61 -0
  10. package/dist/packem_shared/buildOpenApiDocument-yHVN66Xd.mjs +183 -0
  11. package/dist/packem_shared/buildOpenRpcDocument-BZGY1-jT.mjs +60 -0
  12. package/dist/packem_shared/buildSchemaSnapshot-DzLDbWk3.mjs +233 -0
  13. package/dist/packem_shared/createCodegenProject-DGJm0_Pk.mjs +895 -0
  14. package/dist/packem_shared/discover-ast-CT6BgBr4.mjs +13 -0
  15. package/dist/packem_shared/discoverAuthApiCalls-C35R6z0T.mjs +62 -0
  16. package/dist/packem_shared/discoverCrons-BL6iGuJ3.mjs +254 -0
  17. package/dist/packem_shared/discoverFunctions-DEgAcRuD.mjs +460 -0
  18. package/dist/packem_shared/discoverHttpRoutes-C978pBiG.mjs +131 -0
  19. package/dist/packem_shared/discoverInserts-CRQdXvHO.mjs +39 -0
  20. package/dist/packem_shared/discoverMaskProcedures-B64zA740.mjs +217 -0
  21. package/dist/packem_shared/discoverMigrations-Doj_-BAA.mjs +91 -0
  22. package/dist/packem_shared/discoverNondeterministicCalls-4KiPQxQU.mjs +122 -0
  23. package/dist/packem_shared/discoverQueries-BkIi0dBD.mjs +62 -0
  24. package/dist/packem_shared/discoverRlsMetadata-DpRB1HMe.mjs +280 -0
  25. package/dist/packem_shared/discoverSchema-BBulgGbH.mjs +542 -0
  26. package/dist/packem_shared/discoverStorageRulesMetadata-DAqJUxUv.mjs +97 -0
  27. package/dist/packem_shared/discoverWorkflows-DRDQdhfq.mjs +84 -0
  28. package/dist/packem_shared/emitApi-hRVC-kE7.mjs +2426 -0
  29. package/dist/packem_shared/emitApp-CzZ6GbrD.mjs +593 -0
  30. package/dist/packem_shared/lintSchema-DicbOHvH.mjs +68 -0
  31. package/dist/packem_shared/parse-validator-tuQtHrsr.mjs +132 -0
  32. package/dist/packem_shared/paths-BRd6JHuF.mjs +11 -0
  33. package/dist/packem_shared/schemaFromIr-DTYsLBaA.mjs +57 -0
  34. package/package.json +45 -17
@@ -0,0 +1,1473 @@
1
+ import { Finding } from '@lunora/advisor';
2
+ export type { Finding } from '@lunora/advisor';
3
+ import { Node, Project } from 'ts-morph';
4
+ import { StudioFeaturesResult } from '@lunora/do';
5
+ import { Schema } from '@lunora/server';
6
+ import { JsonSchema } from '@lunora/values';
7
+ /**
8
+ * AST-observable subset of a column's modifier chain (`.unique()`, `.default()`,
9
+ * …). Function-valued modifiers (`.$defaultFn`/`.$onUpdateFn`) can't be
10
+ * serialized, so only their *presence* is recorded.
11
+ */
12
+ interface ColumnMetaIR {
13
+ /** `.default(...)` or `.$defaultFn(...)` present — field is optional on insert. */
14
+ hasDefault?: boolean;
15
+ /** `.$onUpdateFn(...)` present. */
16
+ hasOnUpdate?: boolean;
17
+ /** Default `true`; `.nullable()` flips it to `false`. */
18
+ notNull: boolean;
19
+ /** `.unique()` present. */
20
+ unique?: boolean;
21
+ }
22
+ /** Reflective representation of a single validator call from the schema. */
23
+ interface ValidatorIR {
24
+ /** For `v.storage(bucket?)` — the named typed bucket the object key lives in, when given. */
25
+ bucket?: string;
26
+ /** Column modifiers (`.unique()`, `.default()`, `.nullable()`, …) when present. */
27
+ column?: ColumnMetaIR;
28
+ /** For `v.optional(inner)` / `v.array(inner)`. */
29
+ inner?: ValidatorIR;
30
+ /** For `v.record(key, value)`. */
31
+ keyType?: ValidatorIR;
32
+ /** Kind of validator (string, number, id, object, optional, array, union, literal, record, ...). */
33
+ kind: string;
34
+ /** For `v.literal(value)` — the literal value as source text. */
35
+ literalValue?: string;
36
+ /** For `v.union(a, b, ...)`. */
37
+ members?: ValidatorIR[];
38
+ /** For `v.object({...})`. */
39
+ shape?: Record<string, ValidatorIR>;
40
+ /** Verbatim source text — used in emitted code when we can't reconstruct from AST. */
41
+ sourceText?: string;
42
+ /** For `v.id("table")` — the table name. */
43
+ tableName?: string;
44
+ valueType?: ValidatorIR;
45
+ }
46
+ interface IndexIR {
47
+ fields: ReadonlyArray<string>;
48
+ name: string;
49
+ unique?: boolean;
50
+ }
51
+ interface SearchIndexIR {
52
+ /** Primary text-search field. */
53
+ field: string;
54
+ /** Optional filter fields surfaced alongside the FTS column. */
55
+ filterFields?: ReadonlyArray<string>;
56
+ name: string;
57
+ }
58
+ interface VectorIndexIR {
59
+ dimensions?: number;
60
+ /** Shape A: the single source column. Shape B: undefined (derived via a `select` fn). */
61
+ field?: string;
62
+ /** Shape A: metadata field names mirrored into Vectorize. */
63
+ metadata?: ReadonlyArray<string>;
64
+ metric?: "cosine" | "dot-product" | "euclidean";
65
+ name: string;
66
+ /** Owning table the vectors are sourced from. */
67
+ table: string;
68
+ }
69
+ /**
70
+ * One ordering key on a rank index's `sortBy`: the column and direction.
71
+ * Mirrors the runtime `RankSortKey` (defaults `direction` to `"asc"`).
72
+ */
73
+ interface RankSortKeyIR {
74
+ direction: "asc" | "desc";
75
+ field: string;
76
+ }
77
+ /**
78
+ * A `.rankIndex(name, { sortBy, partitionBy?, where? })` declaration. The owning
79
+ * table is always the table the index is declared on, so — unlike a vector index
80
+ * — there is no separate `on`/`table` reference to carry: it rides along on its
81
+ * {@link TableIR}. Only the fields needed for type emission are captured.
82
+ */
83
+ interface RankIndexIR {
84
+ name: string;
85
+ /** Columns scoping each ranking; omitted ⇒ one global rank over the table. */
86
+ partitionBy?: ReadonlyArray<string>;
87
+ /** Ordered sort keys driving the rank. */
88
+ sortBy: ReadonlyArray<RankSortKeyIR>;
89
+ }
90
+ interface RelationIR {
91
+ /** FK column: on this table for `one`, on the target table for `many`. */
92
+ field: string;
93
+ kind: "many" | "one";
94
+ /** Accessor name the relation is loaded under (the `with` key). */
95
+ name: string;
96
+ /** FK behaviour applied to holder rows when the referenced parent is deleted. */
97
+ onDelete?: "cascade" | "restrict" | "set null";
98
+ /** Referenced column (defaults to `_id`). */
99
+ references: string;
100
+ /** Target table name. */
101
+ table: string;
102
+ }
103
+ interface TableIR {
104
+ /**
105
+ * `true` when the table chain carried `.externallyManaged()` — its rows are
106
+ * written outside Lunora's discoverable insert path (adapter/migration/
107
+ * middleware), so advisor insert-path lints skip it. Optional: hand-built
108
+ * IR and the runtime `fromServerSchema` path default it to `false`.
109
+ */
110
+ externallyManaged?: boolean;
111
+ /**
112
+ * Storage backend for a `.global()` table: `"d1"` (default) or
113
+ * `"hyperdrive"` (a Postgres/MySQL database via Cloudflare Hyperdrive). Only
114
+ * meaningful when `shardMode === "global"`; absent for sharded/root tables.
115
+ */
116
+ globalBackend?: "d1" | "hyperdrive";
117
+ indexes: ReadonlyArray<IndexIR>;
118
+ name: string;
119
+ /** Rank indexes declared inline via `.rankIndex(name, …)`. */
120
+ rankIndexes: ReadonlyArray<RankIndexIR>;
121
+ /** Declared relations (via `.relations((r) => …)`), keyed in source order. */
122
+ relations: ReadonlyArray<RelationIR>;
123
+ searchIndexes: ReadonlyArray<SearchIndexIR>;
124
+ shape: Record<string, ValidatorIR>;
125
+ shardMode: "global" | "root" | {
126
+ field: string;
127
+ kind: "shardBy";
128
+ };
129
+ /** Vector indexes declared inline via `.vectorize()` (DSL Shape A). */
130
+ vectorIndexes: ReadonlyArray<VectorIndexIR>;
131
+ }
132
+ interface SchemaIR {
133
+ tables: ReadonlyArray<TableIR>;
134
+ /** All vector indexes (inline Shape A hoisted + standalone Shape B), flattened. */
135
+ vectorIndexes: ReadonlyArray<VectorIndexIR>;
136
+ }
137
+ interface FunctionIR {
138
+ args: Record<string, ValidatorIR>;
139
+ exportName: string;
140
+ /** Path relative to `&lt;projectRoot>/lunora/` without extension, e.g. "messages". */
141
+ filePath: string;
142
+ kind: "action" | "mutation" | "query" | "stream";
143
+ /**
144
+ * Set on connection-lifecycle hooks (`onConnect`/`onDisconnect`): the socket
145
+ * side the hook fires on. Such a function is also an internal mutation (so it
146
+ * lands in `LUNORA_FUNCTIONS` for path dispatch); emit additionally collects
147
+ * it into the `LUNORA_LIFECYCLE_HOOKS` manifest keyed by this side. Absent on
148
+ * ordinary functions.
149
+ */
150
+ lifecycle?: "connect" | "disconnect";
151
+ /**
152
+ * Serialized TS source for the handler's return type, with `Promise&lt;T>`
153
+ * unwrapped so callers see `T` directly. Defaults to `"unknown"` when
154
+ * ts-morph cannot resolve the type (typically because the consuming
155
+ * project lacks a tsconfig that can reach `@lunora/server`).
156
+ */
157
+ returnType: string;
158
+ /**
159
+ * Call surface the function is exposed on. Absent (or `"public"`) means it
160
+ * lands in the generated `api`; `"internal"` routes it to the separate
161
+ * `internal` object and is rejected by the DO's external RPC path.
162
+ */
163
+ visibility?: "internal" | "public";
164
+ }
165
+ /**
166
+ * A `defineMigration({...})` declaration discovered in the user's lunora
167
+ * sources. The emitted `LUNORA_MIGRATIONS` registry keys on {@link MigrationIR.id}; the
168
+ * import wiring needs {@link MigrationIR.exportName}/{@link MigrationIR.filePath}. {@link MigrationIR.table} is
169
+ * informational (the runtime object carries the authoritative value).
170
+ */
171
+ interface MigrationIR {
172
+ /** Export binding name, used to reference the module member in generated imports. */
173
+ exportName: string;
174
+ /** Path relative to `&lt;projectRoot>/lunora/` without extension, e.g. "migrations". */
175
+ filePath: string;
176
+ /** Stable migration id — the registry key and per-shard run-state key. */
177
+ id: string;
178
+ /** Table the migration iterates; `""` when not a static string literal. */
179
+ table: string;
180
+ }
181
+ /**
182
+ * A single cron job lifted from a `cronJobs()` builder in `lunora/crons.ts`.
183
+ * Mirrors `@lunora/scheduler`'s `CronJob`: {@link CronJobIR.cron} is the compiled
184
+ * standard cron expression, {@link CronJobIR.functionPath} is the target
185
+ * `__lunoraRef` (`namespace:fn`), and {@link CronJobIR.args} is the static
186
+ * argument object passed at registration.
187
+ */
188
+ interface CronJobIR {
189
+ /** Static args object (source-text JSON), defaults to `{}`. */
190
+ args: Record<string, unknown>;
191
+ /** Compiled standard cron expression, e.g. `"0 9 * * *"`. */
192
+ cron: string;
193
+ /** Target function ref `namespace:fn`. Present for a function target; absent when {@link CronJobIR.workflow} is set. */
194
+ functionPath?: string;
195
+ /** Unique, human-readable job name. */
196
+ name: string;
197
+ /**
198
+ * Set when the job targets a durable workflow (a `lunora/workflows.ts`
199
+ * export) instead of a function: the workflow's `WORKFLOW_*` binding name
200
+ * plus its export name. On each fire the worker starts a new workflow
201
+ * INSTANCE (the {@link CronJobIR.args} become its `params`) rather than
202
+ * dispatching a one-shot function.
203
+ */
204
+ workflow?: {
205
+ binding: string;
206
+ exportName: string;
207
+ };
208
+ }
209
+ /**
210
+ * A container lifted from a `defineContainer()` export in
211
+ * `lunora/containers.ts`. Carries everything the emitters and the config layer
212
+ * need to wire wrangler (`containers[]` + the Durable Object binding +
213
+ * migration class) and the generated `_generated/containers.ts` DO class.
214
+ * Names are derived via `@lunora/container`'s shared helpers so codegen and
215
+ * the config layer can never disagree.
216
+ */
217
+ interface ContainerIR {
218
+ /** Durable Object binding name, e.g. `CONTAINER_TRANSCODER`. */
219
+ bindingName: string;
220
+ /** Static Dockerfile build args (wrangler `image_vars`), when declared as literals. */
221
+ buildArgs?: Record<string, string>;
222
+ /** Generated DO class name, e.g. `TranscoderContainer`. */
223
+ className: string;
224
+ /**
225
+ * Whether the container may open outbound internet connections, when the
226
+ * value was a static literal. `undefined` means the field was omitted (the
227
+ * platform default is `true`) or wasn't a literal. Lifted for the advisor.
228
+ */
229
+ enableInternet?: boolean;
230
+ /** The `lunora/containers.ts` export name, e.g. `transcoder`. */
231
+ exportName: string;
232
+ /**
233
+ * Normalized image source: a local Dockerfile (`dockerfile`), a pre-built
234
+ * registry reference (`registry`), or a Railpack source directory (`build`)
235
+ * that the deploy step builds and pushes before wrangler runs.
236
+ */
237
+ image: {
238
+ buildContext: string;
239
+ dockerfilePath: string;
240
+ kind: "dockerfile";
241
+ } | {
242
+ buildDir: string;
243
+ kind: "build";
244
+ } | {
245
+ kind: "registry";
246
+ reference: string;
247
+ };
248
+ /** Static `instanceType`, when declared. */
249
+ instanceType?: string | {
250
+ diskMb?: number;
251
+ memoryMib?: number;
252
+ vcpu?: number;
253
+ };
254
+ /** Static `maxInstances`, when declared. */
255
+ maxInstances?: number;
256
+ /** Static wrangler `containers[].name` override, when declared. */
257
+ name?: string;
258
+ /** Static rolling-deploy tuning, when declared as literals. */
259
+ rollout?: {
260
+ gracePeriodSeconds?: number;
261
+ stepPercentage?: number;
262
+ };
263
+ /**
264
+ * The static `sleepAfter` value, when it was a literal. `undefined` means
265
+ * omitted (platform default `"10m"`) or non-literal. Lifted for the advisor.
266
+ */
267
+ sleepAfter?: number | string;
268
+ }
269
+ /**
270
+ * A workflow lifted from a `defineWorkflow()` export in `lunora/workflows.ts`.
271
+ * Carries what the emitters and the config layer need to wire the wrangler
272
+ * `workflows[]` entry and the generated `_generated/workflows.ts`
273
+ * `WorkflowEntrypoint` class. Unlike containers, workflows are NOT Durable
274
+ * Objects — wrangler gets only a `workflows[]` entry, never a `durable_objects`
275
+ * binding or a migration class. Names are derived via `@lunora/workflow`'s
276
+ * shared helpers so codegen and the config layer can never disagree.
277
+ */
278
+ interface WorkflowIR {
279
+ /** The Cloudflare `Workflow` binding name, e.g. `WORKFLOW_ORDER_PIPELINE`. */
280
+ bindingName: string;
281
+ /** Generated `WorkflowEntrypoint` class name, e.g. `OrderPipelineWorkflow`. */
282
+ className: string;
283
+ /** The `lunora/workflows.ts` export name, e.g. `orderPipeline`. */
284
+ exportName: string;
285
+ /**
286
+ * The stable wrangler `workflows[].name`. Defaults to the kebab-cased export
287
+ * name (`orderPipeline` → `order-pipeline`); a static `name:` literal in the
288
+ * definition overrides it.
289
+ */
290
+ name: string;
291
+ }
292
+ /**
293
+ * A `ctx.workflows.get("name")…` call discovered in a function body — the
294
+ * use-site analog of {@link WorkflowIR} (which is the declaration side). Feeds
295
+ * the `workflow_unused` lint (a declared workflow with zero call sites) and the
296
+ * `workflow_unknown_target` lint (a `.get("x")` whose `x` isn't declared — a
297
+ * typo catcher). {@link WorkflowCallIR.workflow} is `""` when the `get(...)`
298
+ * argument is not a string literal (a dynamic name — which suppresses the
299
+ * unused-workflow heuristic rather than producing a false positive).
300
+ */
301
+ interface WorkflowCallIR {
302
+ /** Export binding name of the function performing the call, e.g. `create`. */
303
+ exportName: string;
304
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension (the api namespace). */
305
+ file: string;
306
+ /** 1-based line of the `get(...)` call. */
307
+ line: number;
308
+ /** The referenced workflow export name, or `""` when the argument is not a string literal. */
309
+ workflow: string;
310
+ }
311
+ /**
312
+ * A `ctx.db.query("table")…` read discovered in a function body, reduced to what
313
+ * the `filter_without_index` advisor lint needs: which table, whether the chain
314
+ * narrows with an index, and whether it filters. `table` is `""` when the
315
+ * `query(...)` argument is not a string literal (a dynamic table — not lintable).
316
+ */
317
+ interface QueryReadIR {
318
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
319
+ file: string;
320
+ /** The chain calls `.filter(...)`. */
321
+ hasFilter: boolean;
322
+ /** The chain narrows with `.withIndex(...)` or `.withSearchIndex(...)`. */
323
+ hasIndex: boolean;
324
+ /** 1-based line of the `query(...)` call. */
325
+ line: number;
326
+ /** Queried table name, or `""` when the argument is not a string literal. */
327
+ table: string;
328
+ }
329
+ /**
330
+ * A `ctx.authApi.&lt;method>(...)` call discovered in a function body, attributed
331
+ * to the exported function (and its file = api namespace) that performs it.
332
+ * Structurally identical to `AdvisorAuthApiCall` so it passes straight through
333
+ * to the advisor lint without conversion, exactly as `InsertWriteIR` does for
334
+ * `AdvisorInsertWrite`.
335
+ */
336
+ interface AuthApiCallIR {
337
+ /** Export binding name of the function performing the call, e.g. "createOrg". */
338
+ exportName: string;
339
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
340
+ file: string;
341
+ /** True when the call's argument object includes a `headers` property. */
342
+ hasHeaders: boolean;
343
+ /** 1-based line of the call, or `0` when unknown. */
344
+ line: number;
345
+ /** The better-auth method invoked (e.g. `banUser`); empty when not statically known. */
346
+ method: string;
347
+ }
348
+ /**
349
+ * A `ctx.db.insert("table", …)` write discovered in a function body, attributed
350
+ * to the exported function (and its file = api namespace) that performs it — the
351
+ * write-side analog of {@link QueryReadIR}. Lets tooling wire a table's write
352
+ * action by behavior (which function inserts into it) rather than by naming.
353
+ * {@link InsertWriteIR.table} is `""` when the argument is not a string literal.
354
+ */
355
+ interface InsertWriteIR {
356
+ /** Export binding name of the function performing the insert, e.g. "send". */
357
+ exportName: string;
358
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension (the api namespace). */
359
+ file: string;
360
+ /** 1-based line of the `insert(...)` call. */
361
+ line: number;
362
+ /** Target table name, or `""` when the argument is not a string literal. */
363
+ table: string;
364
+ }
365
+ /**
366
+ * A non-deterministic API call (`Date.now`, `Math.random`, `crypto.randomUUID`,
367
+ * `crypto.getRandomValues`, `fetch`) discovered lexically inside a `query(...)`
368
+ * or `mutation(...)` handler body — the `nondeterministic_query_mutation` lint
369
+ * input. Structurally identical to `AdvisorNondeterministicCall` so values pass
370
+ * straight through to the advisor without conversion, exactly as `AuthApiCallIR`
371
+ * does for `AdvisorAuthApiCall`. `action(...)` handlers are never recorded —
372
+ * actions are the determinism escape hatch.
373
+ */
374
+ interface NondeterministicCallIR {
375
+ /** The non-deterministic API invoked, e.g. `Date.now` / `Math.random` / `crypto.randomUUID` / `fetch`. */
376
+ callee: string;
377
+ /** Export binding name of the function performing the call, e.g. `sendMessage`. */
378
+ exportName: string;
379
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension (the api namespace). */
380
+ file: string;
381
+ /** Which procedure kind the call lives in — only `query`/`mutation` handlers are recorded. */
382
+ kind: "mutation" | "query";
383
+ /** 1-based line of the call, or `0` when unknown. */
384
+ line: number;
385
+ }
386
+ /**
387
+ * Per-procedure RLS usage snapshot, produced by `discoverRlsProcedures` for the
388
+ * `rls_uncovered_table` advisor lint. Structurally identical to
389
+ * `AdvisorRlsProcedure` (they share the same field set) so values pass straight
390
+ * through to the advisor without conversion, exactly as `AuthApiCallIR` does for
391
+ * `AdvisorAuthApiCall`.
392
+ */
393
+ interface RlsProcedureIR {
394
+ /** Export binding name of the procedure (e.g. `listDocuments`). */
395
+ exportName: string;
396
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
397
+ file: string;
398
+ /**
399
+ * Table names extracted from the `rls(policies)` array literal. Empty when the
400
+ * policies argument is not a statically-readable array literal.
401
+ */
402
+ rlsTables: string[];
403
+ /** Tables read by the procedure via `ctx.db.query/findMany/findFirst/…`. */
404
+ tablesRead: string[];
405
+ /** Tables written by the procedure via `ctx.db.insert/patch/replace/delete`. */
406
+ tablesWritten: string[];
407
+ /** `true` when the procedure's builder chain includes `.use(rls(...))`. */
408
+ usesRls: boolean;
409
+ /** `"internal"` for `internalQuery` / `internalMutation` / `internalAction`. */
410
+ visibility: "internal" | "public";
411
+ }
412
+ /**
413
+ * One procedure reduced to the facts the `mask_uncovered_pii_column` lint needs:
414
+ * whether its builder chain includes `.use(mask(...))`, which `(table, column)`
415
+ * pairs that mask declares, and which tables the procedure reads/writes. The
416
+ * column-level analogue of {@link RlsProcedureIR}. Structurally identical to
417
+ * `AdvisorMaskProcedure` so values pass straight through without conversion.
418
+ */
419
+ interface MaskProcedureIR {
420
+ /** Export binding name of the procedure (e.g. `listUsers`). */
421
+ exportName: string;
422
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
423
+ file: string;
424
+ /**
425
+ * `(table, column)` pairs this procedure's `mask(policies)` object literal
426
+ * declares. Empty when the policies argument is not a statically-readable
427
+ * object literal (conservative: `usesMask` is still `true`).
428
+ */
429
+ maskColumns: {
430
+ column: string;
431
+ table: string;
432
+ }[];
433
+ /** Tables read by the procedure via `ctx.db.query/findMany/findFirst/get`. */
434
+ tablesRead: string[];
435
+ /** Tables written by the procedure via `ctx.db.insert/patch/replace/delete`. */
436
+ tablesWritten: string[];
437
+ /** `true` when the procedure's builder chain includes `.use(mask(...))`. */
438
+ usesMask: boolean;
439
+ /** `"internal"` for `internalQuery` / `internalMutation` / `internalAction`. */
440
+ visibility: "internal" | "public";
441
+ }
442
+ /**
443
+ * One masked column surfaced to the studio's data-browser mask preview: a
444
+ * `(table, column)` pair plus the declared {@link MaskProcedureIR} strategy so
445
+ * the preview can pick redact-vs-hash-vs-custom rendering. Aggregated across the
446
+ * project's `.use(mask(...))` chains by `discoverMaskMetadata`; the descriptive
447
+ * twin of {@link RlsPolicyIR}. `"custom"` covers any non-string strategy (a
448
+ * `(value, ctx) => …` function) — its logic is an opaque closure, never read by
449
+ * the UI; the preview renders a fixed sentinel for it.
450
+ */
451
+ interface MaskColumnMetadataIR {
452
+ /** Column the mask policy redacts. */
453
+ column: string;
454
+ /** Declared masking strategy: `"redact"`/`"hash"` string literals, else `"custom"` for a `MaskFn`. */
455
+ strategy: "custom" | "hash" | "redact";
456
+ /** Logical table the masked column belongs to. */
457
+ table: string;
458
+ }
459
+ /**
460
+ * Schema-wide masking metadata the codegen emits into the generated ShardDO so
461
+ * the studio's data-browser mask toggle can preview what a non-privileged caller
462
+ * would see. Aggregated across every `.use(mask(...))` chain in the project —
463
+ * purely descriptive (table + column + strategy), never the masking closure. The
464
+ * column-level analogue of {@link RlsMetadataIR}.
465
+ */
466
+ interface MaskMetadataIR {
467
+ /** Every statically-discovered masked column, deduped by `(table, column)` (first declaration wins). */
468
+ columns: MaskColumnMetadataIR[];
469
+ }
470
+ /**
471
+ * One statically-readable policy entry from an `rls([...])` array literal,
472
+ * surfaced to the studio's read-only RLS inspector via the generated
473
+ * `rlsPolicies()` hook. Captures the policy's `table` + `on` operation and the
474
+ * procedure it guards — never the `when` predicate, which is an opaque JS
475
+ * closure (its logic stays in code, not the UI). Produced by
476
+ * `discoverRlsProcedures` alongside the lint IR.
477
+ */
478
+ interface RlsPolicyIR {
479
+ /** Source file (relative to `lunora/`, without extension) the policy is declared in. */
480
+ file: string;
481
+ /** Operation the policy gates: `read` covers get/query/findMany, the rest are writes. */
482
+ on: "delete" | "insert" | "read" | "update";
483
+ /** Export name of the procedure whose `.use(rls(...))` chain declared this policy. */
484
+ procedure: string;
485
+ /** Logical table the policy applies to. Empty when not a string literal. */
486
+ table: string;
487
+ }
488
+ /**
489
+ * One statically-readable role entry from an `rls(policies, { roles: [...] })`
490
+ * call, surfaced to the studio's RLS inspector. Captures the role's `name`,
491
+ * optional `description`, and the names of the permissions it grants (string
492
+ * literals or `definePermission("name")` calls). Produced by
493
+ * `discoverRlsProcedures`.
494
+ */
495
+ interface RlsRoleIR {
496
+ /** Optional human-readable description from `defineRole(name, { description })`. */
497
+ description?: string;
498
+ /** Role label attached to the request identity (e.g. `"admin"`). */
499
+ name: string;
500
+ /** Permission names this role grants, deduped and in declared order. */
501
+ permissions: string[];
502
+ }
503
+ /**
504
+ * Schema-wide RLS metadata the codegen emits into the generated ShardDO so the
505
+ * studio's read-only inspector can list, per table, which policies guard it and
506
+ * what roles are defined. Aggregated across every `.use(rls(...))` chain in the
507
+ * project — purely descriptive, never the predicate logic.
508
+ */
509
+ interface RlsMetadataIR {
510
+ /** Every statically-discovered policy `(table, on, procedure)` entry. */
511
+ policies: RlsPolicyIR[];
512
+ /** Every statically-discovered role, deduped by name (first declaration wins). */
513
+ roles: RlsRoleIR[];
514
+ }
515
+ /** One statically-discovered `defineStorageRule({ bucket, on, prefix })` entry from a `.use(storageRules(...))` chain. */
516
+ interface StorageRuleIR {
517
+ bucket: string;
518
+ /** Relative to `lunora/`. */
519
+ file: string;
520
+ on: "delete" | "list" | "read" | "write";
521
+ /** Optional key-prefix scope; absent ⇒ the whole bucket. */
522
+ prefix?: string;
523
+ /** The exported procedure that installed the rule. */
524
+ procedure: string;
525
+ }
526
+ /**
527
+ * Schema-wide storage-access-rule metadata emitted into the generated ShardDO so
528
+ * the studio's read-only inspector can list, per bucket, which operations are
529
+ * gated and under what key prefix. Aggregated across every
530
+ * `.use(storageRules(...))` chain — descriptive only, never the predicate logic.
531
+ */
532
+ interface StorageRulesMetadataIR {
533
+ rules: StorageRuleIR[];
534
+ }
535
+ /**
536
+ * A typed REST route declared with the `httpRoute.&lt;verb>("/path")…` builder in
537
+ * `@lunora/server` and mounted on `httpRouter()`. Captured statically from the
538
+ * builder chain so the OpenAPI emitter can render a real `paths` entry: the verb
539
+ * + path become the operation's method + URL, and the accumulated validator maps
540
+ * become its query parameters, path parameters, and request body.
541
+ */
542
+ interface HttpRouteIR {
543
+ /** `v.*` validators decoding the JSON request body (`.body({...})`), keyed by field. */
544
+ body: Record<string, ValidatorIR>;
545
+ /** Export binding name of the route handler (used only for diagnostics / dedupe). */
546
+ exportName: string;
547
+ /** Path relative to `&lt;projectRoot>/lunora/` without extension, e.g. "http". */
548
+ filePath: string;
549
+ /** HTTP verb the route binds to (uppercased), e.g. `"GET"`. */
550
+ method: string;
551
+ /** The `.output(validator)` return schema when declared; absent ⇒ TS-inferred / best-effort. */
552
+ output?: ValidatorIR;
553
+ /** `v.*` validators decoding the hono path params (`.params({...})`), keyed by `:name`. */
554
+ params: Record<string, ValidatorIR>;
555
+ /** The route path passed to `httpRoute.&lt;verb>(path)`, e.g. `/api/todos/:id`. */
556
+ path: string;
557
+ /** `v.*` validators decoding the URL query string (`.searchParams({...})`), keyed by name. */
558
+ searchParams: Record<string, ValidatorIR>;
559
+ /** `true` when declared via the terminal `.stream(...)` (Server-Sent Events) rather than `.handler(...)`. */
560
+ stream: boolean;
561
+ }
562
+ /**
563
+ * Per-procedure protective-middleware snapshot, produced by
564
+ * `discoverProcedureMiddleware` for the security lints
565
+ * (`public_mutation_without_ratelimit`, `user_creating_mutation_without_captcha`).
566
+ * Records which `.use(...)` guards a procedure's builder chain carries plus the
567
+ * behavioural facts that decide whether a guard is *expected* (does it write a
568
+ * user/session table, does it send mail). `protectPublic({ rateLimit, captcha })`
569
+ * is unwrapped: the bundle's object-literal keys set `usesRateLimit`/`usesCaptcha`
570
+ * exactly as the individual `.use(rateLimit(...))` / `.use(verifyTurnstile(...))`
571
+ * steps would. Structurally identical to `AdvisorProcedureProtection` so values
572
+ * pass straight through to the advisor without conversion.
573
+ */
574
+ interface ProcedureMiddlewareIR {
575
+ /** `true` when the handler (or a helper inside it) references `ctx.mail` / `ctx.email`. */
576
+ callsMail: boolean;
577
+ /** Export binding name of the procedure (e.g. `signUp`). */
578
+ exportName: string;
579
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
580
+ file: string;
581
+ /** Registration kind — only `mutation`/`action` are write-shaped; `query` is read-only. */
582
+ kind: "action" | "mutation" | "query";
583
+ /** `true` when the chain carries `.use(verifyTurnstile(...))` or a `protectPublic({ captcha })` bundle. */
584
+ usesCaptcha: boolean;
585
+ /** `true` when the chain carries `.use(mask(...))`. */
586
+ usesMask: boolean;
587
+ /** `true` when the chain carries `.use(rateLimit(...))` or a `protectPublic({ rateLimit })` bundle. */
588
+ usesRateLimit: boolean;
589
+ /** `true` when the chain carries `.use(rls(...))`. */
590
+ usesRls: boolean;
591
+ /** `"internal"` for `internalQuery` / `internalMutation` / `internalAction`. */
592
+ visibility: "internal" | "public";
593
+ /** `true` when the handler inserts into a user/session/account-shaped table. */
594
+ writesUserTable: boolean;
595
+ }
596
+ /**
597
+ * Per-procedure argument-validator snapshot, produced by the
598
+ * argument-validator discoverer for the input-hardening lints
599
+ * (`public_arg_uses_any`, `unbounded_string_arg`). Only public procedures are
600
+ * recorded — internal functions take server-trusted input. Structurally identical
601
+ * to `AdvisorArgumentValidator` so it passes straight through to the advisor
602
+ * without conversion.
603
+ */
604
+ interface ArgumentValidatorIR {
605
+ /** Arg names declared as `v.any()` (unvalidated, untyped input). */
606
+ anyArgs: string[];
607
+ /** Export binding name of the procedure (e.g. `updateProfile`). */
608
+ exportName: string;
609
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
610
+ file: string;
611
+ /** 1-based line of the registration call, or `0` when unknown. */
612
+ line: number;
613
+ /** Arg names declared as `v.string()` with no statically-visible max-length bound. */
614
+ unboundedStringArgs: string[];
615
+ }
616
+ /**
617
+ * One secret-shaped string literal discovered in `lunora/` source — the
618
+ * `hardcoded_secret` lint input. Complements the pre-commit `vis secrets` scan by
619
+ * surfacing the same class of finding in-IDE via the studio Advisors table.
620
+ * Structurally identical to `AdvisorSecretLiteral`.
621
+ */
622
+ interface SecretLiteralIR {
623
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
624
+ file: string;
625
+ /** Heuristic that matched, e.g. `stripe_live_key` / `aws_access_key` / `pem_private_key` / `high_entropy`. */
626
+ kind: string;
627
+ /** 1-based line of the literal, or `0` when unknown. */
628
+ line: number;
629
+ /** Redacted preview of the literal (first few chars + length) for the finding detail — never the full secret. */
630
+ preview: string;
631
+ }
632
+ /**
633
+ * One `ctx.sql.query(text, …)` / `ctx.sql.unsafe(text, …)` call whose `text`
634
+ * argument is built in place rather than passed as a fixed statement — the
635
+ * `sql_injection_risk` lint input. The Hyperdrive driver binds ONLY the `params`
636
+ * array; the `text` string is spliced verbatim into the SQL, so a `text` assembled
637
+ * from a string concatenation or a substitution template literal is an injection
638
+ * vector. A fixed string literal / no-substitution template is safe. Structurally
639
+ * identical to `AdvisorSqlInterpolation`.
640
+ */
641
+ interface SqlInterpolationIR {
642
+ /** Export binding name of the procedure performing the `ctx.sql` call. */
643
+ exportName: string;
644
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
645
+ file: string;
646
+ /** 1-based line of the interpolation, or `0` when unknown. */
647
+ line: number;
648
+ }
649
+ /**
650
+ * One discovered `httpRoute.&lt;verb>("/admin/…")` route on an admin/privileged-looking
651
+ * path, with whether its builder chain references an auth/admin guard — the
652
+ * `admin_route_without_guard` lint input. Structurally identical to
653
+ * `AdvisorAdminRoute`.
654
+ */
655
+ interface AdminRouteIR {
656
+ /** Export binding name of the route handler. */
657
+ exportName: string;
658
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
659
+ file: string;
660
+ /** HTTP verb the route binds to (uppercased), e.g. `"POST"`. */
661
+ method: string;
662
+ /** The route path, e.g. `/admin/users`. */
663
+ path: string;
664
+ /** `true` when the handler body references an auth/session/admin guard (`ctx.auth`, `getSession`, `requireAdmin`, …). */
665
+ usesGuard: boolean;
666
+ }
667
+ interface ProjectIR {
668
+ crons: ReadonlyArray<CronJobIR>;
669
+ functions: ReadonlyArray<FunctionIR>;
670
+ /** Typed REST routes discovered from `httpRoute.&lt;verb>(...)` builder chains. */
671
+ httpRoutes: ReadonlyArray<HttpRouteIR>;
672
+ migrations: ReadonlyArray<MigrationIR>;
673
+ schema: SchemaIR;
674
+ }
675
+ /**
676
+ * Run the static lints against a discovered {@link SchemaIR} and the reads/writes/calls
677
+ * found in function bodies: query reads feed `filter_without_index`, insert writes
678
+ * feed `table_without_insert`, authApi calls feed `auth_api_call_without_headers`,
679
+ * rls procedure snapshots feed `rls_uncovered_table`, and mask procedure
680
+ * snapshots feed `mask_uncovered_pii_column`; declared containers
681
+ * feed the `container_*` lints; declared workflows + `ctx.workflows.get(...)` call
682
+ * sites feed the `workflow_unused` / `workflow_unknown_target` lints; non-deterministic
683
+ * calls inside query/mutation handlers feed the `nondeterministic_query_mutation` lint
684
+ * (all default empty for callers that don't analyze functions/containers/workflows).
685
+ * The IR types are structurally identical to the advisor's evidence types so they
686
+ * pass straight through without conversion. Returns the findings; surfacing them
687
+ * (console, error overlay, studio Advisors table) is the caller's choice.
688
+ */
689
+ declare const lintSchema: (schema: SchemaIR, queries?: ReadonlyArray<QueryReadIR>, inserts?: ReadonlyArray<InsertWriteIR>, authApiCalls?: ReadonlyArray<AuthApiCallIR>, rlsProcedures?: ReadonlyArray<RlsProcedureIR>, containers?: ReadonlyArray<ContainerIR>, workflows?: ReadonlyArray<WorkflowIR>, workflowCalls?: ReadonlyArray<WorkflowCallIR>, maskProcedures?: ReadonlyArray<MaskProcedureIR>, nondeterministicCalls?: ReadonlyArray<NondeterministicCallIR>, procedureProtections?: ReadonlyArray<ProcedureMiddlewareIR>, argumentValidators?: ReadonlyArray<ArgumentValidatorIR>, secretLiterals?: ReadonlyArray<SecretLiteralIR>, sqlInterpolations?: ReadonlyArray<SqlInterpolationIR>, adminRoutes?: ReadonlyArray<AdminRouteIR>) => Finding[];
690
+ /**
691
+ * Render advisor findings as a single multi-line string for console surfacing:
692
+ * a one-line summary header followed by one `[LEVEL] name: detail` line per
693
+ * finding. Returns `""` when there are no findings.
694
+ */
695
+ declare const formatAdvisories: (findings: ReadonlyArray<Finding>) => string;
696
+ /**
697
+ * An error thrown by codegen discovery when the user's schema or function
698
+ * source has a structural problem that can be pinpointed to a specific source
699
+ * location. The `file`, `line`, and `column` properties mirror what Vite's
700
+ * error-overlay `loc` field expects so the browser can display the exact spot.
701
+ */
702
+ declare class CodegenDiagnosticError extends Error {
703
+ readonly column: number;
704
+ readonly file: string;
705
+ readonly line: number;
706
+ constructor(message: string, file: string, line: number, column: number);
707
+ }
708
+ /**
709
+ * Build a {@link CodegenDiagnosticError} whose message includes the source
710
+ * location and whose `file`/`line`/`column` properties are set from the
711
+ * ts-morph `Node`'s position in its source file.
712
+ *
713
+ * Message format: `@lunora/codegen: &lt;detail> (&lt;file>:&lt;line>:&lt;column>)`
714
+ */
715
+ declare const diagnosticAt: (node: Node, detail: string) => CodegenDiagnosticError;
716
+ /**
717
+ * Discover `ctx.authApi.&lt;method>(...)` (and bare `authApi.&lt;method>(...)`) calls
718
+ * under the lunora source directory and attribute each to the exported function
719
+ * (and file) performing it. Calls outside an exported declaration are dropped.
720
+ */
721
+ declare const discoverAuthApiCalls: (project: Project, lunoraDirectory: string) => AuthApiCallIR[];
722
+ /** The only file containers may be declared in — mirrors `lunora/crons.ts`. */
723
+ declare const CONTAINERS_FILENAME = "containers.ts";
724
+ /**
725
+ * Discover every container the project declares: exported `defineContainer()`
726
+ * calls in `lunora/containers.ts`. Returns `[]` when the file doesn't exist.
727
+ * Wrangler-relevant fields (`image`, `instanceType`, `maxInstances`, `name`)
728
+ * must be static literals; runtime-only fields (`env`, `sleepAfter`, …) may be
729
+ * any expression since the generated class imports the definition object.
730
+ */
731
+ declare const discoverContainers: (project: Project, lunoraDirectory: string) => ContainerIR[];
732
+ /**
733
+ * Scan every `.ts` file under `lunoraDir` for `cronJobs()` builder registrations
734
+ * (`crons.interval(...)`, `crons.daily(...)`, `crons.cron(...)`, …) and lift them
735
+ * into {@link CronJobIR}. Schedules are compiled to standard cron expressions;
736
+ * function references are resolved to their `namespace:fn` dispatch path, while a
737
+ * bare identifier naming a declared workflow (`workflows`) resolves to a durable
738
+ * workflow start. Names must be unique across the project.
739
+ */
740
+ declare const discoverCrons: (project: Project, lunoraDirectory: string, workflows?: ReadonlyArray<WorkflowIR>) => CronJobIR[];
741
+ /**
742
+ * Scan all .ts files under `lunoraDir` (skipping `_generated/` and `schema.ts`)
743
+ * for top-level `export const x = query/mutation/action({...})` registrations.
744
+ */
745
+ declare const discoverFunctions: (project: Project, lunoraDirectory: string) => FunctionIR[];
746
+ /**
747
+ * Scan all `.ts` files under `lunoraDir` (skipping `_generated/` and `schema.ts`)
748
+ * for `export const x = httpRoute.&lt;verb>(...)…handler(...)` typed REST routes.
749
+ * These are the headline OpenAPI target: each becomes a real `paths` entry.
750
+ */
751
+ declare const discoverHttpRoutes: (project: Project, lunoraDirectory: string) => HttpRouteIR[];
752
+ /**
753
+ * Discover `ctx.db.insert("table", …)` writes under the lunora source directory
754
+ * and attribute each to the exported function (and file) performing it. Calls
755
+ * with a non-literal table argument, or outside an exported declaration, are
756
+ * dropped (`table === ""` / no enclosing export).
757
+ */
758
+ declare const discoverInserts: (project: Project, lunoraDirectory: string) => InsertWriteIR[];
759
+ /**
760
+ * Discover masking usage for every exported Lunora procedure under the lunora
761
+ * source directory — the column-level twin of `discoverRlsProcedures`. For each
762
+ * procedure, records whether its builder chain includes `.use(mask(...))`, which
763
+ * `(table, column)` pairs that mask declares, and which tables it reads/writes
764
+ * through `ctx.db`. Feeds the `mask_uncovered_pii_column` advisor lint.
765
+ */
766
+ declare const discoverMaskProcedures: (project: Project, lunoraDirectory: string) => MaskProcedureIR[];
767
+ /**
768
+ * Aggregate the schema-wide masking metadata the studio's data-browser mask
769
+ * toggle reads: every statically-discovered `(table, column, strategy)` masked
770
+ * column across the project's `.use(mask(...))` chains. Walks the same builder
771
+ * chains as {@link discoverMaskProcedures} but carries the strategy the preview
772
+ * needs to choose redact-vs-hash-vs-custom rendering. Deduped by `(table,
773
+ * column)` with the first declaration winning, so a column masked by several
774
+ * procedures lists once — the same evidence the advisor lint uses.
775
+ */
776
+ /**
777
+ * Scan all `.ts` files under `lunoraDir` for top-level
778
+ * `export const x = defineMigration({...})` declarations and lift them into
779
+ * {@link MigrationIR}. `id` must be a static string literal (it's the registry
780
+ * key); `table` is best-effort and left `""` when not a literal.
781
+ */
782
+ declare const discoverMigrations: (project: Project, lunoraDirectory: string) => MigrationIR[];
783
+ /**
784
+ * Discover non-deterministic API calls (`Date.now`, `new Date()`, `Date()`,
785
+ * `Math.random`, `crypto.randomUUID`, `crypto.getRandomValues` — including
786
+ * `globalThis`/`self`/`window`-prefixed receivers — and `fetch`) lexically inside
787
+ * the handler body of every exported `query(...)` / `mutation(...)` registration
788
+ * under the lunora source directory — the `nondeterministic_query_mutation` lint
789
+ * input. `action(...)` (and `stream(...)`) registrations are intentionally
790
+ * skipped: actions run exactly once and may use ambient APIs freely.
791
+ *
792
+ * Traversal is scoped to the handler node (not the whole declaration), mirroring
793
+ * how the auth-api / insert feeders attribute calls — so a call in a sibling
794
+ * helper outside the handler, or in a nested `action(...)` passed elsewhere, is
795
+ * not attributed to the query/mutation. One {@link NondeterministicCallIR} is
796
+ * produced per call site.
797
+ */
798
+ declare const discoverNondeterministicCalls: (project: Project, lunoraDirectory: string) => NondeterministicCallIR[];
799
+ /**
800
+ * Discover `ctx.db.query("table")…` reads under the lunora source directory and
801
+ * reduce each to a {@link QueryReadIR}. Only reads that call `.filter()` are
802
+ * returned — an unfiltered read is never a `filter_without_index` candidate, so
803
+ * dropping the rest keeps the lint input small.
804
+ */
805
+ declare const discoverQueries: (project: Project, lunoraDirectory: string) => QueryReadIR[];
806
+ declare const discoverRlsProcedures: (project: Project, lunoraDirectory: string) => RlsProcedureIR[];
807
+ /**
808
+ * Aggregate the schema-wide RLS metadata the studio's read-only inspector reads:
809
+ * every statically-discovered `(table, on, procedure)` policy entry plus every
810
+ * role declared via `rls(policies, { roles })`. Walks the same builder chains as
811
+ * {@link discoverRlsProcedures} but extracts the richer `{ on }` operation +
812
+ * role/permission shape rather than the lint's table-name set.
813
+ *
814
+ * Only the **builder** form (`c.use(rls(...)).query(...)`) can declare policies,
815
+ * so bare-factory procedures contribute nothing. The `when` predicate is never
816
+ * read — it's an opaque JS closure whose logic belongs in code, not the UI.
817
+ * Roles are deduped by name (first declaration wins) so a role registered on
818
+ * several procedures lists once.
819
+ */
820
+ declare const discoverRlsMetadata: (project: Project, lunoraDirectory: string) => RlsMetadataIR;
821
+ /**
822
+ * Load `&lt;projectRoot>/lunora/schema.ts`, find `defineSchema({...})`, and
823
+ * return a structural IR. Throws if the file or call cannot be found.
824
+ */
825
+ declare const discoverSchema: (project: Project, schemaPath: string) => SchemaIR;
826
+ /**
827
+ * Aggregate the schema-wide storage-rule metadata the studio's inspector reads:
828
+ * every statically-discovered `(bucket, on, prefix, procedure)` entry across all
829
+ * `.use(storageRules(...))` chains. Only the builder form can declare rules, so
830
+ * bare-factory procedures contribute nothing.
831
+ */
832
+ declare const discoverStorageRulesMetadata: (project: Project, lunoraDirectory: string) => StorageRulesMetadataIR;
833
+ /** The only file workflows may be declared in — mirrors `lunora/containers.ts`. */
834
+ declare const WORKFLOWS_FILENAME = "workflows.ts";
835
+ /**
836
+ * Discover every workflow the project declares: exported `defineWorkflow()`
837
+ * calls in `lunora/workflows.ts`. Returns `[]` when the file doesn't exist. The
838
+ * only wrangler-relevant literal is the optional `name` override; the workflow
839
+ * body is runtime-only, so codegen never evaluates it.
840
+ */
841
+ declare const discoverWorkflows: (project: Project, lunoraDirectory: string) => WorkflowIR[];
842
+ declare const GENERATED_HEADER = "// GENERATED by @lunora/codegen — do not edit.\n// Run `lunora codegen` to regenerate.\n\n";
843
+ /** Emit `_generated/dataModel.ts` — `Doc&lt;"name">` + `Id&lt;"name">` for every table. */
844
+ declare const emitDataModel: (schema: SchemaIR, useUmbrella?: boolean) => string;
845
+ /**
846
+ * Emit `_generated/api.ts` — the typed `api.*` registry (public functions), the
847
+ * `internal.*` registry, and (when the project declares workflows) the typed
848
+ * `workflows.*` reference object. `api`/`internal` are the same `anyApi` proxy
849
+ * at runtime (the `__lunoraRef` is identical); visibility is enforced
850
+ * server-side at dispatch, not in the reference. Splitting the *types* keeps
851
+ * internal functions off the client-facing `api` surface.
852
+ */
853
+ declare const emitApi: (functions: ReadonlyArray<FunctionIR>, workflows?: ReadonlyArray<WorkflowIR>, useUmbrella?: boolean) => string;
854
+ /**
855
+ * Emit `_generated/seed.ts` — a project-bound `createSeedClient` with this
856
+ * schema's `InsertModel` and runtime schema pre-applied, so a test or script
857
+ * calls `createSeedClient({ seed: 1 }).users(5)` with full column types and no
858
+ * manual wiring. The runtime schema is the default export of `lunora/schema.ts`
859
+ * (the same import the generated ShardDO uses).
860
+ *
861
+ * Returns `""` when `@lunora/seed` is not a declared dependency, so projects
862
+ * that don't use it keep a clean `_generated/` and never import the package.
863
+ */
864
+ interface EmitServerOptions {
865
+ containers?: ReadonlyArray<ContainerIR>;
866
+ hasAi?: boolean;
867
+ /** A `lunora/` source uses `@lunora/analytics` / `ctx.analytics` — wires the write helper onto every ctx. */
868
+ hasAnalytics?: boolean;
869
+ /** A `lunora/` source uses `@lunora/browser` / `ctx.browser` — wires `ctx.browser` onto ActionCtx only. */
870
+ hasBrowser?: boolean;
871
+ /** A `lunora/` source uses `@lunora/hyperdrive` / `ctx.sql` — wires `ctx.sql` onto ActionCtx only. */
872
+ hasHyperdrive?: boolean;
873
+ /** A `lunora/` source uses `@lunora/images` / `ctx.images` — wires `ctx.images` onto ActionCtx only. */
874
+ hasImages?: boolean;
875
+ /** A `lunora/` source uses `@lunora/kv` / `ctx.kv` — wires `ctx.kv` onto every ctx. */
876
+ hasKv?: boolean;
877
+ hasPayments?: boolean;
878
+ /** A `lunora/` source uses `@lunora/pipelines` / `ctx.pipelines` — wires `ctx.pipelines` onto ActionCtx only. */
879
+ hasPipelines?: boolean;
880
+ schema?: SchemaIR;
881
+ storageRuleBuckets?: ReadonlyArray<string>;
882
+ /** The project depends on the `lunora` umbrella — import base packages via its subpaths. */
883
+ useUmbrella?: boolean;
884
+ workflows?: ReadonlyArray<WorkflowIR>;
885
+ }
886
+ declare const emitServer: ({
887
+ containers,
888
+ hasAi,
889
+ hasAnalytics,
890
+ hasBrowser,
891
+ hasHyperdrive,
892
+ hasImages,
893
+ hasKv,
894
+ hasPayments,
895
+ hasPipelines,
896
+ schema,
897
+ storageRuleBuckets,
898
+ useUmbrella,
899
+ workflows
900
+ }?: EmitServerOptions) => string;
901
+ declare const emitFunctions: (functions: ReadonlyArray<FunctionIR>, migrations?: ReadonlyArray<MigrationIR>) => string;
902
+ /**
903
+ * Storage-column map per table for the file browser: `{ table: [field, …] }` for
904
+ * every field declared `v.storage(...)` (unwrapping `v.optional(...)`). The
905
+ * generated shard hands this to the base `storageColumns` hook so the admin
906
+ * `storageReferences` read can join R2 objects back to the rows that own them
907
+ * (and flag orphans — objects no row references). Only scalar storage columns
908
+ * are emitted; an array-of-storage field can't be matched by an equality scan,
909
+ * so it is skipped here.
910
+ */
911
+ /**
912
+ * Emit `_generated/containers.ts` — one container-enabled Durable Object class
913
+ * per `defineContainer` export, each a thin subclass of `LunoraContainer`
914
+ * (`@lunora/container/do`) constructed with the user's definition object. The
915
+ * worker entry must re-export these classes: wrangler requires every
916
+ * `containers[].class_name` to be exported by the deployed worker. Returns ""
917
+ * when the project declares no containers (the file is not written then).
918
+ */
919
+ declare const emitContainers: (containers: ReadonlyArray<ContainerIR>) => string;
920
+ /**
921
+ * Emit `_generated/workflows.ts` — one `WorkflowEntrypoint` class per
922
+ * `defineWorkflow` export, each a thin subclass of `LunoraWorkflow`
923
+ * (`@lunora/workflow/do`) constructed with the user's definition object. The
924
+ * worker entry must re-export these classes: wrangler requires every
925
+ * `workflows[].class_name` to be exported by the deployed worker. Returns ""
926
+ * when the project declares no workflows (the file is not written then).
927
+ */
928
+ declare const emitWorkflows: (workflows: ReadonlyArray<WorkflowIR>) => string;
929
+ interface EmitShardOptions {
930
+ advisories?: ReadonlyArray<Finding>;
931
+ containers?: ReadonlyArray<ContainerIR>;
932
+ hasAi?: boolean;
933
+ /** A `lunora/` source reads `ctx.analytics` — wires the Analytics Engine write helper onto every ctx. */
934
+ hasAnalytics?: boolean;
935
+ /** A `lunora/` source reads `ctx.browser` — wires `ctx.browser` onto the ActionCtx only. */
936
+ hasBrowser?: boolean;
937
+ /** A `lunora/` source reads `ctx.sql` (Hyperdrive) — wires `ctx.sql` onto the ActionCtx only. */
938
+ hasHyperdrive?: boolean;
939
+ /** A `lunora/` source reads `ctx.images` — wires `ctx.images` onto the ActionCtx only. */
940
+ hasImages?: boolean;
941
+ /** A `lunora/` source reads `ctx.kv` — wires `ctx.kv` onto every ctx. */
942
+ hasKv?: boolean;
943
+ hasPayments?: boolean;
944
+ maskMetadata?: MaskMetadataIR;
945
+ rlsMetadata?: RlsMetadataIR;
946
+ schema: SchemaIR;
947
+ storageRules?: StorageRulesMetadataIR;
948
+ studioFeatures?: StudioFeaturesResult;
949
+ /** The project depends on the `lunora` umbrella — import base packages via its subpaths. */
950
+ useUmbrella?: boolean;
951
+ workflows?: ReadonlyArray<WorkflowIR>;
952
+ }
953
+ declare const emitShard: ({
954
+ advisories,
955
+ containers,
956
+ hasAi,
957
+ hasAnalytics,
958
+ hasBrowser,
959
+ hasHyperdrive,
960
+ hasImages,
961
+ hasKv,
962
+ hasPayments,
963
+ maskMetadata,
964
+ rlsMetadata,
965
+ schema,
966
+ storageRules,
967
+ studioFeatures,
968
+ useUmbrella,
969
+ workflows
970
+ }: EmitShardOptions) => string;
971
+ /**
972
+ * Emit drizzle `sqliteTable` definitions for the project schema, split into
973
+ * `global` (D1-backed) and `shard` (DO-SQLite-backed) buckets. Tables marked
974
+ * `.global()` go in the global file; everything else (default root + `.shardBy()`)
975
+ * goes in the shard file.
976
+ *
977
+ * `searchIndexes` are intentionally not emitted — drizzle has no `sqliteTable`
978
+ * abstraction for FTS5 virtual tables. FTS plumbing is handled by the runtime
979
+ * outside of drizzle.
980
+ */
981
+ declare const emitDrizzleSchema: (schema: SchemaIR, useUmbrella?: boolean) => {
982
+ global: string;
983
+ shard: string;
984
+ };
985
+ /**
986
+ * Emit `_generated/crons.ts` from the discovered cron jobs.
987
+ *
988
+ * `LUNORA_CRON_TRIGGERS` is the deduplicated schedule array — what lands in
989
+ * wrangler's `triggers.crons` (the vite plugin reconciles it into
990
+ * `wrangler.jsonc`, and {@link emitWranglerCronTriggers} renders the same list
991
+ * for the CLI / docs).
992
+ *
993
+ * `LUNORA_CRONS` is the dispatcher map keyed by cron expression, each value a
994
+ * list of `{ name, functionPath, args }`. Cloudflare's `scheduled()` handler
995
+ * receives only the cron string, so multiple jobs sharing one expression must
996
+ * all fire — hence a list per key rather than a single entry.
997
+ *
998
+ * Jobs arrive pre-sorted by name (deterministic output); the trigger array
999
+ * preserves first-seen order of distinct expressions.
1000
+ *
1001
+ * Cloudflare caps a Worker at **3 Cron Triggers** (i.e. 3 distinct cron
1002
+ * expressions). Because the dispatcher fires every job sharing an expression,
1003
+ * many jobs can ride a single trigger — only the count of *distinct* schedules
1004
+ * matters. `lunora codegen` warns when that count exceeds the limit; for
1005
+ * finer-grained scheduling use Durable Object alarms (`@lunora/scheduler`),
1006
+ * which have no such cap.
1007
+ */
1008
+ declare const emitCrons: (crons: ReadonlyArray<CronJobIR>) => string;
1009
+ /**
1010
+ * Render `_generated/vectors.ts` — the static registry of every vector index
1011
+ * declared in `schema.ts` (inline `.vectorize()` columns + standalone
1012
+ * `defineVectorIndex()` definitions).
1013
+ *
1014
+ * Cloudflare Vectorize exposes no way to enumerate an account's indexes at
1015
+ * runtime — a binding can `describe()` itself but the worker can't ask "which
1016
+ * indexes exist". So this generated array is the source of truth the studio's
1017
+ * vector browser lists, and the worker's admin route pairs each entry with its
1018
+ * live `describe()` stats. Entries are sorted by name for deterministic output.
1019
+ */
1020
+ declare const emitVectors: (vectorIndexes: ReadonlyArray<VectorIndexIR>) => string;
1021
+ /**
1022
+ * Render the deduplicated cron schedules as a JSON fragment suitable for
1023
+ * splicing into `wrangler.jsonc`'s `triggers.crons`. The vite plugin uses this
1024
+ * (plus the parsed wrangler config) to reconcile generated triggers without the
1025
+ * user hand-editing the file.
1026
+ */
1027
+ declare const emitWranglerCronTriggers: (crons: ReadonlyArray<CronJobIR>) => string[];
1028
+ /** Which capability methods the generated `defineApp` builder exposes — one flag per package-backed feature the app actually uses. */
1029
+ interface EmitAppOptions {
1030
+ /** App uses `@lunora/ai` / `ctx.ai` → emit `.ai()` (override the Workers AI binding backing `ctx.ai`). */
1031
+ hasAi: boolean;
1032
+ /** App uses `@lunora/analytics` / `ctx.analytics` → emit `.analytics()` (override the dataset backing `ctx.analytics`). */
1033
+ hasAnalytics: boolean;
1034
+ /** App depends on `@lunora/auth` → emit `.auth()` + the lazy build/migrate dance. */
1035
+ hasAuth: boolean;
1036
+ /** App uses `@lunora/browser` / `ctx.browser` → emit `.browser()`. */
1037
+ hasBrowser: boolean;
1038
+ /** App depends on a worker-composition framework adapter (`@lunora/astro`/`@lunora/svelte`/`@lunora/vue`) → emit `.buildFrameworkWorker(host)`. */
1039
+ hasFramework: boolean;
1040
+ /** Schema declares **D1-backed** `.global()` tables → emit `.global()` (D1 ctx-db + studio introspector + cross-shard relations). */
1041
+ hasGlobal: boolean;
1042
+ /** App uses `@lunora/hyperdrive` / `ctx.sql` → emit `.hyperdrive()`. */
1043
+ hasHyperdrive: boolean;
1044
+ /** Schema declares **Hyperdrive-backed** `.global({ backend: "hyperdrive" })` tables → emit `.hyperdriveGlobal()` (reactive Postgres/MySQL ctx-db over Hyperdrive). */
1045
+ hasHyperdriveGlobal: boolean;
1046
+ /** App uses `@lunora/images` / `ctx.images` → emit `.images()`. */
1047
+ hasImages: boolean;
1048
+ /** App uses `@lunora/kv` / `ctx.kv` → emit `.kv()`. */
1049
+ hasKv: boolean;
1050
+ /** App uses `@lunora/payment` / `ctx.payments` → emit `.payment()`. */
1051
+ hasPayments: boolean;
1052
+ /** App imports `@lunora/scheduler` / declares crons → emit `.scheduler()`. */
1053
+ hasScheduler: boolean;
1054
+ /** App uses `@lunora/storage` → emit `.storage()` (DO `ctx.storage` + studio file browser). */
1055
+ hasStorage: boolean;
1056
+ /** Schema declares vector indexes → emit `.vectors()` (the Vectorize index map backing `ctx.vectors`). */
1057
+ hasVectors: boolean;
1058
+ /** App declares Cloudflare Workflows (`defineWorkflow`) → wire `options.workflowsClient` so the studio's workflow-instance proxy can reach the CF REST API. */
1059
+ hasWorkflow: boolean;
1060
+ /** Project depends on the unscoped `lunorash` umbrella → import the runtime via `lunorash/runtime` instead of `@lunora/runtime`. */
1061
+ useUmbrella: boolean;
1062
+ /** An OpenAPI spec is emitted (`openapi.ts`) → wire `openApiSpec` into the worker. */
1063
+ wantsOpenApi: boolean;
1064
+ /** An OpenRPC spec is emitted (`openrpc.ts`) → wire `openRpcSpec` into the worker. */
1065
+ wantsOpenRpc: boolean;
1066
+ }
1067
+ /**
1068
+ * Emit `_generated/app.ts` — a fluent, feature-specialized worker-composition
1069
+ * builder. Only the methods for capabilities THIS app uses are emitted, so the
1070
+ * builder's type surface (IntelliSense) lists exactly what can be configured.
1071
+ *
1072
+ * Each capability declaration is fanned into BOTH runtime surfaces: the DO-side
1073
+ * `createShardDO(...)` factory that backs `ctx.*`, and the worker-side
1074
+ * `createWorker(...)` options that back the studio/admin endpoints — so storage
1075
+ * / scheduler / global are declared once instead of twice. The builder is pure
1076
+ * sugar over the public `createWorker` / `createShardDO`; both stay usable.
1077
+ *
1078
+ * Lives in generated code (not `@lunora/runtime`, which is dependency-free) so
1079
+ * it can import the add-on packages the app installed (`@lunora/auth`,
1080
+ * `@lunora/storage`, …) directly.
1081
+ */
1082
+ declare const emitApp: (options: EmitAppOptions) => string;
1083
+ /** Inputs the OpenAPI emitter needs from a codegen run. */
1084
+ interface OpenApiEmitInput {
1085
+ functions: ReadonlyArray<FunctionIR>;
1086
+ httpRoutes: ReadonlyArray<HttpRouteIR>;
1087
+ /** `info.version`; defaults to `"0.0.0"` with a TODO when the project version is unknown. */
1088
+ version?: string;
1089
+ }
1090
+ /**
1091
+ * Emit an OpenAPI 3.1.0 document covering both Lunora function surfaces.
1092
+ *
1093
+ * `httpRouter()` typed REST routes become real `paths` keyed by their method +
1094
+ * URL, with query/path parameters and JSON request bodies derived from their
1095
+ * `v.*` validators, and a response schema from `.output()` when declared.
1096
+ *
1097
+ * RPC `query`/`mutation`/`action` functions become one operation each on
1098
+ * `POST /_lunora/rpc` (disambiguated by a `#functionPath` path fragment), with a
1099
+ * requestBody pinning `functionPath` + typed `args`. `internal`/`stream`
1100
+ * functions are excluded (unreachable / not invocable on the external RPC path).
1101
+ *
1102
+ * Operations are grouped into `tags` by file namespace, and every operation
1103
+ * references a reusable `LunoraError` error-response component enumerating the
1104
+ * standard error codes. Borrows oRPC's per-procedure-operation + tag-grouping +
1105
+ * internal-filtering structure; the JSON Schema dialect matches `@lunora/values`
1106
+ * (Draft 2020-12). Returns the document as a plain object (the single source of
1107
+ * truth `emitOpenApi` stringifies and `emitOpenApiModule` inlines, so the
1108
+ * `.json` and `.ts` artifacts can never drift).
1109
+ */
1110
+ declare const buildOpenApiDocument: (input: OpenApiEmitInput) => Record<string, unknown>;
1111
+ /**
1112
+ * Emit the OpenAPI 3.1 document as a pretty-printed JSON string
1113
+ * (`_generated/openapi.json`) — the portable artifact for external tooling.
1114
+ */
1115
+ declare const emitOpenApi: (input: OpenApiEmitInput) => string;
1116
+ /**
1117
+ * Emit the OpenAPI document as an importable TS module
1118
+ * (`_generated/openapi.ts`) the worker entry imports and passes to
1119
+ * `createWorker({ openApiSpec })`. The document object literal is inlined
1120
+ * verbatim (same `JSON.stringify` form the `.json` uses), so the `.ts` and
1121
+ * `.json` are byte-identical content and regenerate together — closing the gap
1122
+ * where a Worker cannot read the JSON file at runtime. `document_` is the object
1123
+ * returned by {@link buildOpenApiDocument} (reused, never recomputed).
1124
+ */
1125
+ declare const emitOpenApiModule: (document_: Record<string, unknown>) => string;
1126
+ /** The OpenRPC dialect version this emitter targets. */
1127
+ declare const OPENRPC_VERSION = "1.3.2";
1128
+ /** Inputs the OpenRPC emitter needs from a codegen run. */
1129
+ interface OpenRpcEmitInput {
1130
+ functions: ReadonlyArray<FunctionIR>;
1131
+ /** `info.version`; defaults to `"0.0.0"` with a TODO when the project version is unknown. */
1132
+ version?: string;
1133
+ }
1134
+ /**
1135
+ * Emit an OpenRPC 1.x document describing Lunora's JSON-RPC surface.
1136
+ *
1137
+ * Only the RPC `query`/`mutation`/`action` functions become `methods` — one per
1138
+ * function, `name` = `file:fn`. `internal` (off the external RPC path) and
1139
+ * `stream` (not invocable over the RPC envelope) are excluded, the same filter
1140
+ * the OpenAPI emitter applies. Each method's single `args` param is typed from
1141
+ * the function's `v.*` validators (`argsObjectSchema`); `result` is the
1142
+ * `.output()` schema when declared, else a best-effort inferred schema. The
1143
+ * standard `LunoraError` codes ride along under each method's `errors`.
1144
+ *
1145
+ * `httpRouter()` typed REST routes are deliberately omitted — OpenRPC is
1146
+ * RPC-only and cannot represent REST paths; the OpenAPI document is the spec
1147
+ * that covers the REST surface. Methods are sorted by name for stable output.
1148
+ * Returns the document as a plain object (the single source of truth
1149
+ * `emitOpenRpc` stringifies and `emitOpenRpcModule` inlines, so the `.json` and
1150
+ * `.ts` artifacts can never drift).
1151
+ */
1152
+ declare const buildOpenRpcDocument: (input: OpenRpcEmitInput) => Record<string, unknown>;
1153
+ /**
1154
+ * Emit the OpenRPC 1.x document as a pretty-printed JSON string
1155
+ * (`_generated/openrpc.json`) — the portable artifact for external tooling.
1156
+ */
1157
+ declare const emitOpenRpc: (input: OpenRpcEmitInput) => string;
1158
+ /**
1159
+ * Emit the OpenRPC document as an importable TS module
1160
+ * (`_generated/openrpc.ts`) the worker entry imports and passes to
1161
+ * `createWorker({ openRpcSpec })`. The document object literal is inlined
1162
+ * verbatim (same `JSON.stringify` form the `.json` uses), so the `.ts` and
1163
+ * `.json` are byte-identical content and regenerate together. `document_` is
1164
+ * the object returned by {@link buildOpenRpcDocument} (reused, never recomputed).
1165
+ */
1166
+ declare const emitOpenRpcModule: (document_: Record<string, unknown>) => string;
1167
+ /** Current snapshot format version. Bumped if the structural shape below changes. */
1168
+ declare const SCHEMA_SNAPSHOT_VERSION: 1;
1169
+ /** A single field's structural shape: its value kind and whether it is optional. */
1170
+ interface FieldSnapshot {
1171
+ /** The validator kind (`string`, `number`, `id`, `object`, …) after unwrapping `v.optional`. */
1172
+ kind: string;
1173
+ /** True when declared `v.optional(...)` — accepts `undefined` / absent on insert. */
1174
+ optional: boolean;
1175
+ }
1176
+ /** A single secondary index's structural shape. */
1177
+ interface IndexSnapshot {
1178
+ fields: ReadonlyArray<string>;
1179
+ unique: boolean;
1180
+ }
1181
+ /** A single relation's structural shape. */
1182
+ interface RelationSnapshot {
1183
+ field: string;
1184
+ kind: "many" | "one";
1185
+ table: string;
1186
+ }
1187
+ /** Structural snapshot of one table. */
1188
+ interface TableSnapshot {
1189
+ /** Field name → {@link FieldSnapshot}, in declared order. */
1190
+ fields: Record<string, FieldSnapshot>;
1191
+ /** Index name → {@link IndexSnapshot}. */
1192
+ indexes: Record<string, IndexSnapshot>;
1193
+ /** Relation accessor name → {@link RelationSnapshot}. */
1194
+ relations: Record<string, RelationSnapshot>;
1195
+ /**
1196
+ * `"root"` (default single-DO), `"global"` (D1-replicated), or
1197
+ * `"shardBy:&lt;field>"` (partitioned). Encoded as a string so the snapshot
1198
+ * stays a plain JSON-stable value.
1199
+ */
1200
+ shardMode: string;
1201
+ }
1202
+ /** The committed baseline — a deterministic structural view of the whole schema. */
1203
+ interface SchemaSnapshot {
1204
+ /** Sorted list of every declared `defineMigration` id at capture time. */
1205
+ migrationIds: ReadonlyArray<string>;
1206
+ /** Table name → {@link TableSnapshot}, keys sorted for stable serialization. */
1207
+ tables: Record<string, TableSnapshot>;
1208
+ version: typeof SCHEMA_SNAPSHOT_VERSION;
1209
+ }
1210
+ /** One classified structural change between the baseline and the current schema. */
1211
+ interface DriftChange {
1212
+ /** `"breaking"` changes need a data migration; `"safe"` changes are additive. */
1213
+ severity: "breaking" | "safe";
1214
+ /** Human-readable, actionable description (used in the gate message). */
1215
+ summary: string;
1216
+ /** A machine-readable change discriminator. */
1217
+ type: "addedIndex" | "addedOptionalField" | "addedRelation" | "addedRequiredField" | "addedTable" | "changedFieldKind" | "changedIndex" | "changedShardMode" | "fieldOptionalToRequired" | "fieldRequiredToOptional" | "removedField" | "removedIndex" | "removedRelation" | "removedTable";
1218
+ }
1219
+ /** The result of diffing two snapshots: every classified change. */
1220
+ interface SchemaDrift {
1221
+ /** Every classified change, in a stable order (added/changed per table, then removals). */
1222
+ changes: ReadonlyArray<DriftChange>;
1223
+ }
1224
+ /**
1225
+ * Build a {@link SchemaSnapshot} from a parsed {@link SchemaIR} and the set of
1226
+ * declared migration ids. Tables and migration ids are sorted so the emitted
1227
+ * JSON is byte-stable across runs (no spurious diffs / churn).
1228
+ */
1229
+ declare const buildSchemaSnapshot: (schema: SchemaIR, migrationIds: ReadonlyArray<string>) => SchemaSnapshot;
1230
+ /** Serialize a snapshot to the exact bytes written to `lunora/.lunora-schema.json` (trailing newline). */
1231
+ declare const serializeSchemaSnapshot: (snapshot: SchemaSnapshot) => string;
1232
+ /**
1233
+ * Thrown by {@link parseSchemaSnapshot} when the baseline file exists but is
1234
+ * malformed (bad JSON / wrong version / invalid table shape). Lets the CLI gate
1235
+ * treat a corrupt baseline as a hard error rather than silently degrading to a
1236
+ * "first capture" that would mask drift and then overwrite the bad file.
1237
+ */
1238
+ declare class SchemaSnapshotParseError extends Error {
1239
+ override readonly name = "SchemaSnapshotParseError";
1240
+ }
1241
+ /**
1242
+ * Parse a committed snapshot file. Returns `undefined` ONLY when the content is
1243
+ * absent/empty; throws {@link SchemaSnapshotParseError} when content is present
1244
+ * but malformed (bad JSON, wrong version, or structurally-invalid tables) so the
1245
+ * caller can distinguish "no baseline yet" (a legitimate first capture) from "a
1246
+ * corrupt baseline" (which must not be silently treated as a first capture).
1247
+ */
1248
+ declare const parseSchemaSnapshot: (content: string | undefined) => SchemaSnapshot | undefined;
1249
+ /**
1250
+ * Diff the current snapshot against a committed baseline and classify every
1251
+ * structural change. Pure — no I/O. When `baseline` is `undefined` (no committed
1252
+ * snapshot yet) there is no drift to report: every table is treated as a fresh
1253
+ * additive `addedTable`, so a first deploy is never blocked.
1254
+ */
1255
+ declare const diffSchemaSnapshots: (baseline: SchemaSnapshot | undefined, current: SchemaSnapshot) => SchemaDrift;
1256
+ /** The decision the pre-deploy gate returns. */
1257
+ interface SchemaDriftDecision {
1258
+ /** True when the deploy must be blocked (breaking drift with no new migration, and no override). */
1259
+ blocked: boolean;
1260
+ /** Every classified change (both severities), for reporting. */
1261
+ changes: ReadonlyArray<DriftChange>;
1262
+ /** Migration ids declared now but absent from the baseline — proof a migration was added. */
1263
+ newMigrationIds: ReadonlyArray<string>;
1264
+ /**
1265
+ * A multi-line, actionable explanation. Always present; empty string when
1266
+ * there is no drift at all. Mirrors the D1-placeholder guard's message style.
1267
+ */
1268
+ reason: string;
1269
+ }
1270
+ /**
1271
+ * Decide whether breaking schema drift should block a deploy.
1272
+ *
1273
+ * Blocks only when the baseline exists (a first-ever capture is never blocking),
1274
+ * there is at least one `breaking` change, no NEW migration id was added since
1275
+ * the baseline, and the `allowDrift` override is not set. Safe-only drift (or
1276
+ * breaking drift accompanied by a new migration id) passes.
1277
+ */
1278
+ declare const evaluateSchemaDrift: (options: {
1279
+ allowDrift?: boolean;
1280
+ baseline: SchemaSnapshot | undefined;
1281
+ current: SchemaSnapshot;
1282
+ }) => SchemaDriftDecision;
1283
+ /**
1284
+ * Committed, tracked baseline file holding the blessed structural schema
1285
+ * snapshot the pre-deploy drift gate diffs against. Lives in `lunora/` (NOT the
1286
+ * gitignored `_generated/`) so it is committed alongside `schema.ts`. Leading
1287
+ * dot keeps it tucked away next to the schema it describes.
1288
+ */
1289
+ declare const SCHEMA_SNAPSHOT_FILENAME = ".lunora-schema.json";
1290
+ /**
1291
+ * Construct the ts-morph `Project` codegen discovers over. Prefers the user's
1292
+ * `tsconfig.json` (when one is found walking up from `lunoraDirectory`) so
1293
+ * cross-file type resolution and path aliases work; falls back to an isolated
1294
+ * project otherwise. This is the exact construction {@link runCodegen} uses
1295
+ * when no `project` is injected — exported so a long-lived caller (the Vite
1296
+ * dev-loop) can build one once and reuse it across runs via
1297
+ * {@link refreshCodegenProject} instead of re-parsing the user's whole TS
1298
+ * program on every save.
1299
+ */
1300
+ declare const createCodegenProject: (lunoraDirectory: string) => Project;
1301
+ /**
1302
+ * Synchronise a reused {@link createCodegenProject} Project with the current
1303
+ * on-disk state of `lunoraDirectory`, so the next {@link runCodegen} sees the
1304
+ * same files a freshly-constructed Project would — without re-parsing the whole
1305
+ * TS program. Adds any on-disk source file the Project doesn't yet have, and
1306
+ * `refreshFromFileSystemSync()`es the ones it does (picking up edits); then
1307
+ * removes Project source files under `lunoraDirectory` that no longer exist on
1308
+ * disk (the classic stale-deleted-file cache bug).
1309
+ *
1310
+ * Files outside `lunoraDirectory` (e.g. those pulled in by the user's tsconfig)
1311
+ * are left untouched — they back type resolution and rarely change in the
1312
+ * dev-loop; a tsconfig change invalidates the whole cached Project upstream.
1313
+ */
1314
+ declare const refreshCodegenProject: (project: Project, lunoraDirectory: string) => void;
1315
+ /**
1316
+ * Top-level codegen entry. Parses `&lt;projectRoot>/lunora/schema.ts` and every
1317
+ * function file under `&lt;projectRoot>/lunora/`, then writes
1318
+ * `_generated/{api,server,dataModel}.ts` next to them.
1319
+ */
1320
+ declare const runCodegen: (options: CodegenOptions) => CodegenResult;
1321
+ interface CodegenOptions {
1322
+ /**
1323
+ * Which machine-readable API spec(s) to emit into `_generated/`.
1324
+ *
1325
+ * `"openapi"` (the default) writes only `openapi.json` (OpenAPI 3.1; covers
1326
+ * both the RPC functions and `httpRouter()` REST routes). `"openrpc"` writes
1327
+ * only `openrpc.json` (OpenRPC 1.x; the RPC functions only — OpenRPC cannot
1328
+ * represent REST routes). `"both"` writes both files; `"none"` writes neither.
1329
+ *
1330
+ * Regardless of the choice, `CodegenResult.generated.openApi` and `.openRpc`
1331
+ * always carry the rendered string (computation is cheap and pure); only the
1332
+ * on-disk write is gated by this option.
1333
+ */
1334
+ apiSpec?: "both" | "none" | "openapi" | "openrpc";
1335
+ /**
1336
+ * When true, run discovery + emit (so any schema/function parse error
1337
+ * surfaces) but skip writing files to `_generated/`. The returned
1338
+ * `outputDirectory` is still the path that *would* have been written.
1339
+ */
1340
+ dryRun?: boolean;
1341
+ /**
1342
+ * Run the static schema advisor (unindexed FKs, …) during codegen.
1343
+ * Defaults to `true`. When `false`, `CodegenResult.advisories` is empty.
1344
+ * Computed regardless of `dryRun`; codegen never prints them — see
1345
+ * {@link CodegenResult.advisories}.
1346
+ */
1347
+ lint?: boolean;
1348
+ /** Override the lunora subdirectory name. Defaults to `"lunora"`. */
1349
+ lunoraDirectory?: string;
1350
+ /**
1351
+ * Reuse a previously-constructed ts-morph {@link Project} instead of building
1352
+ * a fresh one each run. The caller owns refreshing its source files from disk
1353
+ * (see {@link refreshCodegenProject}) — codegen does not re-read changed files
1354
+ * off an injected Project. Built via {@link createCodegenProject} when absent.
1355
+ * Used by the Vite dev-loop to avoid re-parsing the whole TS program on every
1356
+ * save; omit it (CLI one-shot path) to get the default fresh-Project behaviour.
1357
+ */
1358
+ project?: Project;
1359
+ /** Project root containing the `lunora/` directory. */
1360
+ projectRoot: string;
1361
+ /**
1362
+ * Re-bless the committed schema-drift baseline (`lunora/.lunora-schema.json`)
1363
+ * with the current structural snapshot. The baseline is ALWAYS written on
1364
+ * first capture (when the file is absent); set this to overwrite an existing
1365
+ * one — e.g. after the developer has added the data migration that justifies
1366
+ * a breaking change. Ignored when `dryRun` is true.
1367
+ */
1368
+ updateSchemaBaseline?: boolean;
1369
+ }
1370
+ interface CodegenResult {
1371
+ /**
1372
+ * Static schema advisor findings (e.g. unindexed foreign keys) produced
1373
+ * this run. Empty when `lint` is `false` or the schema is clean. Codegen
1374
+ * does not print these itself — each caller presents them through its own
1375
+ * channel (the CLI logger, the vite overlay, the studio Advisors table).
1376
+ * `formatAdvisories` is exported for a plain multi-line rendering.
1377
+ */
1378
+ advisories: ReadonlyArray<Finding>;
1379
+ /**
1380
+ * Containers discovered from `defineContainer` exports in
1381
+ * `lunora/containers.ts` — the list the config layer reconciles into
1382
+ * wrangler's `containers[]`, `CONTAINER_*` Durable Object bindings, and
1383
+ * migration classes. Empty when the project declares no containers.
1384
+ */
1385
+ containers: ReadonlyArray<ContainerIR>;
1386
+ /**
1387
+ * Deduplicated cron schedules discovered from `cronJobs()` definitions —
1388
+ * the array the vite plugin reconciles into `wrangler.jsonc`'s
1389
+ * `triggers.crons`. Empty when the project declares no crons.
1390
+ */
1391
+ cronTriggers: ReadonlyArray<string>;
1392
+ generated: {
1393
+ api: string; /** Fluent worker-composition builder (`_generated/app.ts`) — `defineApp()`. Always written. */
1394
+ app: string;
1395
+ /** Container DO classes (`_generated/containers.ts`); `""` (and not written) when no containers are declared. */
1396
+ containers: string;
1397
+ crons: string;
1398
+ dataModel: string;
1399
+ drizzleGlobal: string;
1400
+ drizzleShard: string;
1401
+ functions: string; /** OpenAPI 3.1.0 document (`_generated/openapi.json`), pretty-printed JSON. */
1402
+ openApi: string;
1403
+ /**
1404
+ * OpenAPI document as an importable TS module (`_generated/openapi.ts`) —
1405
+ * `export const openApiSpec`, the worker imports it for
1406
+ * `createWorker({ openApiSpec })`. Same document as `openApi`. Written
1407
+ * alongside `openapi.json` whenever `apiSpec` includes `openapi`.
1408
+ */
1409
+ openApiModule: string;
1410
+ /** OpenRPC 1.x document (`_generated/openrpc.json`), pretty-printed JSON. Always computed; written only when `apiSpec` includes `openrpc`. */
1411
+ openRpc: string;
1412
+ /**
1413
+ * OpenRPC document as an importable TS module (`_generated/openrpc.ts`) —
1414
+ * `export const openRpcSpec`, for `createWorker({ openRpcSpec })`. Same
1415
+ * document as `openRpc`. Written alongside `openrpc.json` whenever
1416
+ * `apiSpec` includes `openrpc`.
1417
+ */
1418
+ openRpcModule: string;
1419
+ /** Project-bound seed client (`_generated/seed.ts`); `""` (and not written) when `@lunora/seed` is not a declared dependency. */
1420
+ seed: string;
1421
+ server: string;
1422
+ shard: string; /** Static vector-index registry (`_generated/vectors.ts`) — `LUNORA_VECTOR_INDEXES`. Empty array body when the schema declares none. */
1423
+ vectors: string;
1424
+ /** WorkflowEntrypoint classes (`_generated/workflows.ts`); `""` (and not written) when no workflows are declared. */
1425
+ workflows: string;
1426
+ };
1427
+ outputDirectory: string;
1428
+ /**
1429
+ * The CURRENT structural schema snapshot computed this run (tables + field
1430
+ * kinds/optionality + indexes/relations/shard mode + declared migration ids).
1431
+ * The pre-deploy drift gate diffs this against the committed baseline read
1432
+ * from {@link CodegenResult.schemaSnapshotPath}. Always present, even on a
1433
+ * `dryRun`.
1434
+ */
1435
+ schemaSnapshot: SchemaSnapshot;
1436
+ /** Absolute path of the committed baseline file (`lunora/.lunora-schema.json`). */
1437
+ schemaSnapshotPath: string;
1438
+ /**
1439
+ * Workflows discovered from `defineWorkflow` exports in
1440
+ * `lunora/workflows.ts` — the list the config layer reconciles into
1441
+ * wrangler's `workflows[]` array. Workflows are NOT Durable Objects, so this
1442
+ * adds no binding or migration. Empty when the project declares no workflows.
1443
+ */
1444
+ workflows: ReadonlyArray<WorkflowIR>;
1445
+ }
1446
+ /**
1447
+ * Convert a {@link SchemaIR} into a synthetic runtime {@link Schema} carrying just
1448
+ * the `tables[name].shape` surface `@lunora/seed` introspects. System columns
1449
+ * (`_id`, `_creationTime`) are absent from the IR shape, exactly as the seed
1450
+ * engine expects (it assigns `_id` itself).
1451
+ */
1452
+ declare const schemaFromIr: (ir: SchemaIR) => Schema;
1453
+ /**
1454
+ * Convert a codegen {@link ValidatorIR} into a JSON Schema node. A thin wrapper
1455
+ * over the shared {@link jsonSchemaFromNode} core (from `@lunora/values`) with the
1456
+ * IR-backed {@link irReader}, so the kind→schema mapping is the *same* algorithm
1457
+ * `@lunora/values`' `toJsonSchema` runs — codegen never instantiates the runtime
1458
+ * `v.*` objects, it only holds the reflected IR. Shared by the OpenAPI and
1459
+ * OpenRPC emitters so both surfaces speak one JSON Schema dialect.
1460
+ */
1461
+ declare const validatorIrToJsonSchema: (validator: ValidatorIR) => JsonSchema;
1462
+ /** Build `{ type: "object", properties, required }` from an IR shape (mirrors `@lunora/values`' object mapping). */
1463
+
1464
+ /**
1465
+ * The machine-readable `LunoraError` codes Lunora emits on the RPC + REST
1466
+ * surfaces, enumerated from `@lunora/server`'s `CODE_STATUS` map plus the
1467
+ * runtime/DO dispatch codes (`FUNCTION_NOT_FOUND`, `PAYLOAD_TOO_LARGE`,
1468
+ * `METHOD_NOT_ALLOWED`, the `*_NOT_CONFIGURED` admin gates, …). The list documents
1469
+ * the contract; clients switch on `error.code`. Kept sorted for stable output.
1470
+ */
1471
+ declare const LUNORA_ERROR_CODES: ReadonlyArray<string>;
1472
+ declare const VERSION = "0.0.0";
1473
+ export { type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, type DriftChange, type EmitAppOptions, type FieldSnapshot, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, type MaskProcedureIR, type MigrationIR, OPENRPC_VERSION, type OpenApiEmitInput, type OpenRpcEmitInput, type ProjectIR, type QueryReadIR, type RelationSnapshot, type RlsMetadataIR, type RlsPolicyIR, type RlsProcedureIR, type RlsRoleIR, SCHEMA_SNAPSHOT_FILENAME, SCHEMA_SNAPSHOT_VERSION, type SchemaDrift, type SchemaDriftDecision, type SchemaIR, type SchemaSnapshot, SchemaSnapshotParseError, type StorageRuleIR, type StorageRulesMetadataIR, type TableIR, type TableSnapshot, VERSION, type ValidatorIR, type VectorIndexIR, WORKFLOWS_FILENAME, type WorkflowIR, buildOpenApiDocument, buildOpenRpcDocument, buildSchemaSnapshot, createCodegenProject, diagnosticAt, diffSchemaSnapshots, discoverAuthApiCalls, discoverContainers, discoverCrons, discoverFunctions, discoverHttpRoutes, discoverInserts, discoverMaskProcedures, discoverMigrations, discoverNondeterministicCalls, discoverQueries, discoverRlsMetadata, discoverRlsProcedures, discoverSchema, discoverStorageRulesMetadata, discoverWorkflows, emitApi, emitApp, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitOpenApi, emitOpenApiModule, emitOpenRpc, emitOpenRpcModule, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers, evaluateSchemaDrift, formatAdvisories, lintSchema, parseSchemaSnapshot, refreshCodegenProject, runCodegen, schemaFromIr, serializeSchemaSnapshot, validatorIrToJsonSchema };