@lunora/codegen 0.0.0 → 1.0.0-alpha.10

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