@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.
- package/LICENSE.md +105 -0
- package/README.md +117 -9
- package/__assets__/package-og.svg +14 -0
- package/dist/index.d.mts +1473 -0
- package/dist/index.d.ts +1473 -0
- package/dist/index.mjs +28 -0
- package/dist/packem_shared/CONTAINERS_FILENAME-0K-pjNb8.mjs +224 -0
- package/dist/packem_shared/CodegenDiagnosticError-54jWDxA9.mjs +22 -0
- package/dist/packem_shared/LUNORA_ERROR_CODES-CySpQPD3.mjs +61 -0
- package/dist/packem_shared/buildOpenApiDocument-yHVN66Xd.mjs +183 -0
- package/dist/packem_shared/buildOpenRpcDocument-BZGY1-jT.mjs +60 -0
- package/dist/packem_shared/buildSchemaSnapshot-DzLDbWk3.mjs +233 -0
- package/dist/packem_shared/createCodegenProject-DGJm0_Pk.mjs +895 -0
- package/dist/packem_shared/discover-ast-CT6BgBr4.mjs +13 -0
- package/dist/packem_shared/discoverAuthApiCalls-C35R6z0T.mjs +62 -0
- package/dist/packem_shared/discoverCrons-BL6iGuJ3.mjs +254 -0
- package/dist/packem_shared/discoverFunctions-DEgAcRuD.mjs +460 -0
- package/dist/packem_shared/discoverHttpRoutes-C978pBiG.mjs +131 -0
- package/dist/packem_shared/discoverInserts-CRQdXvHO.mjs +39 -0
- package/dist/packem_shared/discoverMaskProcedures-B64zA740.mjs +217 -0
- package/dist/packem_shared/discoverMigrations-Doj_-BAA.mjs +91 -0
- package/dist/packem_shared/discoverNondeterministicCalls-4KiPQxQU.mjs +122 -0
- package/dist/packem_shared/discoverQueries-BkIi0dBD.mjs +62 -0
- package/dist/packem_shared/discoverRlsMetadata-DpRB1HMe.mjs +280 -0
- package/dist/packem_shared/discoverSchema-BBulgGbH.mjs +542 -0
- package/dist/packem_shared/discoverStorageRulesMetadata-DAqJUxUv.mjs +97 -0
- package/dist/packem_shared/discoverWorkflows-DRDQdhfq.mjs +84 -0
- package/dist/packem_shared/emitApi-hRVC-kE7.mjs +2426 -0
- package/dist/packem_shared/emitApp-CzZ6GbrD.mjs +593 -0
- package/dist/packem_shared/lintSchema-DicbOHvH.mjs +68 -0
- package/dist/packem_shared/parse-validator-tuQtHrsr.mjs +132 -0
- package/dist/packem_shared/paths-BRd6JHuF.mjs +11 -0
- package/dist/packem_shared/schemaFromIr-DTYsLBaA.mjs +57 -0
- package/package.json +45 -17
package/dist/index.d.mts
ADDED
|
@@ -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 `<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<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 `<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 `<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 `<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.<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 `<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 `<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 `<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 `<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 `<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.<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 `<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.<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 `<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 `<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 `<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 `<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.<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 `<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.<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: <detail> (<file>:<line>:<column>)`
|
|
714
|
+
*/
|
|
715
|
+
declare const diagnosticAt: (node: Node, detail: string) => CodegenDiagnosticError;
|
|
716
|
+
/**
|
|
717
|
+
* Discover `ctx.authApi.<method>(...)` (and bare `authApi.<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.<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 `<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<"name">` + `Id<"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:<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 `<projectRoot>/lunora/schema.ts` and every
|
|
1317
|
+
* function file under `<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 };
|