@lunora/codegen 1.0.0-alpha.13 → 1.0.0-alpha.130
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 +6 -0
- package/dist/index.d.mts +3010 -1005
- package/dist/index.d.ts +3010 -1005
- package/dist/index.mjs +1 -31
- package/dist/packem_shared/AGENTS_FILENAME-CPHo8Se5.mjs +1 -0
- package/dist/packem_shared/CONTAINERS_FILENAME-Bvn6LZP6.mjs +1 -0
- package/dist/packem_shared/CodegenDiagnosticError-BUXlG3P-.mjs +1 -0
- package/dist/packem_shared/DEFAULT_TARGET-CrhWLUK7.mjs +1 -0
- package/dist/packem_shared/FLAGS_FILENAME-DtBWyUKq.mjs +1 -0
- package/dist/packem_shared/GENERATED_HEADER-NWWDWOX4.mjs +1 -0
- package/dist/packem_shared/LUNORA_ERROR_CODES-BYg8Lpgb.mjs +1 -0
- package/dist/packem_shared/MUTATORS_FILENAME-DJerSBO-.mjs +1 -0
- package/dist/packem_shared/NOTIFY_FILENAME-CPg7zlsb.mjs +1 -0
- package/dist/packem_shared/OPENRPC_VERSION-6XrIwoNr.mjs +3 -0
- package/dist/packem_shared/QUEUES_FILENAME-BnGLtAIu.mjs +1 -0
- package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME--CCXsODJ.mjs +9 -0
- package/dist/packem_shared/SCHEMA_SNAPSHOT_VERSION-CftvRWB8.mjs +2 -0
- package/dist/packem_shared/SDK_LANGUAGES-CFyux-74.mjs +371 -0
- package/dist/packem_shared/SHAPES_FILENAME-DTp6VI6K.mjs +1 -0
- package/dist/packem_shared/SchemaSnapshotParseError-D2h88OHU.mjs +16 -0
- package/dist/packem_shared/WORKFLOWS_FILENAME-B8y51Frf.mjs +1 -0
- package/dist/packem_shared/buildOpenApiDocument-DDvSUMD9.mjs +3 -0
- package/dist/packem_shared/describeErrorLevelFindings-D23VSkFP.mjs +1 -0
- package/dist/packem_shared/discover-ast-D33zP-NZ.mjs +1 -0
- package/dist/packem_shared/discover-queries-Dfj_0WJG.mjs +1 -0
- package/dist/packem_shared/discoverAuthApiCalls-DUjcl0SB.mjs +1 -0
- package/dist/packem_shared/discoverCrons-BUJO2ipU.mjs +1 -0
- package/dist/packem_shared/discoverFunctions-CYHtspcp.mjs +1 -0
- package/dist/packem_shared/discoverHttpRoutes-BkwEwmK0.mjs +1 -0
- package/dist/packem_shared/discoverInserts-YX8AmrzL.mjs +1 -0
- package/dist/packem_shared/discoverMaskProcedures-DExWLcsu.mjs +1 -0
- package/dist/packem_shared/discoverMigrations-4Jjf5DJE.mjs +1 -0
- package/dist/packem_shared/discoverNondeterministicCalls-xRw6-4OU.mjs +1 -0
- package/dist/packem_shared/discoverQueries-C5S9tFoD.mjs +1 -0
- package/dist/packem_shared/discoverR2sqlCalls-Ciq0-bVB.mjs +1 -0
- package/dist/packem_shared/discoverRlsMetadata-Bnn3zDlc.mjs +1 -0
- package/dist/packem_shared/discoverSandboxUsage-L0lV17de.mjs +1 -0
- package/dist/packem_shared/discoverSchema-gJ93Xjpx.mjs +1 -0
- package/dist/packem_shared/discoverStorageRulesMetadata-DYAgRSL6.mjs +1 -0
- package/dist/packem_shared/emit-GW3Jf9w0.mjs +2268 -0
- package/dist/packem_shared/emitApp-D-mz_p6P.mjs +646 -0
- package/dist/packem_shared/formatAdvisories-CkEu1eye.mjs +2 -0
- package/dist/packem_shared/isTypedSchema-B_qlBbE-.mjs +2 -0
- package/dist/packem_shared/module-specifiers-WF8iAydP.mjs +1 -0
- package/dist/packem_shared/parse-validator-C5ycrhR3.mjs +1 -0
- package/dist/packem_shared/paths-C-9fwEgQ.mjs +1 -0
- package/dist/packem_shared/readPackageDependencies-C6h6FZuP.mjs +1 -0
- package/dist/packem_shared/redact-Cy14LEnc.mjs +1 -0
- package/dist/packem_shared/schemaFromIr-BWFnvCVg.mjs +1 -0
- package/package.json +12 -7
- package/dist/packem_shared/CONTAINERS_FILENAME-DlP6YM_Q.mjs +0 -224
- package/dist/packem_shared/CodegenDiagnosticError-DeblMkzO.mjs +0 -23
- package/dist/packem_shared/GENERATED_HEADER-BEA7oWGC.mjs +0 -2841
- package/dist/packem_shared/LUNORA_ERROR_CODES-CySpQPD3.mjs +0 -61
- package/dist/packem_shared/LUNORA_SOLUTION_RULES-BTejmZp-.mjs +0 -131
- package/dist/packem_shared/OPENRPC_VERSION-CQ3f8YQ-.mjs +0 -60
- package/dist/packem_shared/QUEUES_FILENAME-B5_eWCRe.mjs +0 -119
- package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-BXtduJFE.mjs +0 -954
- package/dist/packem_shared/SCHEMA_SNAPSHOT_VERSION-wWGP2_5E.mjs +0 -245
- package/dist/packem_shared/WORKFLOWS_FILENAME-D62dcBGg.mjs +0 -84
- package/dist/packem_shared/buildOpenApiDocument-699G4RP4.mjs +0 -183
- package/dist/packem_shared/discover-ast-CT6BgBr4.mjs +0 -13
- package/dist/packem_shared/discoverAuthApiCalls-CoirYbg6.mjs +0 -62
- package/dist/packem_shared/discoverCrons-Cev7RRAf.mjs +0 -257
- package/dist/packem_shared/discoverFunctions-BWMczzBx.mjs +0 -465
- package/dist/packem_shared/discoverHttpRoutes-CfP6cMzt.mjs +0 -131
- package/dist/packem_shared/discoverInserts-C7zxXkUf.mjs +0 -61
- package/dist/packem_shared/discoverMaskProcedures-Cm81kwrb.mjs +0 -217
- package/dist/packem_shared/discoverMigrations-Bi5nJ0mJ.mjs +0 -97
- package/dist/packem_shared/discoverNondeterministicCalls-C4M8AXmQ.mjs +0 -122
- package/dist/packem_shared/discoverQueries-B0wGT-xe.mjs +0 -62
- package/dist/packem_shared/discoverR2sqlCalls-BVNMd428.mjs +0 -80
- package/dist/packem_shared/discoverRlsMetadata-BS9GOGC5.mjs +0 -280
- package/dist/packem_shared/discoverSchema-BnWHHJ4T.mjs +0 -822
- package/dist/packem_shared/discoverStorageRulesMetadata-Da8BKXcI.mjs +0 -97
- package/dist/packem_shared/emitApp-DiK0QSBn.mjs +0 -617
- package/dist/packem_shared/formatAdvisories-8NIv1k0I.mjs +0 -69
- package/dist/packem_shared/parse-validator-Cabb60UV.mjs +0 -133
- package/dist/packem_shared/paths-BRd6JHuF.mjs +0 -11
- package/dist/packem_shared/schemaFromIr-DTYsLBaA.mjs +0 -57
package/dist/index.d.mts
CHANGED
|
@@ -1,14 +1,116 @@
|
|
|
1
|
-
import { Finding } from '@lunora/advisor';
|
|
1
|
+
import { AdvisorExportSink, AdvisorGeoIndexUsage, AdvisorNotifyCall, AdvisorNotifyConfig, Finding, LintContext, AdvisorProcedureProtection } from '@lunora/advisor';
|
|
2
2
|
export type { Finding } from '@lunora/advisor';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { Project, Node } from 'ts-morph';
|
|
4
|
+
import { LunoraError } from '@lunora/errors';
|
|
5
|
+
export { MESSAGE_SOLUTIONS as LUNORA_SOLUTION_RULES, type Solution as LunoraSolution, type SolutionRule as LunoraSolutionRule, findSolutionByMessage as findLunoraSolution } from '@lunora/errors';
|
|
6
|
+
import { StudioFeaturesResult } from '@lunora/shard-engine';
|
|
5
7
|
import { Schema } from '@lunora/server';
|
|
6
8
|
import { JsonSchema } from '@lunora/values';
|
|
9
|
+
import { LanguageName } from 'quicktype-core';
|
|
10
|
+
/** Current snapshot format version. Bumped if the structural shape below changes. */
|
|
11
|
+
declare const SCHEMA_SNAPSHOT_VERSION: 1;
|
|
12
|
+
/** A single field's structural shape: its value kind and whether it is optional. */
|
|
13
|
+
interface FieldSnapshot {
|
|
14
|
+
/** The validator kind (`string`, `number`, `id`, `object`, …) after unwrapping `v.optional`. */
|
|
15
|
+
kind: string;
|
|
16
|
+
/** True when declared `v.optional(...)` — accepts `undefined` / absent on insert. */
|
|
17
|
+
optional: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** A single secondary index's structural shape. */
|
|
20
|
+
interface IndexSnapshot {
|
|
21
|
+
fields: ReadonlyArray<string>;
|
|
22
|
+
unique: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** A single relation's structural shape. */
|
|
25
|
+
interface RelationSnapshot {
|
|
26
|
+
field: string;
|
|
27
|
+
kind: "many" | "one";
|
|
28
|
+
table: string;
|
|
29
|
+
}
|
|
30
|
+
/** Structural snapshot of one table. */
|
|
31
|
+
interface TableSnapshot {
|
|
32
|
+
/** Field name → {@link FieldSnapshot}, in declared order. */
|
|
33
|
+
fields: Record<string, FieldSnapshot>;
|
|
34
|
+
/** Index name → {@link IndexSnapshot}. */
|
|
35
|
+
indexes: Record<string, IndexSnapshot>;
|
|
36
|
+
/** Relation accessor name → {@link RelationSnapshot}. */
|
|
37
|
+
relations: Record<string, RelationSnapshot>;
|
|
38
|
+
/**
|
|
39
|
+
* `"root"` (default single-DO), `"global"` (D1-replicated), or
|
|
40
|
+
* `"shardBy:<field>"` (partitioned). Encoded as a string so the snapshot
|
|
41
|
+
* stays a plain JSON-stable value.
|
|
42
|
+
*/
|
|
43
|
+
shardMode: string;
|
|
44
|
+
}
|
|
45
|
+
/** A deterministic structural view of the whole schema at one point in time. */
|
|
46
|
+
interface SchemaSnapshot {
|
|
47
|
+
/**
|
|
48
|
+
* Cloudflare DO data-residency jurisdiction declared via `.jurisdiction("…")`,
|
|
49
|
+
* or absent. Tracked because changing it strands all existing Durable Object
|
|
50
|
+
* data (a DO name maps to a different ID per jurisdiction). Optional, so old
|
|
51
|
+
* baselines written before this field parse cleanly (absent ⇒ undefined).
|
|
52
|
+
*
|
|
53
|
+
* Typed as a plain `string` (not the authoring union) on purpose: this is
|
|
54
|
+
* STORED data that a newer Lunora may have written with a jurisdiction this
|
|
55
|
+
* version doesn't yet know. Preserving the raw value keeps the breaking
|
|
56
|
+
* `changedJurisdiction` diff correct under a downgrade — coercing an unknown
|
|
57
|
+
* value to `undefined` would fail OPEN and hide the most destructive change.
|
|
58
|
+
*/
|
|
59
|
+
jurisdiction?: string;
|
|
60
|
+
/** Sorted list of every declared `defineMigration` id at capture time. */
|
|
61
|
+
migrationIds: ReadonlyArray<string>;
|
|
62
|
+
/** Table name → {@link TableSnapshot}, keys sorted for stable serialization. */
|
|
63
|
+
tables: Record<string, TableSnapshot>;
|
|
64
|
+
version: typeof SCHEMA_SNAPSHOT_VERSION;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Whether a change is anchored to one table's own shape, or to the schema as a
|
|
68
|
+
* whole.
|
|
69
|
+
*
|
|
70
|
+
* This is the signal a UI needs to decide which tables to mark as changed, and
|
|
71
|
+
* it lives HERE — next to the change union it classifies — rather than as a
|
|
72
|
+
* hand-maintained set of type names in the consumer. A set in the consumer gives
|
|
73
|
+
* zero compile-time pressure: adding a variant to `DriftChange["type"]` would
|
|
74
|
+
* silently render an affected table as untouched, which is exactly the
|
|
75
|
+
* UI-disagrees-with-the-deploy-gate divergence this module exists to prevent.
|
|
76
|
+
*/
|
|
77
|
+
type DriftScope = "schema" | "table";
|
|
78
|
+
/** One classified structural change between two snapshots. */
|
|
79
|
+
interface DriftChange {
|
|
80
|
+
/**
|
|
81
|
+
* `"table"` means this table's own DDL moved (fields, indexes, shard mode) —
|
|
82
|
+
* a relation whose foreign key lives on the OTHER table stays `"schema"`, so
|
|
83
|
+
* the "changed" signal keeps meaning "this table's shape moved".
|
|
84
|
+
*/
|
|
85
|
+
scope: DriftScope;
|
|
86
|
+
/** `"breaking"` changes need a data migration; `"safe"` changes are additive. */
|
|
87
|
+
severity: "breaking" | "safe";
|
|
88
|
+
/** Human-readable, actionable description (used in the gate message). */
|
|
89
|
+
summary: string;
|
|
90
|
+
/** The table this change belongs to. Always set when `scope` is `"table"`. */
|
|
91
|
+
table?: string;
|
|
92
|
+
/** A machine-readable change discriminator. */
|
|
93
|
+
type: "addedIndex" | "addedOptionalField" | "addedRelation" | "addedRequiredField" | "addedTable" | "changedFieldKind" | "changedIndex" | "changedJurisdiction" | "changedShardMode" | "fieldOptionalToRequired" | "fieldRequiredToOptional" | "removedField" | "removedIndex" | "removedRelation" | "removedTable";
|
|
94
|
+
}
|
|
95
|
+
/** The result of diffing two snapshots: every classified change. */
|
|
96
|
+
interface SchemaDrift {
|
|
97
|
+
/** Every classified change, in a stable order (added/changed per table, then removals). */
|
|
98
|
+
changes: ReadonlyArray<DriftChange>;
|
|
99
|
+
}
|
|
100
|
+
/** Serialize a snapshot to the exact bytes written to `lunora/.lunora-schema.json` (trailing newline). */
|
|
101
|
+
declare const serializeSchemaSnapshot: (snapshot: SchemaSnapshot) => string;
|
|
7
102
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
103
|
+
* Diff the current snapshot against a baseline and classify every structural
|
|
104
|
+
* change. Pure — no I/O. When `baseline` is `undefined` (no snapshot yet) there
|
|
105
|
+
* is no drift to report: every table is treated as a fresh additive
|
|
106
|
+
* `addedTable`, so a first deploy is never blocked.
|
|
107
|
+
*/
|
|
108
|
+
declare const diffSchemaSnapshots: (baseline: SchemaSnapshot | undefined, current: SchemaSnapshot) => SchemaDrift;
|
|
109
|
+
/**
|
|
110
|
+
* AST-observable subset of a column's modifier chain (`.unique()`, `.default()`,
|
|
111
|
+
* …). Function-valued modifiers (`.$defaultFn`/`.$onUpdateFn`) can't be
|
|
112
|
+
* serialized, so only their *presence* is recorded.
|
|
113
|
+
*/
|
|
12
114
|
interface ColumnMetaIR {
|
|
13
115
|
/** `.default(...)` or `.$defaultFn(...)` present — field is optional on insert. */
|
|
14
116
|
hasDefault?: boolean;
|
|
@@ -26,13 +128,13 @@ interface ValidatorIR {
|
|
|
26
128
|
/** Column modifiers (`.unique()`, `.default()`, `.nullable()`, …) when present. */
|
|
27
129
|
column?: ColumnMetaIR;
|
|
28
130
|
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
131
|
+
* `true` when this validator carries a `.check(...)` refinement. The predicate
|
|
132
|
+
* is a runtime closure the AST→IR step can't represent, so the node keeps its
|
|
133
|
+
* base `kind` but records the refinement's presence here. The AOT args-validator
|
|
134
|
+
* compiler declines any node with this flag (compiling it would silently skip
|
|
135
|
+
* the predicate). `.meta(...)` is pure metadata with no parse effect and does
|
|
136
|
+
* NOT set this.
|
|
137
|
+
*/
|
|
36
138
|
hasRefinement?: boolean;
|
|
37
139
|
/** For `v.optional(inner)` / `v.array(inner)`. */
|
|
38
140
|
inner?: ValidatorIR;
|
|
@@ -50,6 +152,14 @@ interface ValidatorIR {
|
|
|
50
152
|
sourceText?: string;
|
|
51
153
|
/** For `v.id("table")` — the table name. */
|
|
52
154
|
tableName?: string;
|
|
155
|
+
/**
|
|
156
|
+
* For `v.from(externalSchema)` — the wrapped Standard Schema's inferred type,
|
|
157
|
+
* rendered as TS source. Recovered through the type checker from
|
|
158
|
+
* `~standard.types.output`, mirroring the runtime's `InferStandardOutput`.
|
|
159
|
+
* Absent when it could not be recovered safely, in which case the emitted
|
|
160
|
+
* type falls back to `unknown`.
|
|
161
|
+
*/
|
|
162
|
+
tsType?: string;
|
|
53
163
|
valueType?: ValidatorIR;
|
|
54
164
|
}
|
|
55
165
|
interface IndexIR {
|
|
@@ -58,11 +168,32 @@ interface IndexIR {
|
|
|
58
168
|
unique?: boolean;
|
|
59
169
|
}
|
|
60
170
|
interface SearchIndexIR {
|
|
61
|
-
/** Primary text-search field. */
|
|
171
|
+
/** Primary text-search field; a dot-separated path reads a nested field. */
|
|
62
172
|
field: string;
|
|
63
173
|
/** Optional filter fields surfaced alongside the FTS column. */
|
|
64
174
|
filterFields?: ReadonlyArray<string>;
|
|
175
|
+
/** Text-analysis profile (accent folding + that language's stopwords). */
|
|
176
|
+
language?: string;
|
|
177
|
+
name: string;
|
|
178
|
+
/** Skip the migration-time backfill of the search companion (large tables index out-of-band). */
|
|
179
|
+
staged?: boolean;
|
|
180
|
+
/** `"native"` opts into the engine's own full-text index where it has one (Postgres). */
|
|
181
|
+
strategy?: string;
|
|
182
|
+
}
|
|
183
|
+
/** A `.geoIndex(name, { field, precision? })` declaration — a geohash companion over a `v.geoPoint()` column. */
|
|
184
|
+
interface GeoIndexIR {
|
|
185
|
+
/** The `v.geoPoint()` column feeding the geohash. */
|
|
186
|
+
field: string;
|
|
65
187
|
name: string;
|
|
188
|
+
/** Geohash precision (characters) maintained on the companion; omitted ⇒ the runtime default. */
|
|
189
|
+
precision?: number;
|
|
190
|
+
}
|
|
191
|
+
/** A `.ttl(field, { after? })` declaration — declarative table-level auto-expiry. */
|
|
192
|
+
interface TtlIR {
|
|
193
|
+
/** Millisecond offset added to `field` to derive the expiry (`field + after`); omitted ⇒ `field` is the absolute expiry. */
|
|
194
|
+
after?: number;
|
|
195
|
+
/** The epoch-millisecond expiry column. */
|
|
196
|
+
field: string;
|
|
66
197
|
}
|
|
67
198
|
interface VectorIndexIR {
|
|
68
199
|
dimensions?: number;
|
|
@@ -76,19 +207,19 @@ interface VectorIndexIR {
|
|
|
76
207
|
table: string;
|
|
77
208
|
}
|
|
78
209
|
/**
|
|
79
|
-
* One ordering key on a rank index's `sortBy`: the column and direction.
|
|
80
|
-
* Mirrors the runtime `RankSortKey` (defaults `direction` to `"asc"`).
|
|
81
|
-
*/
|
|
210
|
+
* One ordering key on a rank index's `sortBy`: the column and direction.
|
|
211
|
+
* Mirrors the runtime `RankSortKey` (defaults `direction` to `"asc"`).
|
|
212
|
+
*/
|
|
82
213
|
interface RankSortKeyIR {
|
|
83
214
|
direction: "asc" | "desc";
|
|
84
215
|
field: string;
|
|
85
216
|
}
|
|
86
217
|
/**
|
|
87
|
-
* A `.rankIndex(name, { sortBy, partitionBy?, where? })` declaration. The owning
|
|
88
|
-
* table is always the table the index is declared on, so — unlike a vector index
|
|
89
|
-
* — there is no separate `on`/`table` reference to carry: it rides along on its
|
|
90
|
-
* {@link TableIR}. Only the fields needed for type emission are captured.
|
|
91
|
-
*/
|
|
218
|
+
* A `.rankIndex(name, { sortBy, partitionBy?, where? })` declaration. The owning
|
|
219
|
+
* table is always the table the index is declared on, so — unlike a vector index
|
|
220
|
+
* — there is no separate `on`/`table` reference to carry: it rides along on its
|
|
221
|
+
* {@link TableIR}. Only the fields needed for type emission are captured.
|
|
222
|
+
*/
|
|
92
223
|
interface RankIndexIR {
|
|
93
224
|
name: string;
|
|
94
225
|
/** Columns scoping each ranking; omitted ⇒ one global rank over the table. */
|
|
@@ -109,21 +240,93 @@ interface RelationIR {
|
|
|
109
240
|
/** Target table name. */
|
|
110
241
|
table: string;
|
|
111
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Statically-discovered `.source(...)` config (plan 077). Only the bits the
|
|
245
|
+
* advisor lints + DO wiring need are captured; `map`/`tenantBy` are functions and
|
|
246
|
+
* cannot be serialized, so their presence is recorded as `hasTenantBy` rather than
|
|
247
|
+
* the function itself.
|
|
248
|
+
*/
|
|
249
|
+
interface ExternalSourceIR {
|
|
250
|
+
/** The wrangler Hyperdrive binding name. */
|
|
251
|
+
binding: string;
|
|
252
|
+
/** Whether a `columns` projection allow-list was given. */
|
|
253
|
+
columns?: ReadonlyArray<string>;
|
|
254
|
+
/** `true` when a `reconcileEveryMs` was given — one of the two incremental delete-visibility paths the `external_source_incremental_no_delete_path` lint checks. */
|
|
255
|
+
hasReconcile?: boolean;
|
|
256
|
+
/** `true` when a `softDeleteColumn` was given — the other incremental delete-visibility path. */
|
|
257
|
+
hasSoftDelete?: boolean;
|
|
258
|
+
/** `true` when a `tenantBy` mapper was given — the tenant-isolation boundary the `external_source_unscoped` lint checks. */
|
|
259
|
+
hasTenantBy: boolean;
|
|
260
|
+
/** The `idColumn` literal, when given (defaults to `"id"` at runtime). */
|
|
261
|
+
idColumn?: string;
|
|
262
|
+
/** Delete-detection mode literal, when given (`"full-pull"` today). */
|
|
263
|
+
mode?: string;
|
|
264
|
+
/** The membership query literal, when statically knowable. */
|
|
265
|
+
query?: string;
|
|
266
|
+
/**
|
|
267
|
+
* `true` when `.source(...)` was present but its argument was **not** a static
|
|
268
|
+
* object literal (e.g. `.source(buildConfig())`), so none of the fields above
|
|
269
|
+
* could be read. The source still exists — this flag lets `hasSourcedTables`
|
|
270
|
+
* (codegen) and the `external_source_*` lints treat it as a source that can't be
|
|
271
|
+
* verified, instead of mistaking it for no `.source()` at all.
|
|
272
|
+
*/
|
|
273
|
+
unanalyzable?: boolean;
|
|
274
|
+
}
|
|
112
275
|
interface TableIR {
|
|
113
276
|
/**
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
277
|
+
* `true` when the table chain carried `.commitOrdered()` — every row carries
|
|
278
|
+
* `_commitSeq`, a per-shard integer allocated once per mutation and strictly
|
|
279
|
+
* increasing in commit order. Optional: hand-built IR and tables that never
|
|
280
|
+
* called `.commitOrdered()` default it to `false`.
|
|
281
|
+
*/
|
|
282
|
+
commitOrdered?: boolean;
|
|
283
|
+
/**
|
|
284
|
+
* The `defineSchemaExtension` key that contributed this table, set when it
|
|
285
|
+
* arrived through `defineSchema(...).extend(...)`. Absent for a table the app
|
|
286
|
+
* declared itself.
|
|
287
|
+
*
|
|
288
|
+
* Drives the generated `AppTableName` union: an add-on's tables
|
|
289
|
+
* (`ratelimit_buckets`, …) are real tables and stay in `TableName`, but an app
|
|
290
|
+
* enumerating "my tables" should not have to know about them.
|
|
291
|
+
*/
|
|
292
|
+
extensionKey?: string;
|
|
293
|
+
/**
|
|
294
|
+
* `true` when the table chain carried `.externallyManaged()` — its rows are
|
|
295
|
+
* written outside Lunora's discoverable insert path (adapter/migration/
|
|
296
|
+
* middleware), so advisor insert-path lints skip it. Optional: hand-built
|
|
297
|
+
* IR and the runtime `fromServerSchema` path default it to `false`.
|
|
298
|
+
*/
|
|
119
299
|
externallyManaged?: boolean;
|
|
120
300
|
/**
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
301
|
+
* Set when the chain carried `.source(...)` — the table is materialized from an
|
|
302
|
+
* external Hyperdrive-backed database by a system poll loop (plan 077). Carries
|
|
303
|
+
* the statically-knowable bits the advisor lints read; the functions (`map`,
|
|
304
|
+
* `tenantBy`) are not serialized, only their presence (`hasTenantBy`).
|
|
305
|
+
*/
|
|
306
|
+
externalSource?: ExternalSourceIR;
|
|
307
|
+
/**
|
|
308
|
+
* Storage backend for a `.global()` table: `"d1"` (default) or
|
|
309
|
+
* `"hyperdrive"` (a Postgres/MySQL database via Cloudflare Hyperdrive). Only
|
|
310
|
+
* meaningful when `shardMode === "global"`; absent for sharded/root tables.
|
|
311
|
+
*/
|
|
312
|
+
/** Geospatial indexes declared inline via `.geoIndex(name, …)`. Optional so hand-built IR may omit it (discovery always sets it). */
|
|
313
|
+
geoIndexes?: ReadonlyArray<GeoIndexIR>;
|
|
125
314
|
globalBackend?: "d1" | "hyperdrive";
|
|
126
315
|
indexes: ReadonlyArray<IndexIR>;
|
|
316
|
+
/**
|
|
317
|
+
* `true` when the table chain carried `.public()` — an explicit opt-OUT of
|
|
318
|
+
* the schema's `.rls("required")` enforcement for this one table. Optional:
|
|
319
|
+
* hand-built IR and tables that never called `.public()` default it to
|
|
320
|
+
* `false`.
|
|
321
|
+
*/
|
|
322
|
+
isPublic?: boolean;
|
|
323
|
+
/**
|
|
324
|
+
* `true` when the table chain carried `.memory()` — its rows are cleared on
|
|
325
|
+
* every Durable Object cold start and never reach the CDC changelog.
|
|
326
|
+
* Optional: hand-built IR and tables that never called `.memory()` default it
|
|
327
|
+
* to `false`.
|
|
328
|
+
*/
|
|
329
|
+
memory?: boolean;
|
|
127
330
|
name: string;
|
|
128
331
|
/** Rank indexes declared inline via `.rankIndex(name, …)`. */
|
|
129
332
|
rankIndexes: ReadonlyArray<RankIndexIR>;
|
|
@@ -139,6 +342,8 @@ interface TableIR {
|
|
|
139
342
|
softDelete?: {
|
|
140
343
|
field: string;
|
|
141
344
|
};
|
|
345
|
+
/** Set when the chain carried `.ttl(field, { after? })` — the declarative auto-expiry policy read by the DO alarm sweep. */
|
|
346
|
+
ttl?: TtlIR;
|
|
142
347
|
/** Vector indexes declared inline via `.vectorize()` (DSL Shape A). */
|
|
143
348
|
vectorIndexes: ReadonlyArray<VectorIndexIR>;
|
|
144
349
|
}
|
|
@@ -146,54 +351,98 @@ interface TableIR {
|
|
|
146
351
|
type JurisdictionIR = "eu" | "fedramp" | "us";
|
|
147
352
|
interface SchemaIR {
|
|
148
353
|
/**
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
354
|
+
* Cloudflare data-residency jurisdiction declared via
|
|
355
|
+
* `defineSchema(...).jurisdiction("…")`. Emitted into the generated worker's
|
|
356
|
+
* `createWorker({ jurisdiction })` (and `ctx.scheduler` / `ctx.containers`).
|
|
357
|
+
* Absent ⇒ un-pinned.
|
|
358
|
+
*/
|
|
154
359
|
jurisdiction?: JurisdictionIR;
|
|
360
|
+
/**
|
|
361
|
+
* Set when `defineSchema(...).rls("required")` was chained onto the schema —
|
|
362
|
+
* every table's `ctx.db` write path is denied without an RLS-covering
|
|
363
|
+
* procedure unless the table itself is `.public()` (see {@link TableIR.isPublic}).
|
|
364
|
+
* Absent when the schema never called `.rls("required")`.
|
|
365
|
+
*/
|
|
366
|
+
rlsMode?: "required";
|
|
155
367
|
tables: ReadonlyArray<TableIR>;
|
|
156
368
|
/** All vector indexes (inline Shape A hoisted + standalone Shape B), flattened. */
|
|
157
369
|
vectorIndexes: ReadonlyArray<VectorIndexIR>;
|
|
158
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Statically-read mirror of `@lunora/server`'s `RestCacheConfig`. Every field is
|
|
373
|
+
* optional because discovery only records literals it could actually read off the
|
|
374
|
+
* `.expose({ cache: … })` object.
|
|
375
|
+
*/
|
|
376
|
+
interface ExposeCacheIR {
|
|
377
|
+
maxAge?: number;
|
|
378
|
+
scope?: "private" | "public";
|
|
379
|
+
staleWhileRevalidate?: number;
|
|
380
|
+
tag?: string;
|
|
381
|
+
vary?: string;
|
|
382
|
+
}
|
|
159
383
|
interface FunctionIR {
|
|
160
384
|
args: Record<string, ValidatorIR>;
|
|
161
385
|
exportName: string;
|
|
162
|
-
/**
|
|
386
|
+
/**
|
|
387
|
+
* Set by the `.expose({ rest: true })` builder modifier (plan 167). When
|
|
388
|
+
* `rest` is `true` the function is published on the public REST surface, so the
|
|
389
|
+
* OpenAPI emitter describes it as a real `/_lunora/rest/<namespace>/<fn>` path
|
|
390
|
+
* (the single source of truth the runtime router also derives from). Absent →
|
|
391
|
+
* RPC-only (the default; not on the REST surface).
|
|
392
|
+
*
|
|
393
|
+
* `cache` mirrors the `RestCacheConfig` the runtime turns into response
|
|
394
|
+
* headers, so the emitted spec can document the `Cache-Control` a caller will
|
|
395
|
+
* actually observe. Only statically-readable literal fields are carried; a
|
|
396
|
+
* computed value is simply absent (the spec under-documents rather than lies).
|
|
397
|
+
*/
|
|
398
|
+
expose?: {
|
|
399
|
+
cache?: ExposeCacheIR;
|
|
400
|
+
rest?: boolean;
|
|
401
|
+
};
|
|
402
|
+
/** Path relative to `<projectRoot>/lunora/` without extension, e.g. "messages". */
|
|
163
403
|
filePath: string;
|
|
164
404
|
kind: "action" | "mutation" | "query" | "stream";
|
|
165
405
|
/**
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
lifecycle?: "connect" | "disconnect";
|
|
173
|
-
/**
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
406
|
+
* Set on connection-lifecycle hooks (`onConnect`/`onDisconnect`): the socket
|
|
407
|
+
* side the hook fires on. Such a function is also an internal mutation (so it
|
|
408
|
+
* lands in `LUNORA_FUNCTIONS` for path dispatch); emit additionally collects
|
|
409
|
+
* it into the `LUNORA_LIFECYCLE_HOOKS` manifest keyed by this side. Absent on
|
|
410
|
+
* ordinary functions.
|
|
411
|
+
*/
|
|
412
|
+
lifecycle?: "connect" | "disconnect" | "init" | "reactor";
|
|
413
|
+
/**
|
|
414
|
+
* The `.output(validator)` declaration, when the chain has one.
|
|
415
|
+
*
|
|
416
|
+
* Takes precedence over {@link FunctionIR.returnType} (the handler's
|
|
417
|
+
* inferred type) for the emitted `FunctionReference`. `.output()` is what
|
|
418
|
+
* validates at runtime and what a reader takes as the contract, so the two
|
|
419
|
+
* must agree — see the emit-side note for what went wrong when they did not.
|
|
420
|
+
*/
|
|
421
|
+
output?: ValidatorIR;
|
|
422
|
+
/**
|
|
423
|
+
* Serialized TS source for the handler's return type, with `Promise<T>`
|
|
424
|
+
* unwrapped so callers see `T` directly. Defaults to `"unknown"` when
|
|
425
|
+
* ts-morph cannot resolve the type (typically because the consuming
|
|
426
|
+
* project lacks a tsconfig that can reach `@lunora/server`).
|
|
427
|
+
*/
|
|
179
428
|
returnType: string;
|
|
180
429
|
/**
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
430
|
+
* Call surface the function is exposed on. Absent (or `"public"`) means it
|
|
431
|
+
* lands in the generated `api`; `"internal"` routes it to the separate
|
|
432
|
+
* `internal` object and is rejected by the DO's external RPC path.
|
|
433
|
+
*/
|
|
185
434
|
visibility?: "internal" | "public";
|
|
186
435
|
}
|
|
187
436
|
/**
|
|
188
|
-
* A `defineMigration({...})` declaration discovered in the user's lunora
|
|
189
|
-
* sources. The emitted `LUNORA_MIGRATIONS` registry keys on {@link MigrationIR.id}; the
|
|
190
|
-
* import wiring needs {@link MigrationIR.exportName}/{@link MigrationIR.filePath}. {@link MigrationIR.table} is
|
|
191
|
-
* informational (the runtime object carries the authoritative value).
|
|
192
|
-
*/
|
|
437
|
+
* A `defineMigration({...})` declaration discovered in the user's lunora
|
|
438
|
+
* sources. The emitted `LUNORA_MIGRATIONS` registry keys on {@link MigrationIR.id}; the
|
|
439
|
+
* import wiring needs {@link MigrationIR.exportName}/{@link MigrationIR.filePath}. {@link MigrationIR.table} is
|
|
440
|
+
* informational (the runtime object carries the authoritative value).
|
|
441
|
+
*/
|
|
193
442
|
interface MigrationIR {
|
|
194
443
|
/** Export binding name, used to reference the module member in generated imports. */
|
|
195
444
|
exportName: string;
|
|
196
|
-
/** Path relative to
|
|
445
|
+
/** Path relative to `<projectRoot>/lunora/` without extension, e.g. "migrations". */
|
|
197
446
|
filePath: string;
|
|
198
447
|
/** Stable migration id — the registry key and per-shard run-state key. */
|
|
199
448
|
id: string;
|
|
@@ -201,12 +450,109 @@ interface MigrationIR {
|
|
|
201
450
|
table: string;
|
|
202
451
|
}
|
|
203
452
|
/**
|
|
204
|
-
* A
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
|
|
453
|
+
* A `defineShape({...})` declaration discovered in `lunora/shapes.ts`
|
|
454
|
+
* (local-first sync engine, Phase 7). The emitted `LUNORA_SHAPES` registry keys
|
|
455
|
+
* on {@link ShapeIR.exportName}; the generated DO's `resolveShape` override
|
|
456
|
+
* dispatches a `shape_subscribe` to the matching registered shape. Discovery is
|
|
457
|
+
* marker-driven (the `__lunoraShape` brand) — no field metadata is lifted here
|
|
458
|
+
* because the runtime object (`columns`/`compileWhere`) carries the authority.
|
|
459
|
+
*/
|
|
460
|
+
interface ShapeIR {
|
|
461
|
+
/**
|
|
462
|
+
* The shape's `args` validator map — its partition selector. Lifted so
|
|
463
|
+
* `_generated/collections.ts` can type the selector a caller passes instead of
|
|
464
|
+
* widening it to `Record<string, unknown>`. `{}` for a parameterless shape.
|
|
465
|
+
*/
|
|
466
|
+
args: Record<string, ValidatorIR>;
|
|
467
|
+
/** Export binding name — the shape's registry key and import member. */
|
|
468
|
+
exportName: string;
|
|
469
|
+
/** Path relative to `<projectRoot>/lunora/` without extension — always `"shapes"`. */
|
|
470
|
+
filePath: string;
|
|
471
|
+
/**
|
|
472
|
+
* The `table` string literal from the `defineShape({ table })` call, lifted
|
|
473
|
+
* only for static advisor lints (the runtime object stays authoritative).
|
|
474
|
+
* `undefined` when `table` is not a plain string literal — lints skip those.
|
|
475
|
+
*/
|
|
476
|
+
table?: string;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* The single `defineIdentity({...})` claim contract discovered in
|
|
480
|
+
* `lunora/identity.ts`. Discovery is **marker-driven** (the `__lunoraIdentity`
|
|
481
|
+
* brand, exactly like {@link ShapeIR}) — no claim metadata is lifted here
|
|
482
|
+
* because the emitted `_generated/server.ts` recovers the claim *type* from the
|
|
483
|
+
* declaration itself (`InferIdentity` over the contract's `typeof`), and the
|
|
484
|
+
* runtime object (`validate`/`onInvalid`) carries the authority at the boundary.
|
|
485
|
+
* Exactly one per app; absent ⇒ generated output is byte-identical to today.
|
|
486
|
+
*/
|
|
487
|
+
interface IdentityIR {
|
|
488
|
+
/** Export binding name — the namespace member `_generated/server.ts` reads via `typeof`. */
|
|
489
|
+
exportName: string;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* The single `defineEnv({...})` contract discovered in `lunora/env.ts`. Like
|
|
493
|
+
* {@link IdentityIR}, only the export binding is lifted — the emitted
|
|
494
|
+
* `_generated/server.ts` recovers the validated shape from the declaration
|
|
495
|
+
* itself (`ReturnType` over the accessor's `typeof`), and the generated ShardDO
|
|
496
|
+
* applies the same accessor to the worker `env` at ctx-build time to populate
|
|
497
|
+
* `ctx.env`. Exactly one per app; absent ⇒ generated output is byte-identical.
|
|
498
|
+
*/
|
|
499
|
+
interface EnvIR {
|
|
500
|
+
/** Export binding name — the namespace member `_generated/server.ts` reads via `typeof`. */
|
|
501
|
+
exportName: string;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* A `defineMutator({...})` declaration discovered in `lunora/mutators.ts`
|
|
505
|
+
* (local-first sync engine, Phase 7). The emitted registry registers the
|
|
506
|
+
* authoritative `server` impl into the DO's `LUNORA_FUNCTIONS` table (so
|
|
507
|
+
* `handleRpc` transaction-wraps it) and records its path in
|
|
508
|
+
* `LUNORA_MUTATOR_PATHS` so the DO's `isCustomMutator` override routes the
|
|
509
|
+
* client-watermark push protocol. The client `client` impl is split into the
|
|
510
|
+
* browser bundle separately — only the path crosses to the server side.
|
|
511
|
+
*/
|
|
512
|
+
interface MutatorIR {
|
|
513
|
+
/**
|
|
514
|
+
* The mutator's `args` validator map, parsed exactly as a procedure's is, so
|
|
515
|
+
* the emitted `api.mutators.<name>` reference carries the arg type a client
|
|
516
|
+
* `defineMutator` infers instead of restating. `{}` for a parameterless
|
|
517
|
+
* mutator (or one whose `args` isn't an inline object literal).
|
|
518
|
+
*/
|
|
519
|
+
args: Record<string, ValidatorIR>;
|
|
520
|
+
/** Export binding name — the mutator's registry key and import member. */
|
|
521
|
+
exportName: string;
|
|
522
|
+
/** Path relative to `<projectRoot>/lunora/` without extension — always `"mutators"`. */
|
|
523
|
+
filePath: string;
|
|
524
|
+
/**
|
|
525
|
+
* Serialized TS source for the authoritative `server` impl's return type,
|
|
526
|
+
* `Promise<T>` unwrapped. `"unknown"` when ts-morph can't resolve it — same
|
|
527
|
+
* contract as {@link FunctionIR.returnType}.
|
|
528
|
+
*/
|
|
529
|
+
returnType: string;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* A whole-row `ctx.db.replace(id, document)` write discovered inside a custom
|
|
533
|
+
* mutator's inline `server` impl (`lunora/mutators.ts`) — the input the
|
|
534
|
+
* `mutator_full_row_replace` advisor lint consumes. A `replace` overwrites the
|
|
535
|
+
* entire row, so a concurrent edit to a different column on a synced table is
|
|
536
|
+
* clobbered; `ctx.db.patch(id, { field })` merges at the column level instead.
|
|
537
|
+
* Structurally identical to `AdvisorMutatorWrite` so it passes straight through
|
|
538
|
+
* to the advisor without conversion, exactly as `InsertWriteIR` does for
|
|
539
|
+
* `AdvisorInsertWrite`.
|
|
540
|
+
*/
|
|
541
|
+
interface MutatorWriteIR {
|
|
542
|
+
/** The mutator export whose `server` impl performs the replace, e.g. `renameChannel`. */
|
|
543
|
+
exportName: string;
|
|
544
|
+
/** Openable source path the replace appears in — always `lunora/mutators.ts`. */
|
|
545
|
+
file: string;
|
|
546
|
+
/** 1-based line of the `replace(...)` call. */
|
|
547
|
+
line: number;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* A single cron job lifted from a `cronJobs()` builder in `lunora/crons.ts`.
|
|
551
|
+
* Mirrors `@lunora/scheduler`'s `CronJob`: {@link CronJobIR.cron} is the compiled
|
|
552
|
+
* standard cron expression, {@link CronJobIR.functionPath} is the target
|
|
553
|
+
* `__lunoraRef` (`namespace:fn`), and {@link CronJobIR.args} is the static
|
|
554
|
+
* argument object passed at registration.
|
|
555
|
+
*/
|
|
210
556
|
interface CronJobIR {
|
|
211
557
|
/** Static args object (source-text JSON), defaults to `{}`. */
|
|
212
558
|
args: Record<string, unknown>;
|
|
@@ -217,25 +563,25 @@ interface CronJobIR {
|
|
|
217
563
|
/** Unique, human-readable job name. */
|
|
218
564
|
name: string;
|
|
219
565
|
/**
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
566
|
+
* Set when the job targets a durable workflow (a `lunora/workflows.ts`
|
|
567
|
+
* export) instead of a function: the workflow's `WORKFLOW_*` binding name
|
|
568
|
+
* plus its export name. On each fire the worker starts a new workflow
|
|
569
|
+
* INSTANCE (the {@link CronJobIR.args} become its `params`) rather than
|
|
570
|
+
* dispatching a one-shot function.
|
|
571
|
+
*/
|
|
226
572
|
workflow?: {
|
|
227
573
|
binding: string;
|
|
228
574
|
exportName: string;
|
|
229
575
|
};
|
|
230
576
|
}
|
|
231
577
|
/**
|
|
232
|
-
* A container lifted from a `defineContainer()` export in
|
|
233
|
-
* `lunora/containers.ts`. Carries everything the emitters and the config layer
|
|
234
|
-
* need to wire wrangler (`containers[]` + the Durable Object binding +
|
|
235
|
-
* migration class) and the generated `_generated/containers.ts` DO class.
|
|
236
|
-
* Names are derived via `@lunora/container`'s shared helpers so codegen and
|
|
237
|
-
* the config layer can never disagree.
|
|
238
|
-
*/
|
|
578
|
+
* A container lifted from a `defineContainer()` export in
|
|
579
|
+
* `lunora/containers.ts`. Carries everything the emitters and the config layer
|
|
580
|
+
* need to wire wrangler (`containers[]` + the Durable Object binding +
|
|
581
|
+
* migration class) and the generated `_generated/containers.ts` DO class.
|
|
582
|
+
* Names are derived via `@lunora/container`'s shared helpers so codegen and
|
|
583
|
+
* the config layer can never disagree.
|
|
584
|
+
*/
|
|
239
585
|
interface ContainerIR {
|
|
240
586
|
/** Durable Object binding name, e.g. `CONTAINER_TRANSCODER`. */
|
|
241
587
|
bindingName: string;
|
|
@@ -244,18 +590,18 @@ interface ContainerIR {
|
|
|
244
590
|
/** Generated DO class name, e.g. `TranscoderContainer`. */
|
|
245
591
|
className: string;
|
|
246
592
|
/**
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
593
|
+
* Whether the container may open outbound internet connections, when the
|
|
594
|
+
* value was a static literal. `undefined` means the field was omitted (the
|
|
595
|
+
* platform default is `true`) or wasn't a literal. Lifted for the advisor.
|
|
596
|
+
*/
|
|
251
597
|
enableInternet?: boolean;
|
|
252
598
|
/** The `lunora/containers.ts` export name, e.g. `transcoder`. */
|
|
253
599
|
exportName: string;
|
|
254
600
|
/**
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
601
|
+
* Normalized image source: a local Dockerfile (`dockerfile`), a pre-built
|
|
602
|
+
* registry reference (`registry`), or a Railpack source directory (`build`)
|
|
603
|
+
* that the deploy step builds and pushes before wrangler runs.
|
|
604
|
+
*/
|
|
259
605
|
image: {
|
|
260
606
|
buildContext: string;
|
|
261
607
|
dockerfilePath: string;
|
|
@@ -283,20 +629,20 @@ interface ContainerIR {
|
|
|
283
629
|
stepPercentage?: number;
|
|
284
630
|
};
|
|
285
631
|
/**
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
632
|
+
* The static `sleepAfter` value, when it was a literal. `undefined` means
|
|
633
|
+
* omitted (platform default `"10m"`) or non-literal. Lifted for the advisor.
|
|
634
|
+
*/
|
|
289
635
|
sleepAfter?: number | string;
|
|
290
636
|
}
|
|
291
637
|
/**
|
|
292
|
-
* A workflow lifted from a `defineWorkflow()` export in `lunora/workflows.ts`.
|
|
293
|
-
* Carries what the emitters and the config layer need to wire the wrangler
|
|
294
|
-
* `workflows[]` entry and the generated `_generated/workflows.ts`
|
|
295
|
-
* `WorkflowEntrypoint` class. Unlike containers, workflows are NOT Durable
|
|
296
|
-
* Objects — wrangler gets only a `workflows[]` entry, never a `durable_objects`
|
|
297
|
-
* binding or a migration class. Names are derived via `@lunora/workflow`'s
|
|
298
|
-
* shared helpers so codegen and the config layer can never disagree.
|
|
299
|
-
*/
|
|
638
|
+
* A workflow lifted from a `defineWorkflow()` export in `lunora/workflows.ts`.
|
|
639
|
+
* Carries what the emitters and the config layer need to wire the wrangler
|
|
640
|
+
* `workflows[]` entry and the generated `_generated/workflows.ts`
|
|
641
|
+
* `WorkflowEntrypoint` class. Unlike containers, workflows are NOT Durable
|
|
642
|
+
* Objects — wrangler gets only a `workflows[]` entry, never a `durable_objects`
|
|
643
|
+
* binding or a migration class. Names are derived via `@lunora/workflow`'s
|
|
644
|
+
* shared helpers so codegen and the config layer can never disagree.
|
|
645
|
+
*/
|
|
300
646
|
interface WorkflowIR {
|
|
301
647
|
/** The Cloudflare `Workflow` binding name, e.g. `WORKFLOW_ORDER_PIPELINE`. */
|
|
302
648
|
bindingName: string;
|
|
@@ -305,21 +651,97 @@ interface WorkflowIR {
|
|
|
305
651
|
/** The `lunora/workflows.ts` export name, e.g. `orderPipeline`. */
|
|
306
652
|
exportName: string;
|
|
307
653
|
/**
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
654
|
+
* The stable wrangler `workflows[].name`. Defaults to the kebab-cased export
|
|
655
|
+
* name (`orderPipeline` → `order-pipeline`); a static `name:` literal in the
|
|
656
|
+
* definition overrides it.
|
|
657
|
+
*/
|
|
658
|
+
name: string;
|
|
659
|
+
/**
|
|
660
|
+
* Durable step labels lifted from the handler body — the first string-literal
|
|
661
|
+
* argument of every `ctx.step.do` / `.sleep` / `.sleepUntil` / `.waitForEvent`
|
|
662
|
+
* call. Feeds the duplicate-step-name lint, which flags a name used twice
|
|
663
|
+
* (Cloudflare memoizes by name, so the second call silently returns the
|
|
664
|
+
* first's cached result). Calls with a non-literal name are omitted (not
|
|
665
|
+
* statically comparable).
|
|
666
|
+
*/
|
|
667
|
+
steps: ReadonlyArray<WorkflowStepIR>;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* An agent lifted from a `defineAgent()` export in `lunora/agents.ts`. A
|
|
671
|
+
* `defineAgent` compiles its durable tool-loop onto a Cloudflare Workflow, so —
|
|
672
|
+
* like {@link WorkflowIR} — an agent is NOT a Durable Object: wrangler gets only
|
|
673
|
+
* a `workflows[]` entry, never a `durable_objects` binding or a migration class.
|
|
674
|
+
* Carries what the emitters and the config layer need to wire the generated
|
|
675
|
+
* agent `WorkflowEntrypoint` class (e.g. `SupportAgentWorkflow`), the typed
|
|
676
|
+
* per-agent `ctx.agents` producer, and the reconciled wrangler `workflows[]`
|
|
677
|
+
* entry. Names are derived via `@lunora/agent`'s shared helpers so codegen and
|
|
678
|
+
* the config layer can never disagree.
|
|
679
|
+
*/
|
|
680
|
+
interface AgentIR {
|
|
681
|
+
/** The Cloudflare `Workflow` binding name, e.g. `AGENT_SUPPORT`. */
|
|
682
|
+
bindingName: string;
|
|
683
|
+
/** Generated `WorkflowEntrypoint` class name, e.g. `SupportAgentWorkflow`. */
|
|
684
|
+
className: string;
|
|
685
|
+
/** The `lunora/agents.ts` export name, e.g. `support`. */
|
|
686
|
+
exportName: string;
|
|
687
|
+
/**
|
|
688
|
+
* The stable wrangler `workflows[].name`. Defaults to the kebab-cased export
|
|
689
|
+
* name (`support` → `agent-support`); a static `name:` literal in the
|
|
690
|
+
* definition overrides it.
|
|
691
|
+
*/
|
|
692
|
+
name: string;
|
|
693
|
+
/**
|
|
694
|
+
* Whether the definition declares an `onEmail` mapper on
|
|
695
|
+
* `defineAgent({ onEmail: … })`. When `true` the emitter wires this agent
|
|
696
|
+
* onto the worker's top-level `email()` handler (via `@lunora/agent/inbound`)
|
|
697
|
+
* so inbound mail starts a durable run. Detected by AST PRESENCE — the
|
|
698
|
+
* closure is never evaluated — and written to IR only when present, so
|
|
699
|
+
* email-free agents (and agent-free projects) stay byte-identical.
|
|
700
|
+
*/
|
|
701
|
+
onEmail?: boolean;
|
|
702
|
+
/**
|
|
703
|
+
* Whether the definition opted into public run-starts via
|
|
704
|
+
* `defineAgent({ publicRun: true })` — emitted into the `ctx.agents` wiring
|
|
705
|
+
* spec so the public `agents:agentRun` mutation can gate on it fail-closed.
|
|
706
|
+
* Absent (falsy) means server-side starts only; the field is written to IR
|
|
707
|
+
* only when the literal is `true`, so agent-free and non-opted-in output is
|
|
708
|
+
* byte-identical.
|
|
709
|
+
*/
|
|
710
|
+
publicRun?: boolean;
|
|
711
|
+
/**
|
|
712
|
+
* Whether the definition opted into a real-time voice session via a `voice`
|
|
713
|
+
* block on `defineAgent({ voice: … })`. Unlike the durable loop (a Workflow),
|
|
714
|
+
* the voice path IS a Durable Object — so when this is `true` the emitter
|
|
715
|
+
* generates the `voiceClassName` `VoiceSessionDO` subclass and the
|
|
716
|
+
* `api.agents.{name}Voice` client reference, and the config layer reconciles
|
|
717
|
+
* a `durable_objects` binding (`voiceBindingName`) + `new_sqlite_classes`
|
|
718
|
+
* migration. Written to IR only when the literal is present, so voice-free
|
|
719
|
+
* agents (and agent-free projects) stay byte-identical.
|
|
720
|
+
*/
|
|
721
|
+
voice?: boolean;
|
|
722
|
+
/** The voice DO's Cloudflare `DurableObjectNamespace` binding name, e.g. `VOICE_SUPPORT`. Present only when `voice`. */
|
|
723
|
+
voiceBindingName?: string;
|
|
724
|
+
/** Generated `VoiceSessionDO` subclass name, e.g. `SupportVoiceDO`. Present only when `voice`. */
|
|
725
|
+
voiceClassName?: string;
|
|
726
|
+
}
|
|
727
|
+
/** One durable step call lifted from a workflow handler body (the use side of {@link WorkflowIR.steps}). */
|
|
728
|
+
interface WorkflowStepIR {
|
|
729
|
+
/** 1-based line of the durable step call. */
|
|
730
|
+
line: number;
|
|
731
|
+
/** The native step method invoked: `do` / `sleep` / `sleepUntil` / `waitForEvent`. */
|
|
732
|
+
method: string;
|
|
733
|
+
/** The step's static label (the first string-literal argument). */
|
|
312
734
|
name: string;
|
|
313
735
|
}
|
|
314
736
|
/**
|
|
315
|
-
* A queue lifted from a `defineQueue()` export in `lunora/queues.ts`. Carries
|
|
316
|
-
* what the emitters and the config layer need to wire the typed `ctx.queues`
|
|
317
|
-
* producer, the generated worker `queue()` dispatch, and the wrangler
|
|
318
|
-
* `queues.producers[]` / `queues.consumers[]` entries. Like workflows, a queue
|
|
319
|
-
* is NOT a Durable Object — wrangler gets only `queues.*` entries. Names are
|
|
320
|
-
* derived via `@lunora/queue`'s shared helpers so codegen and the config layer
|
|
321
|
-
* can never disagree.
|
|
322
|
-
*/
|
|
737
|
+
* A queue lifted from a `defineQueue()` export in `lunora/queues.ts`. Carries
|
|
738
|
+
* what the emitters and the config layer need to wire the typed `ctx.queues`
|
|
739
|
+
* producer, the generated worker `queue()` dispatch, and the wrangler
|
|
740
|
+
* `queues.producers[]` / `queues.consumers[]` entries. Like workflows, a queue
|
|
741
|
+
* is NOT a Durable Object — wrangler gets only `queues.*` entries. Names are
|
|
742
|
+
* derived via `@lunora/queue`'s shared helpers so codegen and the config layer
|
|
743
|
+
* can never disagree.
|
|
744
|
+
*/
|
|
323
745
|
interface QueueIR {
|
|
324
746
|
/** The Cloudflare `Queue` producer binding name, e.g. `QUEUE_EMAIL`. */
|
|
325
747
|
bindingName: string;
|
|
@@ -328,10 +750,10 @@ interface QueueIR {
|
|
|
328
750
|
/** How the queue is consumed: `"push"` (a worker `queue()` handler) or `"pull"` (external HTTP). */
|
|
329
751
|
mode: "pull" | "push";
|
|
330
752
|
/**
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
753
|
+
* The stable wrangler queue name (`queues.producers[].queue`). Defaults to
|
|
754
|
+
* the kebab-cased export name (`emailQueue` → `email-queue`); a static
|
|
755
|
+
* `name:` literal in the definition overrides it.
|
|
756
|
+
*/
|
|
335
757
|
name: string;
|
|
336
758
|
/** Push-consumer batch/retry tuning, mirrored onto the wrangler `queues.consumers[]` entry. */
|
|
337
759
|
tuning: {
|
|
@@ -343,18 +765,42 @@ interface QueueIR {
|
|
|
343
765
|
};
|
|
344
766
|
}
|
|
345
767
|
/**
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
* the
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
|
|
353
|
-
|
|
768
|
+
* The feature-flag provider declared by the default export of `lunora/flags.ts`
|
|
769
|
+
* (`defineFlags({ provider, … })`). Discovery is **metadata-only** — codegen
|
|
770
|
+
* imports the real module at runtime for the provider value; this IR exists so
|
|
771
|
+
* the config layer can reconcile/validate the wrangler `flagship` binding when
|
|
772
|
+
* the app uses Flagship in binding mode. A `custom` provider (any other
|
|
773
|
+
* OpenFeature factory) carries no binding to reconcile.
|
|
774
|
+
*/
|
|
775
|
+
interface FlagsIR {
|
|
776
|
+
/**
|
|
777
|
+
* The wrangler `flagship[].binding` name — set **only** for a flagship
|
|
778
|
+
* `provider` in binding mode (`flagshipProvider({ binding: "FLAGS" })`). The
|
|
779
|
+
* config layer hints/validates a matching `flagship` binding from this.
|
|
780
|
+
*/
|
|
781
|
+
bindingName?: string;
|
|
782
|
+
/**
|
|
783
|
+
* Flagship operating mode — `"binding"` (wrangler binding, needs a
|
|
784
|
+
* `flagship` entry) or `"http"` (no binding); `undefined` for a `custom`
|
|
785
|
+
* provider or when the mode can't be read statically.
|
|
786
|
+
*/
|
|
787
|
+
mode?: "binding" | "http";
|
|
788
|
+
/** `"flagship"` when the provider is `flagshipProvider(...)`, else `"custom"` (any other OpenFeature provider factory). */
|
|
789
|
+
provider: "custom" | "flagship";
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* A `ctx.workflows.get("name")…` call discovered in a function body — the
|
|
793
|
+
* use-site analog of {@link WorkflowIR} (which is the declaration side). Feeds
|
|
794
|
+
* the `workflow_unused` lint (a declared workflow with zero call sites) and the
|
|
795
|
+
* `workflow_unknown_target` lint (a `.get("x")` whose `x` isn't declared — a
|
|
796
|
+
* typo catcher). {@link WorkflowCallIR.workflow} is `""` when the `get(...)`
|
|
797
|
+
* argument is not a string literal (a dynamic name — which suppresses the
|
|
798
|
+
* unused-workflow heuristic rather than producing a false positive).
|
|
799
|
+
*/
|
|
354
800
|
interface WorkflowCallIR {
|
|
355
801
|
/** Export binding name of the function performing the call, e.g. `create`. */
|
|
356
802
|
exportName: string;
|
|
357
|
-
/** Source file relative to
|
|
803
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension (the api namespace). */
|
|
358
804
|
file: string;
|
|
359
805
|
/** 1-based line of the `get(...)` call. */
|
|
360
806
|
line: number;
|
|
@@ -362,14 +808,24 @@ interface WorkflowCallIR {
|
|
|
362
808
|
workflow: string;
|
|
363
809
|
}
|
|
364
810
|
/**
|
|
365
|
-
* A `ctx.db.query("table")…` read discovered in a function body, reduced to what
|
|
366
|
-
* the
|
|
367
|
-
*
|
|
368
|
-
* `query(...)` argument is not a string literal (a
|
|
369
|
-
|
|
811
|
+
* A `ctx.db.query("table")…` read discovered in a function body, reduced to what
|
|
812
|
+
* the query advisor lints need: which table, whether the chain narrows with an
|
|
813
|
+
* index, whether it filters, and which terminal materializes the result.
|
|
814
|
+
* `table` is `""` when the `query(...)` argument is not a string literal (a
|
|
815
|
+
* dynamic table — not lintable).
|
|
816
|
+
*/
|
|
370
817
|
interface QueryReadIR {
|
|
371
|
-
/**
|
|
818
|
+
/** Exported procedure the read sits in, or `""` at module scope. */
|
|
819
|
+
exportName: string;
|
|
820
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
372
821
|
file: string;
|
|
822
|
+
/**
|
|
823
|
+
* True when the chain's `.filter()` predicate compares `_id`
|
|
824
|
+
* (`(d) => d._id === args.id`) — a full scan for a row that `ctx.db.get`
|
|
825
|
+
* addresses directly. Optional so a feeder predating this field still
|
|
826
|
+
* typechecks; absent is treated as "not a primary-key filter".
|
|
827
|
+
*/
|
|
828
|
+
filtersPrimaryKey?: boolean;
|
|
373
829
|
/** The chain calls `.filter(...)`. */
|
|
374
830
|
hasFilter: boolean;
|
|
375
831
|
/** The chain narrows with `.withIndex(...)` or `.withSearchIndex(...)`. */
|
|
@@ -378,18 +834,30 @@ interface QueryReadIR {
|
|
|
378
834
|
line: number;
|
|
379
835
|
/** Queried table name, or `""` when the argument is not a string literal. */
|
|
380
836
|
table: string;
|
|
837
|
+
/**
|
|
838
|
+
* The materializing call the chain ends in — `"collect"`, `"take"`,
|
|
839
|
+
* `"paginate"`, `"first"`, `"unique"`, … — i.e. how much of the narrowed set
|
|
840
|
+
* the read actually loads.
|
|
841
|
+
*
|
|
842
|
+
* `undefined` when the chain reaches no recognised terminal (a reader passed
|
|
843
|
+
* on, a bare `query(...)`) AND when a feeder predating this field produced
|
|
844
|
+
* the read. The two are deliberately not distinguished: no consumer could act
|
|
845
|
+
* on the difference, so the terminal-shaped lints skip the read either way
|
|
846
|
+
* rather than guessing a terminal.
|
|
847
|
+
*/
|
|
848
|
+
terminal?: string;
|
|
381
849
|
}
|
|
382
850
|
/**
|
|
383
|
-
* A `ctx.authApi
|
|
384
|
-
* to the exported function (and its file = api namespace) that performs it.
|
|
385
|
-
* Structurally identical to `AdvisorAuthApiCall` so it passes straight through
|
|
386
|
-
* to the advisor lint without conversion, exactly as `InsertWriteIR` does for
|
|
387
|
-
* `AdvisorInsertWrite`.
|
|
388
|
-
*/
|
|
851
|
+
* A `ctx.authApi.<method>(...)` call discovered in a function body, attributed
|
|
852
|
+
* to the exported function (and its file = api namespace) that performs it.
|
|
853
|
+
* Structurally identical to `AdvisorAuthApiCall` so it passes straight through
|
|
854
|
+
* to the advisor lint without conversion, exactly as `InsertWriteIR` does for
|
|
855
|
+
* `AdvisorInsertWrite`.
|
|
856
|
+
*/
|
|
389
857
|
interface AuthApiCallIR {
|
|
390
858
|
/** Export binding name of the function performing the call, e.g. "createOrg". */
|
|
391
859
|
exportName: string;
|
|
392
|
-
/** Source file relative to
|
|
860
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
393
861
|
file: string;
|
|
394
862
|
/** True when the call's argument object includes a `headers` property. */
|
|
395
863
|
hasHeaders: boolean;
|
|
@@ -399,16 +867,16 @@ interface AuthApiCallIR {
|
|
|
399
867
|
method: string;
|
|
400
868
|
}
|
|
401
869
|
/**
|
|
402
|
-
* A `ctx.db.insert("table", …)` write discovered in a function body, attributed
|
|
403
|
-
* to the exported function (and its file = api namespace) that performs it — the
|
|
404
|
-
* write-side analog of {@link QueryReadIR}. Lets tooling wire a table's write
|
|
405
|
-
* action by behavior (which function inserts into it) rather than by naming.
|
|
406
|
-
* {@link InsertWriteIR.table} is `""` when the argument is not a string literal.
|
|
407
|
-
*/
|
|
870
|
+
* A `ctx.db.insert("table", …)` write discovered in a function body, attributed
|
|
871
|
+
* to the exported function (and its file = api namespace) that performs it — the
|
|
872
|
+
* write-side analog of {@link QueryReadIR}. Lets tooling wire a table's write
|
|
873
|
+
* action by behavior (which function inserts into it) rather than by naming.
|
|
874
|
+
* {@link InsertWriteIR.table} is `""` when the argument is not a string literal.
|
|
875
|
+
*/
|
|
408
876
|
interface InsertWriteIR {
|
|
409
877
|
/** Export binding name of the function performing the insert, e.g. "send". */
|
|
410
878
|
exportName: string;
|
|
411
|
-
/** Source file relative to
|
|
879
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension (the api namespace). */
|
|
412
880
|
file: string;
|
|
413
881
|
/** 1-based line of the `insert(...)` call. */
|
|
414
882
|
line: number;
|
|
@@ -416,20 +884,20 @@ interface InsertWriteIR {
|
|
|
416
884
|
table: string;
|
|
417
885
|
}
|
|
418
886
|
/**
|
|
419
|
-
* A non-deterministic API call (`Date.now`, `Math.random`, `crypto.randomUUID`,
|
|
420
|
-
* `crypto.getRandomValues`, `fetch`) discovered lexically inside a `query(...)`
|
|
421
|
-
* or `mutation(...)` handler body — the `nondeterministic_query_mutation` lint
|
|
422
|
-
* input. Structurally identical to `AdvisorNondeterministicCall` so values pass
|
|
423
|
-
* straight through to the advisor without conversion, exactly as `AuthApiCallIR`
|
|
424
|
-
* does for `AdvisorAuthApiCall`. `action(...)` handlers are never recorded —
|
|
425
|
-
* actions are the determinism escape hatch.
|
|
426
|
-
*/
|
|
887
|
+
* A non-deterministic API call (`Date.now`, `Math.random`, `crypto.randomUUID`,
|
|
888
|
+
* `crypto.getRandomValues`, `fetch`) discovered lexically inside a `query(...)`
|
|
889
|
+
* or `mutation(...)` handler body — the `nondeterministic_query_mutation` lint
|
|
890
|
+
* input. Structurally identical to `AdvisorNondeterministicCall` so values pass
|
|
891
|
+
* straight through to the advisor without conversion, exactly as `AuthApiCallIR`
|
|
892
|
+
* does for `AdvisorAuthApiCall`. `action(...)` handlers are never recorded —
|
|
893
|
+
* actions are the determinism escape hatch.
|
|
894
|
+
*/
|
|
427
895
|
interface NondeterministicCallIR {
|
|
428
896
|
/** The non-deterministic API invoked, e.g. `Date.now` / `Math.random` / `crypto.randomUUID` / `fetch`. */
|
|
429
897
|
callee: string;
|
|
430
898
|
/** Export binding name of the function performing the call, e.g. `sendMessage`. */
|
|
431
899
|
exportName: string;
|
|
432
|
-
/** Source file relative to
|
|
900
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension (the api namespace). */
|
|
433
901
|
file: string;
|
|
434
902
|
/** Which procedure kind the call lives in — only `query`/`mutation` handlers are recorded. */
|
|
435
903
|
kind: "mutation" | "query";
|
|
@@ -437,13 +905,13 @@ interface NondeterministicCallIR {
|
|
|
437
905
|
line: number;
|
|
438
906
|
}
|
|
439
907
|
/**
|
|
440
|
-
* One `ctx.r2sql` access lexically inside a `query`/`mutation` handler — the
|
|
441
|
-
* `r2sql_outside_action` advisor lint input. Structurally identical to the
|
|
442
|
-
* advisor's `AdvisorR2sqlCall` (same field set) so values pass straight through
|
|
443
|
-
* `lintSchema` without conversion, exactly as `NondeterministicCallIR` does.
|
|
444
|
-
* Only `query`/`mutation` handlers are recorded; `action(...)` is the intended
|
|
445
|
-
* home for `ctx.r2sql` and is skipped.
|
|
446
|
-
*/
|
|
908
|
+
* One `ctx.r2sql` access lexically inside a `query`/`mutation` handler — the
|
|
909
|
+
* `r2sql_outside_action` advisor lint input. Structurally identical to the
|
|
910
|
+
* advisor's `AdvisorR2sqlCall` (same field set) so values pass straight through
|
|
911
|
+
* `lintSchema` without conversion, exactly as `NondeterministicCallIR` does.
|
|
912
|
+
* Only `query`/`mutation` handlers are recorded; `action(...)` is the intended
|
|
913
|
+
* home for `ctx.r2sql` and is skipped.
|
|
914
|
+
*/
|
|
447
915
|
interface R2sqlCallIR {
|
|
448
916
|
/** The accessed `ctx.r2sql` surface, e.g. `ctx.r2sql.query` / `ctx.r2sql.from`. */
|
|
449
917
|
callee: string;
|
|
@@ -457,21 +925,21 @@ interface R2sqlCallIR {
|
|
|
457
925
|
line: number;
|
|
458
926
|
}
|
|
459
927
|
/**
|
|
460
|
-
* Per-procedure RLS usage snapshot, produced by `discoverRlsProcedures` for the
|
|
461
|
-
* `rls_uncovered_table` advisor lint. Structurally identical to
|
|
462
|
-
* `AdvisorRlsProcedure` (they share the same field set) so values pass straight
|
|
463
|
-
* through to the advisor without conversion, exactly as `AuthApiCallIR` does for
|
|
464
|
-
* `AdvisorAuthApiCall`.
|
|
465
|
-
*/
|
|
928
|
+
* Per-procedure RLS usage snapshot, produced by `discoverRlsProcedures` for the
|
|
929
|
+
* `rls_uncovered_table` advisor lint. Structurally identical to
|
|
930
|
+
* `AdvisorRlsProcedure` (they share the same field set) so values pass straight
|
|
931
|
+
* through to the advisor without conversion, exactly as `AuthApiCallIR` does for
|
|
932
|
+
* `AdvisorAuthApiCall`.
|
|
933
|
+
*/
|
|
466
934
|
interface RlsProcedureIR {
|
|
467
935
|
/** Export binding name of the procedure (e.g. `listDocuments`). */
|
|
468
936
|
exportName: string;
|
|
469
|
-
/** Source file relative to
|
|
937
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
470
938
|
file: string;
|
|
471
939
|
/**
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
940
|
+
* Table names extracted from the `rls(policies)` array literal. Empty when the
|
|
941
|
+
* policies argument is not a statically-readable array literal.
|
|
942
|
+
*/
|
|
475
943
|
rlsTables: string[];
|
|
476
944
|
/** Tables read by the procedure via `ctx.db.query/findMany/findFirst/…`. */
|
|
477
945
|
tablesRead: string[];
|
|
@@ -483,22 +951,22 @@ interface RlsProcedureIR {
|
|
|
483
951
|
visibility: "internal" | "public";
|
|
484
952
|
}
|
|
485
953
|
/**
|
|
486
|
-
* One procedure reduced to the facts the `mask_uncovered_pii_column` lint needs:
|
|
487
|
-
* whether its builder chain includes `.use(mask(...))`, which `(table, column)`
|
|
488
|
-
* pairs that mask declares, and which tables the procedure reads/writes. The
|
|
489
|
-
* column-level analogue of {@link RlsProcedureIR}. Structurally identical to
|
|
490
|
-
* `AdvisorMaskProcedure` so values pass straight through without conversion.
|
|
491
|
-
*/
|
|
954
|
+
* One procedure reduced to the facts the `mask_uncovered_pii_column` lint needs:
|
|
955
|
+
* whether its builder chain includes `.use(mask(...))`, which `(table, column)`
|
|
956
|
+
* pairs that mask declares, and which tables the procedure reads/writes. The
|
|
957
|
+
* column-level analogue of {@link RlsProcedureIR}. Structurally identical to
|
|
958
|
+
* `AdvisorMaskProcedure` so values pass straight through without conversion.
|
|
959
|
+
*/
|
|
492
960
|
interface MaskProcedureIR {
|
|
493
961
|
/** Export binding name of the procedure (e.g. `listUsers`). */
|
|
494
962
|
exportName: string;
|
|
495
|
-
/** Source file relative to
|
|
963
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
496
964
|
file: string;
|
|
497
965
|
/**
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
966
|
+
* `(table, column)` pairs this procedure's `mask(policies)` object literal
|
|
967
|
+
* declares. Empty when the policies argument is not a statically-readable
|
|
968
|
+
* object literal (conservative: `usesMask` is still `true`).
|
|
969
|
+
*/
|
|
502
970
|
maskColumns: {
|
|
503
971
|
column: string;
|
|
504
972
|
table: string;
|
|
@@ -513,14 +981,14 @@ interface MaskProcedureIR {
|
|
|
513
981
|
visibility: "internal" | "public";
|
|
514
982
|
}
|
|
515
983
|
/**
|
|
516
|
-
* One masked column surfaced to the studio's data-browser mask preview: a
|
|
517
|
-
* `(table, column)` pair plus the declared {@link MaskProcedureIR} strategy so
|
|
518
|
-
* the preview can pick redact-vs-hash-vs-custom rendering. Aggregated across the
|
|
519
|
-
* project's `.use(mask(...))` chains by `discoverMaskMetadata`; the descriptive
|
|
520
|
-
* twin of {@link RlsPolicyIR}. `"custom"` covers any non-string strategy (a
|
|
521
|
-
* `(value, ctx) => …` function) — its logic is an opaque closure, never read by
|
|
522
|
-
* the UI; the preview renders a fixed sentinel for it.
|
|
523
|
-
*/
|
|
984
|
+
* One masked column surfaced to the studio's data-browser mask preview: a
|
|
985
|
+
* `(table, column)` pair plus the declared {@link MaskProcedureIR} strategy so
|
|
986
|
+
* the preview can pick redact-vs-hash-vs-custom rendering. Aggregated across the
|
|
987
|
+
* project's `.use(mask(...))` chains by `discoverMaskMetadata`; the descriptive
|
|
988
|
+
* twin of {@link RlsPolicyIR}. `"custom"` covers any non-string strategy (a
|
|
989
|
+
* `(value, ctx) => …` function) — its logic is an opaque closure, never read by
|
|
990
|
+
* the UI; the preview renders a fixed sentinel for it.
|
|
991
|
+
*/
|
|
524
992
|
interface MaskColumnMetadataIR {
|
|
525
993
|
/** Column the mask policy redacts. */
|
|
526
994
|
column: string;
|
|
@@ -530,24 +998,48 @@ interface MaskColumnMetadataIR {
|
|
|
530
998
|
table: string;
|
|
531
999
|
}
|
|
532
1000
|
/**
|
|
533
|
-
* Schema-wide masking metadata the codegen emits into the generated ShardDO so
|
|
534
|
-
* the studio's data-browser mask toggle can preview what a non-privileged caller
|
|
535
|
-
* would see. Aggregated across every `.use(mask(...))` chain in the project —
|
|
536
|
-
* purely descriptive (table + column + strategy), never the masking closure. The
|
|
537
|
-
* column-level analogue of {@link RlsMetadataIR}.
|
|
538
|
-
*/
|
|
1001
|
+
* Schema-wide masking metadata the codegen emits into the generated ShardDO so
|
|
1002
|
+
* the studio's data-browser mask toggle can preview what a non-privileged caller
|
|
1003
|
+
* would see. Aggregated across every `.use(mask(...))` chain in the project —
|
|
1004
|
+
* purely descriptive (table + column + strategy), never the masking closure. The
|
|
1005
|
+
* column-level analogue of {@link RlsMetadataIR}.
|
|
1006
|
+
*/
|
|
539
1007
|
interface MaskMetadataIR {
|
|
540
1008
|
/** Every statically-discovered masked column, deduped by `(table, column)` (first declaration wins). */
|
|
541
1009
|
columns: MaskColumnMetadataIR[];
|
|
542
1010
|
}
|
|
543
1011
|
/**
|
|
544
|
-
* One
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
* `
|
|
550
|
-
|
|
1012
|
+
* One masked column whose `mask(policies)` strategy is a statically-known
|
|
1013
|
+
* literal (`"hash"` or `"redact"`) — the `mask_weak_hash_strategy_on_pii` lint
|
|
1014
|
+
* input. Unlike {@link MaskColumnMetadataIR} (app-wide, deduped by `(table,
|
|
1015
|
+
* column)`, studio-preview evidence), this is per declaration site (file + line
|
|
1016
|
+
* + enclosing export), undeduped, so the lint can point at the exact
|
|
1017
|
+
* `mask(...)` call that applies a weak strategy. A `MaskFn` (custom, non-literal)
|
|
1018
|
+
* strategy carries no lint-relevant signal and is never recorded here.
|
|
1019
|
+
* Structurally identical to `AdvisorMaskStrategy`.
|
|
1020
|
+
*/
|
|
1021
|
+
interface MaskStrategyIR {
|
|
1022
|
+
/** Masked column name. */
|
|
1023
|
+
column: string;
|
|
1024
|
+
/** Export binding name of the procedure whose `.use(mask(...))` chain declared this column, or `"<module>"` when declared at file scope. */
|
|
1025
|
+
exportName: string;
|
|
1026
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1027
|
+
file: string;
|
|
1028
|
+
/** 1-based line of the masked column's strategy property. */
|
|
1029
|
+
line: number;
|
|
1030
|
+
/** The statically-known strategy literal: `"hash"` or `"redact"`. */
|
|
1031
|
+
strategy: string;
|
|
1032
|
+
/** Logical table the masked column belongs to. */
|
|
1033
|
+
table: string;
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* One statically-readable policy entry from an `rls([...])` array literal,
|
|
1037
|
+
* surfaced to the studio's read-only RLS inspector via the generated
|
|
1038
|
+
* `rlsPolicies()` hook. Captures the policy's `table` + `on` operation and the
|
|
1039
|
+
* procedure it guards — never the `when` predicate, which is an opaque JS
|
|
1040
|
+
* closure (its logic stays in code, not the UI). Produced by
|
|
1041
|
+
* `discoverRlsProcedures` alongside the lint IR.
|
|
1042
|
+
*/
|
|
551
1043
|
interface RlsPolicyIR {
|
|
552
1044
|
/** Source file (relative to `lunora/`, without extension) the policy is declared in. */
|
|
553
1045
|
file: string;
|
|
@@ -559,12 +1051,12 @@ interface RlsPolicyIR {
|
|
|
559
1051
|
table: string;
|
|
560
1052
|
}
|
|
561
1053
|
/**
|
|
562
|
-
* One statically-readable role entry from an `rls(policies, { roles: [...] })`
|
|
563
|
-
* call, surfaced to the studio's RLS inspector. Captures the role's `name`,
|
|
564
|
-
* optional `description`, and the names of the permissions it grants (string
|
|
565
|
-
* literals or `definePermission("name")` calls). Produced by
|
|
566
|
-
* `discoverRlsProcedures`.
|
|
567
|
-
*/
|
|
1054
|
+
* One statically-readable role entry from an `rls(policies, { roles: [...] })`
|
|
1055
|
+
* call, surfaced to the studio's RLS inspector. Captures the role's `name`,
|
|
1056
|
+
* optional `description`, and the names of the permissions it grants (string
|
|
1057
|
+
* literals or `definePermission("name")` calls). Produced by
|
|
1058
|
+
* `discoverRlsProcedures`.
|
|
1059
|
+
*/
|
|
568
1060
|
interface RlsRoleIR {
|
|
569
1061
|
/** Optional human-readable description from `defineRole(name, { description })`. */
|
|
570
1062
|
description?: string;
|
|
@@ -574,11 +1066,11 @@ interface RlsRoleIR {
|
|
|
574
1066
|
permissions: string[];
|
|
575
1067
|
}
|
|
576
1068
|
/**
|
|
577
|
-
* Schema-wide RLS metadata the codegen emits into the generated ShardDO so the
|
|
578
|
-
* studio's read-only inspector can list, per table, which policies guard it and
|
|
579
|
-
* what roles are defined. Aggregated across every `.use(rls(...))` chain in the
|
|
580
|
-
* project — purely descriptive, never the predicate logic.
|
|
581
|
-
*/
|
|
1069
|
+
* Schema-wide RLS metadata the codegen emits into the generated ShardDO so the
|
|
1070
|
+
* studio's read-only inspector can list, per table, which policies guard it and
|
|
1071
|
+
* what roles are defined. Aggregated across every `.use(rls(...))` chain in the
|
|
1072
|
+
* project — purely descriptive, never the predicate logic.
|
|
1073
|
+
*/
|
|
582
1074
|
interface RlsMetadataIR {
|
|
583
1075
|
/** Every statically-discovered policy `(table, on, procedure)` entry. */
|
|
584
1076
|
policies: RlsPolicyIR[];
|
|
@@ -597,27 +1089,36 @@ interface StorageRuleIR {
|
|
|
597
1089
|
procedure: string;
|
|
598
1090
|
}
|
|
599
1091
|
/**
|
|
600
|
-
* Schema-wide storage-access-rule metadata emitted into the generated ShardDO so
|
|
601
|
-
* the studio's read-only inspector can list, per bucket, which operations are
|
|
602
|
-
* gated and under what key prefix. Aggregated across every
|
|
603
|
-
* `.use(storageRules(...))` chain — descriptive only, never the predicate logic.
|
|
604
|
-
*/
|
|
1092
|
+
* Schema-wide storage-access-rule metadata emitted into the generated ShardDO so
|
|
1093
|
+
* the studio's read-only inspector can list, per bucket, which operations are
|
|
1094
|
+
* gated and under what key prefix. Aggregated across every
|
|
1095
|
+
* `.use(storageRules(...))` chain — descriptive only, never the predicate logic.
|
|
1096
|
+
*/
|
|
605
1097
|
interface StorageRulesMetadataIR {
|
|
606
1098
|
rules: StorageRuleIR[];
|
|
607
1099
|
}
|
|
608
1100
|
/**
|
|
609
|
-
* A typed REST route declared with the `httpRoute
|
|
610
|
-
* `@lunora/server` and mounted on `httpRouter()`. Captured statically from the
|
|
611
|
-
* builder chain so the OpenAPI emitter can render a real `paths` entry: the verb
|
|
612
|
-
* + path become the operation's method + URL, and the accumulated validator maps
|
|
613
|
-
* become its query parameters, path parameters, and request body.
|
|
614
|
-
*/
|
|
1101
|
+
* A typed REST route declared with the `httpRoute.<verb>("/path")…` builder in
|
|
1102
|
+
* `@lunora/server` and mounted on `httpRouter()`. Captured statically from the
|
|
1103
|
+
* builder chain so the OpenAPI emitter can render a real `paths` entry: the verb
|
|
1104
|
+
* + path become the operation's method + URL, and the accumulated validator maps
|
|
1105
|
+
* become its query parameters, path parameters, and request body.
|
|
1106
|
+
*/
|
|
615
1107
|
interface HttpRouteIR {
|
|
616
1108
|
/** `v.*` validators decoding the JSON request body (`.body({...})`), keyed by field. */
|
|
617
1109
|
body: Record<string, ValidatorIR>;
|
|
1110
|
+
/**
|
|
1111
|
+
* Rendered TS type of one SSE chunk — the `R` the `.stream(handler)`
|
|
1112
|
+
* handler yields — inferred from the handler via the type checker. Present
|
|
1113
|
+
* only when {@link HttpRouteIR.stream} is `true`; `"unknown"` when the
|
|
1114
|
+
* checker can't resolve enough context. Feeds the emitted
|
|
1115
|
+
* `HttpStreamRef<Chunk, …>` so the chunk type flows to the client.
|
|
1116
|
+
* @experimental Part of the HTTP-SSE stream surface (the `httpStreams.*` emission).
|
|
1117
|
+
*/
|
|
1118
|
+
chunkType?: string;
|
|
618
1119
|
/** Export binding name of the route handler (used only for diagnostics / dedupe). */
|
|
619
1120
|
exportName: string;
|
|
620
|
-
/** Path relative to
|
|
1121
|
+
/** Path relative to `<projectRoot>/lunora/` without extension, e.g. "http". */
|
|
621
1122
|
filePath: string;
|
|
622
1123
|
/** HTTP verb the route binds to (uppercased), e.g. `"GET"`. */
|
|
623
1124
|
method: string;
|
|
@@ -625,7 +1126,7 @@ interface HttpRouteIR {
|
|
|
625
1126
|
output?: ValidatorIR;
|
|
626
1127
|
/** `v.*` validators decoding the hono path params (`.params({...})`), keyed by `:name`. */
|
|
627
1128
|
params: Record<string, ValidatorIR>;
|
|
628
|
-
/** The route path passed to `httpRoute
|
|
1129
|
+
/** The route path passed to `httpRoute.<verb>(path)`, e.g. `/api/todos/:id`. */
|
|
629
1130
|
path: string;
|
|
630
1131
|
/** `v.*` validators decoding the URL query string (`.searchParams({...})`), keyed by name. */
|
|
631
1132
|
searchParams: Record<string, ValidatorIR>;
|
|
@@ -633,28 +1134,69 @@ interface HttpRouteIR {
|
|
|
633
1134
|
stream: boolean;
|
|
634
1135
|
}
|
|
635
1136
|
/**
|
|
636
|
-
* Per-procedure protective-middleware snapshot, produced by
|
|
637
|
-
* `discoverProcedureMiddleware` for the security lints
|
|
638
|
-
* (`public_mutation_without_ratelimit`, `user_creating_mutation_without_captcha`).
|
|
639
|
-
* Records which `.use(...)` guards a procedure's builder chain carries plus the
|
|
640
|
-
* behavioural facts that decide whether a guard is *expected* (does it write a
|
|
641
|
-
* user/session table, does it send mail). `protectPublic({ rateLimit, captcha })`
|
|
642
|
-
* is unwrapped: the bundle's object-literal keys set `usesRateLimit`/`usesCaptcha`
|
|
643
|
-
* exactly as the individual `.use(rateLimit(...))` / `.use(verifyTurnstile(...))`
|
|
644
|
-
* steps would. Structurally identical to `AdvisorProcedureProtection` so values
|
|
645
|
-
* pass straight through to the advisor without conversion.
|
|
646
|
-
*/
|
|
1137
|
+
* Per-procedure protective-middleware snapshot, produced by
|
|
1138
|
+
* `discoverProcedureMiddleware` for the security lints
|
|
1139
|
+
* (`public_mutation_without_ratelimit`, `user_creating_mutation_without_captcha`).
|
|
1140
|
+
* Records which `.use(...)` guards a procedure's builder chain carries plus the
|
|
1141
|
+
* behavioural facts that decide whether a guard is *expected* (does it write a
|
|
1142
|
+
* user/session table, does it send mail). `protectPublic({ rateLimit, captcha })`
|
|
1143
|
+
* is unwrapped: the bundle's object-literal keys set `usesRateLimit`/`usesCaptcha`
|
|
1144
|
+
* exactly as the individual `.use(rateLimit(...))` / `.use(verifyTurnstile(...))`
|
|
1145
|
+
* steps would. Structurally identical to `AdvisorProcedureProtection` so values
|
|
1146
|
+
* pass straight through to the advisor without conversion.
|
|
1147
|
+
*/
|
|
647
1148
|
interface ProcedureMiddlewareIR {
|
|
648
|
-
/**
|
|
649
|
-
|
|
1149
|
+
/**
|
|
1150
|
+
* `true` when the handler body could be read statically — an inline function
|
|
1151
|
+
* expression/arrow, or a same-file identifier resolved to one. `false` for a
|
|
1152
|
+
* genuinely cross-file handler (an imported function, or an identifier that
|
|
1153
|
+
* doesn't resolve in this file), in which case every behavioural fact below
|
|
1154
|
+
* is `undefined` rather than a false "not observed" — the feeder never saw
|
|
1155
|
+
* the body, so it has nothing to report.
|
|
1156
|
+
*/
|
|
1157
|
+
analyzableBody: boolean;
|
|
1158
|
+
/** `true` when the handler (or a helper inside it) references `ctx.mail` / `ctx.email`. `undefined` when `analyzableBody` is `false`. */
|
|
1159
|
+
callsMail?: boolean;
|
|
1160
|
+
/** `true` when the handler emits a structured observability event (`ctx.log` / `ctx.span` / `ctx.trace`). */
|
|
1161
|
+
emitsEvent?: boolean;
|
|
1162
|
+
/** `true` when a `// lunora-advisor-exempt` directive sits above the export. */
|
|
1163
|
+
exempt: boolean;
|
|
1164
|
+
/** The `-- reason` from that directive, or `""`. */
|
|
1165
|
+
exemptReason: string;
|
|
650
1166
|
/** Export binding name of the procedure (e.g. `signUp`). */
|
|
651
1167
|
exportName: string;
|
|
652
|
-
/**
|
|
1168
|
+
/** `true` when the handler fans work out to a privileged, cost-bearing dispatch surface (scheduler `runAfter`/`runAt`, a queue producer send, or a workflow create). Feeds the privileged-fanout lint. `undefined` when `analyzableBody` is `false`. */
|
|
1169
|
+
fanOut?: boolean;
|
|
1170
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
653
1171
|
file: string;
|
|
654
|
-
/**
|
|
1172
|
+
/** `true` when the handler wraps work in `try`/`catch`. */
|
|
1173
|
+
handlesErrors?: boolean;
|
|
1174
|
+
/**
|
|
1175
|
+
* `true` when the procedure declares an email-shaped argument (`email`,
|
|
1176
|
+
* `emailAddress`, `userEmail`, …), `false` when it provably declares none,
|
|
1177
|
+
* and **absent** when the argument list can't be read statically (a
|
|
1178
|
+
* `.input(sharedSchema)`, a spread, or a factory whose `args` comes from a
|
|
1179
|
+
* variable). Feeds `signup_mutation_without_disposable_gating`, which can
|
|
1180
|
+
* only be actioned when there is an address to gate — so "unreadable" must
|
|
1181
|
+
* stay distinguishable from "none", or the lint would clear itself on a
|
|
1182
|
+
* registration that may well expose one.
|
|
1183
|
+
*/
|
|
1184
|
+
hasEmailArg?: boolean;
|
|
655
1185
|
kind: "action" | "mutation" | "query";
|
|
1186
|
+
/** `true` when the handler reaches an outbound surface (`ctx.fetch`, mail, queues, storage, sql, ai, …) that can fail. */
|
|
1187
|
+
reachesOutbound?: boolean;
|
|
1188
|
+
/** `true` when the handler runs any AI generation, bounded or not. */
|
|
1189
|
+
runsAiGeneration?: boolean;
|
|
1190
|
+
/** `true` when the handler throws a bare `new Error(...)` rather than a coded `LunoraError`. */
|
|
1191
|
+
throwsBareError?: boolean;
|
|
1192
|
+
/** `true` when the handler runs an AI generation (`generateText`/`streamText`/`generateObject`/`streamObject`) with no `maxOutputTokens` bound in its config literal. Feeds the `ai_unbounded_generation_public` lint. `undefined` when `analyzableBody` is `false`. */
|
|
1193
|
+
unboundedAiGeneration?: boolean;
|
|
656
1194
|
/** `true` when the chain carries `.use(verifyTurnstile(...))` or a `protectPublic({ captcha })` bundle. */
|
|
657
1195
|
usesCaptcha: boolean;
|
|
1196
|
+
/** `true` when the chain carries `.use(emailGateMiddleware(...))` (`@lunora/auth`). Feeds the `signup_mutation_without_disposable_gating` lint. */
|
|
1197
|
+
usesEmailGate: boolean;
|
|
1198
|
+
/** `true` when the handler calls `ctx.db.insertManyUnsafe(...)`, bypassing validators and triggers. Feeds the `insert_many_unsafe_user_data` lint. `undefined` when `analyzableBody` is `false`. */
|
|
1199
|
+
usesInsertManyUnsafe?: boolean;
|
|
658
1200
|
/** `true` when the chain carries `.use(mask(...))`. */
|
|
659
1201
|
usesMask: boolean;
|
|
660
1202
|
/** `true` when the chain carries `.use(rateLimit(...))` or a `protectPublic({ rateLimit })` bundle. */
|
|
@@ -663,23 +1205,23 @@ interface ProcedureMiddlewareIR {
|
|
|
663
1205
|
usesRls: boolean;
|
|
664
1206
|
/** `"internal"` for `internalQuery` / `internalMutation` / `internalAction`. */
|
|
665
1207
|
visibility: "internal" | "public";
|
|
666
|
-
/** `true` when the handler inserts into a user/session/account-shaped table. */
|
|
667
|
-
writesUserTable
|
|
1208
|
+
/** `true` when the handler inserts into a user/session/account-shaped table. `undefined` when `analyzableBody` is `false`. */
|
|
1209
|
+
writesUserTable?: boolean;
|
|
668
1210
|
}
|
|
669
1211
|
/**
|
|
670
|
-
* Per-procedure argument-validator snapshot, produced by the
|
|
671
|
-
* argument-validator discoverer for the input-hardening lints
|
|
672
|
-
* (`public_arg_uses_any`, `unbounded_string_arg`). Only public procedures are
|
|
673
|
-
* recorded — internal functions take server-trusted input. Structurally identical
|
|
674
|
-
* to `AdvisorArgumentValidator` so it passes straight through to the advisor
|
|
675
|
-
* without conversion.
|
|
676
|
-
*/
|
|
1212
|
+
* Per-procedure argument-validator snapshot, produced by the
|
|
1213
|
+
* argument-validator discoverer for the input-hardening lints
|
|
1214
|
+
* (`public_arg_uses_any`, `unbounded_string_arg`). Only public procedures are
|
|
1215
|
+
* recorded — internal functions take server-trusted input. Structurally identical
|
|
1216
|
+
* to `AdvisorArgumentValidator` so it passes straight through to the advisor
|
|
1217
|
+
* without conversion.
|
|
1218
|
+
*/
|
|
677
1219
|
interface ArgumentValidatorIR {
|
|
678
1220
|
/** Arg names declared as `v.any()` (unvalidated, untyped input). */
|
|
679
1221
|
anyArgs: string[];
|
|
680
1222
|
/** Export binding name of the procedure (e.g. `updateProfile`). */
|
|
681
1223
|
exportName: string;
|
|
682
|
-
/** Source file relative to
|
|
1224
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
683
1225
|
file: string;
|
|
684
1226
|
/** 1-based line of the registration call, or `0` when unknown. */
|
|
685
1227
|
line: number;
|
|
@@ -687,13 +1229,34 @@ interface ArgumentValidatorIR {
|
|
|
687
1229
|
unboundedStringArgs: string[];
|
|
688
1230
|
}
|
|
689
1231
|
/**
|
|
690
|
-
* One
|
|
691
|
-
*
|
|
692
|
-
*
|
|
693
|
-
* Structurally identical to `
|
|
694
|
-
|
|
1232
|
+
* One factory/constructor call in `lunora/` whose config object literal a
|
|
1233
|
+
* security lint inspects for a present-or-absent key — the shared input for the
|
|
1234
|
+
* config-call security lints (payment authorize, inbound-mail verify, rate-limit
|
|
1235
|
+
* store, browser private-targets). Structurally identical to `AdvisorConfigCall`
|
|
1236
|
+
* so it passes straight through to the advisor without conversion.
|
|
1237
|
+
*/
|
|
1238
|
+
interface ConfigCallIR {
|
|
1239
|
+
/** `true` when the config argument was a static object literal the feeder could read. */
|
|
1240
|
+
analyzable: boolean;
|
|
1241
|
+
/** The factory function or constructor name at the call site, e.g. `createPayment` / `RateLimiter`. */
|
|
1242
|
+
callee: string;
|
|
1243
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1244
|
+
file: string;
|
|
1245
|
+
/** 1-based line of the call site, or `0` when unknown. */
|
|
1246
|
+
line: number;
|
|
1247
|
+
/** Keys present in the config object literal (empty when not `analyzable`). */
|
|
1248
|
+
presentKeys: string[];
|
|
1249
|
+
/** Keys in the config object literal explicitly assigned the literal `true`. */
|
|
1250
|
+
trueKeys: string[];
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* One secret-shaped string literal discovered in `lunora/` source — the
|
|
1254
|
+
* `hardcoded_secret` lint input. Complements the pre-commit `vis secrets` scan by
|
|
1255
|
+
* surfacing the same class of finding in-IDE via the studio Advisors table.
|
|
1256
|
+
* Structurally identical to `AdvisorSecretLiteral`.
|
|
1257
|
+
*/
|
|
695
1258
|
interface SecretLiteralIR {
|
|
696
|
-
/** Source file relative to
|
|
1259
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
697
1260
|
file: string;
|
|
698
1261
|
/** Heuristic that matched, e.g. `stripe_live_key` / `aws_access_key` / `pem_private_key` / `high_entropy`. */
|
|
699
1262
|
kind: string;
|
|
@@ -703,283 +1266,1701 @@ interface SecretLiteralIR {
|
|
|
703
1266
|
preview: string;
|
|
704
1267
|
}
|
|
705
1268
|
/**
|
|
706
|
-
* One `ctx.sql.query(text, …)` / `ctx.sql.unsafe(text, …)` call whose `text`
|
|
707
|
-
* argument is built in place rather than passed as a fixed statement — the
|
|
708
|
-
* `sql_injection_risk` lint input. The Hyperdrive driver binds ONLY the `params`
|
|
709
|
-
* array; the `text` string is spliced verbatim into the SQL, so a `text` assembled
|
|
710
|
-
* from a string concatenation or a substitution template literal is an injection
|
|
711
|
-
* vector. A fixed string literal / no-substitution template is safe. Structurally
|
|
712
|
-
* identical to `AdvisorSqlInterpolation`.
|
|
713
|
-
*/
|
|
1269
|
+
* One `ctx.sql.query(text, …)` / `ctx.sql.unsafe(text, …)` call whose `text`
|
|
1270
|
+
* argument is built in place rather than passed as a fixed statement — the
|
|
1271
|
+
* `sql_injection_risk` lint input. The Hyperdrive driver binds ONLY the `params`
|
|
1272
|
+
* array; the `text` string is spliced verbatim into the SQL, so a `text` assembled
|
|
1273
|
+
* from a string concatenation or a substitution template literal is an injection
|
|
1274
|
+
* vector. A fixed string literal / no-substitution template is safe. Structurally
|
|
1275
|
+
* identical to `AdvisorSqlInterpolation`.
|
|
1276
|
+
*/
|
|
714
1277
|
interface SqlInterpolationIR {
|
|
715
1278
|
/** Export binding name of the procedure performing the `ctx.sql` call. */
|
|
716
1279
|
exportName: string;
|
|
717
|
-
/** Source file relative to
|
|
1280
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
718
1281
|
file: string;
|
|
719
1282
|
/** 1-based line of the interpolation, or `0` when unknown. */
|
|
720
1283
|
line: number;
|
|
721
1284
|
}
|
|
722
1285
|
/**
|
|
723
|
-
* One
|
|
724
|
-
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1286
|
+
* One `ctx.fetch(url, …)` call inside an action whose URL argument is derived
|
|
1287
|
+
* from the handler's `args` — the `action_fetch_ssrf` lint input. `ctx.fetch` is
|
|
1288
|
+
* the action-only outbound-request escape hatch with no host allowlist, so a URL
|
|
1289
|
+
* assembled from request input is a server-side request forgery vector (cloud
|
|
1290
|
+
* metadata endpoints, internal services). Only arg-derived URLs reach here; a
|
|
1291
|
+
* fixed literal or a URL built from config/`ctx.*` is not recorded. Structurally
|
|
1292
|
+
* identical to `AdvisorArgumentDerivedFetch`.
|
|
1293
|
+
*/
|
|
1294
|
+
interface ArgumentDerivedFetchIR {
|
|
1295
|
+
/** Export binding name of the action performing the `ctx.fetch` call. */
|
|
730
1296
|
exportName: string;
|
|
731
|
-
/** Source file relative to
|
|
1297
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
732
1298
|
file: string;
|
|
733
|
-
/**
|
|
1299
|
+
/** 1-based line of the `ctx.fetch` call, or `0` when unknown. */
|
|
1300
|
+
line: number;
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* One `ctx.kv.<method>(key, …)` call whose namespace key is derived from the
|
|
1304
|
+
* handler's `args` with no server-side scoping — the `kv_unscoped_user_key_idor`
|
|
1305
|
+
* lint input. Workers KV is a single flat namespace, so a key taken straight from
|
|
1306
|
+
* request input lets any caller read, overwrite, or delete another user's entry
|
|
1307
|
+
* (IDOR). Only arg-derived, unscoped keys reach here; a fixed literal, or a key
|
|
1308
|
+
* prefixed with a server-trusted identity (`${ctx.auth.userId}:…` — references
|
|
1309
|
+
* `ctx`, so treated as scoped), is not recorded. `list` is excluded (it takes a
|
|
1310
|
+
* prefix, not a per-entry key). Structurally identical to `AdvisorKvKeyAccess`.
|
|
1311
|
+
*/
|
|
1312
|
+
interface KvKeyAccessIR {
|
|
1313
|
+
/** Export binding name of the procedure performing the `ctx.kv` access. */
|
|
1314
|
+
exportName: string;
|
|
1315
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1316
|
+
file: string;
|
|
1317
|
+
/** 1-based line of the `ctx.kv` call, or `0` when unknown. */
|
|
1318
|
+
line: number;
|
|
1319
|
+
/** The `ctx.kv` method invoked: `get` / `getRaw` / `getWithMetadata` / `put` / `delete`. */
|
|
734
1320
|
method: string;
|
|
735
|
-
/** The route path, e.g. `/admin/users`. */
|
|
736
|
-
path: string;
|
|
737
|
-
/** `true` when the handler body references an auth/session/admin guard (`ctx.auth`, `getSession`, `requireAdmin`, …). */
|
|
738
|
-
usesGuard: boolean;
|
|
739
1321
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
1322
|
+
/**
|
|
1323
|
+
* One `ctx.db` write (`insert` / `replace` / `patch` / `insertManyUnsafe`) that sets
|
|
1324
|
+
* an ownership / identity column — `userId`, `ownerId`, `tenantId`, and the like —
|
|
1325
|
+
* from the handler's `args` instead of the server-trusted identity. The
|
|
1326
|
+
* `owner_field_from_args_not_auth` lint input: the ownership column decides who a
|
|
1327
|
+
* row belongs to, so a value taken from request input lets any caller write rows
|
|
1328
|
+
* owned by another user or tenant (the act-as-any-user / cross-tenant IDOR vector).
|
|
1329
|
+
* A column stamped from `ctx.*`, or set to a fixed literal, is not recorded; only an
|
|
1330
|
+
* arg-derived identity write reaches here. Structurally identical to
|
|
1331
|
+
* `AdvisorOwnerFieldWrite`.
|
|
1332
|
+
*/
|
|
1333
|
+
/**
|
|
1334
|
+
* One branching `defineShape({ where })` / `definePolicy({ when })` predicate arm
|
|
1335
|
+
* that returns an unrestricted predicate — the `unrestricted_where_branch` lint
|
|
1336
|
+
* input. A denial arm must match NO rows (`deny()` / `{ OR: [] }`); `{}` matches
|
|
1337
|
+
* every row, so the near-miss silently replicates the whole table.
|
|
1338
|
+
*/
|
|
1339
|
+
interface UnrestrictedWhereBranchIR {
|
|
1340
|
+
/** Export binding name of the shape / policy the predicate belongs to. */
|
|
1341
|
+
exportName: string;
|
|
1342
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1343
|
+
file: string;
|
|
1344
|
+
/** Which unrestricted form was returned. */
|
|
1345
|
+
form: "empty-object" | "undefined";
|
|
1346
|
+
/** The config key carrying the predicate (`where` for a shape, `when` for a policy). */
|
|
1347
|
+
key: string;
|
|
1348
|
+
/** 1-based line of the offending returned expression. */
|
|
1349
|
+
line: number;
|
|
1350
|
+
/** The declaring call (`defineShape` / `definePolicy`). */
|
|
1351
|
+
owner: string;
|
|
1352
|
+
}
|
|
1353
|
+
interface OwnerFieldWriteIR {
|
|
1354
|
+
/** Export binding name of the procedure performing the write. */
|
|
1355
|
+
exportName: string;
|
|
1356
|
+
/** The identity column being written from `args` (e.g. `userId`). */
|
|
1357
|
+
field: string;
|
|
1358
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1359
|
+
file: string;
|
|
1360
|
+
/** 1-based line of the `ctx.db` write call, or `0` when unknown. */
|
|
1361
|
+
line: number;
|
|
1362
|
+
/** The `ctx.db` write method (`insert` / `replace` / `patch` / `insertManyUnsafe`). */
|
|
1363
|
+
method: string;
|
|
1364
|
+
/**
|
|
1365
|
+
* Visibility of the enclosing procedure. `internal` procedures are not
|
|
1366
|
+
* reachable by a caller, so the lint's premise ("any caller can write rows
|
|
1367
|
+
* owned by another user") does not hold there — see
|
|
1368
|
+
* `owner_field_from_args_not_auth`. `undefined` when the write sits outside
|
|
1369
|
+
* any recognised procedure (a bare helper).
|
|
1370
|
+
*/
|
|
1371
|
+
visibility?: "internal" | "public";
|
|
747
1372
|
}
|
|
748
1373
|
/**
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
*/
|
|
762
|
-
|
|
763
|
-
/**
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1374
|
+
* One `ctx.storage.<bucket>.<method>(key, …)` call whose R2 object key is derived
|
|
1375
|
+
* from the handler's `args` with no server-side scoping — the
|
|
1376
|
+
* `storage_key_from_user_args` lint input. The bucket read/write/URL/delete methods
|
|
1377
|
+
* key by their first argument, so an object key taken straight from request input is
|
|
1378
|
+
* object-level IDOR (read/overwrite/delete anyone's object). A key referencing a
|
|
1379
|
+
* server-trusted `ctx.*` value (e.g. `${ctx.auth.userId}/…`) is treated as scoped
|
|
1380
|
+
* and is not recorded; only an arg-derived, `ctx`-free key reaches here.
|
|
1381
|
+
* Structurally identical to `AdvisorStorageKeyAccess`.
|
|
1382
|
+
*/
|
|
1383
|
+
interface StorageKeyAccessIR {
|
|
1384
|
+
/** Export binding name of the procedure performing the storage call. */
|
|
1385
|
+
exportName: string;
|
|
1386
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1387
|
+
file: string;
|
|
1388
|
+
/** 1-based line of the storage call, or `0` when unknown. */
|
|
1389
|
+
line: number;
|
|
1390
|
+
/** The bucket method invoked with the arg-derived key, e.g. `get` / `put` / `delete` / `download`. */
|
|
1391
|
+
method: string;
|
|
1392
|
+
/**
|
|
1393
|
+
* Visibility of the enclosing procedure. `internal` procedures have no
|
|
1394
|
+
* untrusted caller by construction — see `owner_field_from_args_not_auth`'s
|
|
1395
|
+
* identical split — so `storage_key_from_user_args` drops the finding to
|
|
1396
|
+
* INFO rather than ERROR there. `undefined` when the access sits outside any
|
|
1397
|
+
* registered procedure the feeder could attribute it to.
|
|
1398
|
+
*/
|
|
1399
|
+
visibility?: "internal" | "public";
|
|
1400
|
+
}
|
|
769
1401
|
/**
|
|
770
|
-
*
|
|
771
|
-
*
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1402
|
+
* One `ctx.containers.<exportName>.get(name, …)` call whose instance key is derived
|
|
1403
|
+
* from the handler's `args` with no server-side scoping — the
|
|
1404
|
+
* `container_instance_key_from_user_input` lint input. Each container definition's
|
|
1405
|
+
* `.get(name)` accessor routes to one instance per `name`, so a key taken straight from
|
|
1406
|
+
* request input lets any caller reach another tenant's container (a cross-tenant IDOR). A
|
|
1407
|
+
* fixed literal key, or one derived from a server-trusted identity (`${ctx.auth.userId}` —
|
|
1408
|
+
* references `ctx`, so treated as scoped), is not recorded; only an arg-derived, unscoped
|
|
1409
|
+
* key reaches here. `.any()`/`.pool()` take no key and are not sinks. Structurally
|
|
1410
|
+
* identical to `AdvisorContainerKeyAccess`.
|
|
1411
|
+
*/
|
|
1412
|
+
interface ContainerKeyAccessIR {
|
|
1413
|
+
/** Export binding name of the procedure performing the `ctx.containers` access. */
|
|
1414
|
+
exportName: string;
|
|
1415
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1416
|
+
file: string;
|
|
1417
|
+
/** 1-based line of the `ctx.containers.*.get` call, or `0` when unknown. */
|
|
1418
|
+
line: number;
|
|
1419
|
+
/** The container accessor method invoked — always `get`. */
|
|
1420
|
+
method: string;
|
|
780
1421
|
}
|
|
781
1422
|
/**
|
|
782
|
-
*
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
*
|
|
788
|
-
*
|
|
789
|
-
*
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
1423
|
+
* One `ctx.ai.run(model, …)` call whose model-id argument is derived from the handler's
|
|
1424
|
+
* `args` with no server-side scoping — the `ai_raw_run_escape_hatch` lint input.
|
|
1425
|
+
* `ctx.ai.run` is the raw Workers AI binding passthrough, bypassing the typed
|
|
1426
|
+
* `ctx.ai.model(...)` + AI-SDK layer (`generateText`/`streamText`/…) that caps output and
|
|
1427
|
+
* enforces a schema, so an arg-derived model id lets any caller select an arbitrary model.
|
|
1428
|
+
* A fixed literal model, or one scoped by a server-trusted `ctx.*` value, is not recorded;
|
|
1429
|
+
* only an arg-derived, unscoped model id reaches here (an arg-derived `inputs` argument is
|
|
1430
|
+
* normal usage and is never inspected). Structurally identical to `AdvisorAiRawRun`.
|
|
1431
|
+
*/
|
|
1432
|
+
interface AiRawRunIR {
|
|
1433
|
+
/** Export binding name of the procedure performing the `ctx.ai.run` call. */
|
|
1434
|
+
exportName: string;
|
|
1435
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1436
|
+
file: string;
|
|
1437
|
+
/** 1-based line of the `ctx.ai.run` call, or `0` when unknown. */
|
|
1438
|
+
line: number;
|
|
1439
|
+
}
|
|
795
1440
|
/**
|
|
796
|
-
*
|
|
797
|
-
*
|
|
798
|
-
*
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
1441
|
+
* One `ctx.vectors.<method>(indexName, input)` call whose `input.namespace` is derived
|
|
1442
|
+
* from the handler's `args` with no server-side scoping — the
|
|
1443
|
+
* `vectors_namespace_from_user_input` lint input. A Vectorize namespace partitions one
|
|
1444
|
+
* index into isolated sub-collections, so a namespace taken straight from request input
|
|
1445
|
+
* lets any caller read or poison another tenant's vectors. A fixed literal namespace, or
|
|
1446
|
+
* one prefixed with a server-trusted identity (`${ctx.auth.orgId}` — references `ctx`, so
|
|
1447
|
+
* treated as scoped), is not recorded; only an arg-derived, unscoped namespace reaches
|
|
1448
|
+
* here. Structurally identical to `AdvisorVectorNamespaceAccess`.
|
|
1449
|
+
*/
|
|
1450
|
+
interface VectorNamespaceAccessIR {
|
|
1451
|
+
/** Export binding name of the procedure performing the `ctx.vectors` access. */
|
|
1452
|
+
exportName: string;
|
|
1453
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1454
|
+
file: string;
|
|
1455
|
+
/** 1-based line of the `ctx.vectors` call, or `0` when unknown. */
|
|
1456
|
+
line: number;
|
|
1457
|
+
/** The `ctx.vectors` method invoked: `query` / `upsert` / `upsertMany`. */
|
|
1458
|
+
method: string;
|
|
1459
|
+
}
|
|
803
1460
|
/**
|
|
804
|
-
*
|
|
805
|
-
*
|
|
806
|
-
*
|
|
807
|
-
*
|
|
808
|
-
*
|
|
809
|
-
|
|
810
|
-
|
|
1461
|
+
* One `ctx.mail`/`ctx.email` `send`/`queue` call whose recipient field (`to`/`cc`/`bcc`)
|
|
1462
|
+
* is derived from the handler's `args` with no server-side scoping — the
|
|
1463
|
+
* `mail_recipient_from_request_input` lint input. A recipient taken straight from request
|
|
1464
|
+
* input turns the deployment into an open relay / spam amplifier (any caller can direct
|
|
1465
|
+
* mail to an arbitrary address). A fixed literal recipient, or one scoped by a
|
|
1466
|
+
* server-trusted `ctx.*` value (e.g. `ctx.auth.user.email`), is not recorded; only an
|
|
1467
|
+
* arg-derived, unscoped recipient reaches here. Structurally identical to
|
|
1468
|
+
* `AdvisorMailRecipientAccess`.
|
|
1469
|
+
*/
|
|
1470
|
+
interface MailRecipientAccessIR {
|
|
1471
|
+
/** Export binding name of the procedure performing the `ctx.mail`/`ctx.email` call. */
|
|
1472
|
+
exportName: string;
|
|
1473
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1474
|
+
file: string;
|
|
1475
|
+
/** 1-based line of the `ctx.mail`/`ctx.email` call, or `0` when unknown. */
|
|
1476
|
+
line: number;
|
|
1477
|
+
/** The mailer method invoked: `send` / `queue`. */
|
|
1478
|
+
method: string;
|
|
1479
|
+
}
|
|
811
1480
|
/**
|
|
812
|
-
*
|
|
813
|
-
*
|
|
814
|
-
*
|
|
815
|
-
*
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
*/
|
|
819
|
-
|
|
820
|
-
/**
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1481
|
+
* One `ctx.browser.<method>(url, …)` call whose navigation URL (`arguments[0]`)
|
|
1482
|
+
* is derived from the handler's `args` with no server-side scoping — the
|
|
1483
|
+
* `browser_user_url_without_allowlist` lint input. The lint additionally
|
|
1484
|
+
* cross-references `createBrowser` config-call evidence to suppress findings
|
|
1485
|
+
* when the browser is hardened with an `allowedHosts` allowlist or
|
|
1486
|
+
* `resolveDns`. Structurally identical to `AdvisorBrowserUrlAccess`.
|
|
1487
|
+
*/
|
|
1488
|
+
interface BrowserUrlAccessIR {
|
|
1489
|
+
/** Export binding name of the procedure performing the `ctx.browser` call. */
|
|
1490
|
+
exportName: string;
|
|
1491
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1492
|
+
file: string;
|
|
1493
|
+
/** 1-based line of the `ctx.browser` call, or `0` when unknown. */
|
|
1494
|
+
line: number;
|
|
1495
|
+
/** The browser method invoked: `content` / `pdf` / `scrape` / `screenshot`. */
|
|
1496
|
+
method: string;
|
|
1497
|
+
}
|
|
825
1498
|
/**
|
|
826
|
-
*
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
|
|
830
|
-
|
|
1499
|
+
* One runtime container-override call: a `<handle>.start({ enableInternet: true, … })`
|
|
1500
|
+
* launch override, or a `<handle>.egress.<method>(...)` runtime firewall mutation
|
|
1501
|
+
* (`allow` / `deny` / `setAllowed`) — the `container_start_enable_internet_override`
|
|
1502
|
+
* and `container_runtime_egress_relaxation` lint input. Both shapes re-open network
|
|
1503
|
+
* access the static `defineContainer` declaration (and its `container_public_internet`
|
|
1504
|
+
* lint) assumes is locked down. Matched structurally by call shape, independent of the
|
|
1505
|
+
* receiver's resolved type. Structurally identical to `AdvisorContainerOverride`.
|
|
1506
|
+
*/
|
|
1507
|
+
interface ContainerOverrideIR {
|
|
1508
|
+
/** e.g. the egress method name, or `"enableInternet: true"`. */
|
|
1509
|
+
detail: string;
|
|
1510
|
+
/** Export binding name of the procedure performing the call. */
|
|
1511
|
+
exportName: string;
|
|
1512
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1513
|
+
file: string;
|
|
1514
|
+
/** Which override shape matched. */
|
|
1515
|
+
kind: "egress_relaxation" | "enable_internet";
|
|
1516
|
+
/** 1-based line of the call, or `0` when unknown. */
|
|
1517
|
+
line: number;
|
|
1518
|
+
}
|
|
831
1519
|
/**
|
|
832
|
-
*
|
|
833
|
-
*
|
|
834
|
-
*
|
|
835
|
-
*
|
|
836
|
-
|
|
837
|
-
|
|
1520
|
+
* One `buildImageDeliveryUrl({ key, … })` call (`@lunora/bindings/images`) whose
|
|
1521
|
+
* `key` — the CDN transform's source image, an absolute URL or an
|
|
1522
|
+
* origin-relative key — is derived from the handler's `args` with no
|
|
1523
|
+
* server-side scoping — the `images_url_source_from_user_input` lint input.
|
|
1524
|
+
* `ctx.images.transform`/`info` take image *bytes*, never a URL, so they are not
|
|
1525
|
+
* sinks; only the `key` of `buildImageDeliveryUrl` accepts a URL-or-key source
|
|
1526
|
+
* and is inspected. An arg-derived `key` lets any caller point the CDN's
|
|
1527
|
+
* `/cdn-cgi/image/` transform at an attacker-chosen origin (SSRF / open proxy)
|
|
1528
|
+
* or at an arbitrary key under the account's own store. A fixed literal, or a
|
|
1529
|
+
* key scoped by a server-trusted `ctx.*` value, is not recorded. Structurally
|
|
1530
|
+
* identical to `AdvisorImageDeliveryUrlAccess`.
|
|
1531
|
+
*/
|
|
1532
|
+
interface ImageDeliveryUrlAccessIR {
|
|
1533
|
+
/** Export binding name of the procedure performing the `buildImageDeliveryUrl` call. */
|
|
1534
|
+
exportName: string;
|
|
1535
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1536
|
+
file: string;
|
|
1537
|
+
/** 1-based line of the `buildImageDeliveryUrl` call, or `0` when unknown. */
|
|
1538
|
+
line: number;
|
|
1539
|
+
}
|
|
838
1540
|
/**
|
|
839
|
-
*
|
|
840
|
-
*
|
|
841
|
-
*
|
|
842
|
-
*
|
|
843
|
-
*
|
|
844
|
-
|
|
845
|
-
|
|
1541
|
+
* One `createAuth({...})` call's configuration snapshot — the shared input for
|
|
1542
|
+
* the five `auth_*` security lints (trusted-origins wildcard, CSRF check
|
|
1543
|
+
* disabled, secure cookies disabled, email verification disabled, session
|
|
1544
|
+
* freshAge zero). Matched by callee NAME (an `import`-agnostic, fail-closed
|
|
1545
|
+
* convention the other feeders share), so a re-export or alias still resolves.
|
|
1546
|
+
* When the config argument isn't a statically-analyzable object literal (a
|
|
1547
|
+
* top-level spread, or not an object literal at all), `analyzable` is `false`
|
|
1548
|
+
* and every boolean fact defaults to its SAFE (not-flagged) value — an opaque
|
|
1549
|
+
* config can't be relied on either way. Structurally identical to
|
|
1550
|
+
* `AdvisorAuthConfig`.
|
|
1551
|
+
*/
|
|
1552
|
+
interface AuthConfigIR {
|
|
1553
|
+
/** `true` when the call's config argument was a static object literal the feeder could read. */
|
|
1554
|
+
analyzable: boolean;
|
|
1555
|
+
/** `advanced.disableCSRFCheck === true`. */
|
|
1556
|
+
disableCsrfCheck: boolean;
|
|
1557
|
+
/** `emailAndPassword.enabled === true`. */
|
|
1558
|
+
emailPasswordEnabled: boolean;
|
|
1559
|
+
/** Export binding name enclosing the `createAuth(...)` call. */
|
|
1560
|
+
exportName: string;
|
|
1561
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1562
|
+
file: string;
|
|
1563
|
+
/** 1-based line of the `createAuth(...)` call, or `0` when unknown. */
|
|
1564
|
+
line: number;
|
|
1565
|
+
/** `emailAndPassword.requireEmailVerification === true` present. */
|
|
1566
|
+
requireEmailVerification: boolean;
|
|
1567
|
+
/** `trustedOrigins` array literal contains a `"*"` element. */
|
|
1568
|
+
/** `plugins` includes `scim(...)` while `database` is a non-transactional Lunora adapter — a combination that throws at runtime. */
|
|
1569
|
+
scimOnNonTransactionalAdapter: boolean;
|
|
1570
|
+
/** `advanced.useSecureCookies === false`. */
|
|
1571
|
+
secureCookiesDisabled: boolean;
|
|
1572
|
+
/** `session.freshAge === 0` (explicit literal). */
|
|
1573
|
+
sessionFreshAgeZero: boolean;
|
|
1574
|
+
trustedOriginsWildcard: boolean;
|
|
1575
|
+
}
|
|
846
1576
|
/**
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
*
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1577
|
+
* One `rateLimit`/`dbRateLimit` middleware call (`@lunora/ratelimit`) whose
|
|
1578
|
+
* `key` selector — the per-caller rate-limit sub-key, `(ctx) => string |
|
|
1579
|
+
* undefined` — is derived from the handler's `args` with no server-side
|
|
1580
|
+
* scoping (no reference to the trusted `ctx` binding anywhere in the selector)
|
|
1581
|
+
* — the `ratelimit_key_spoofable_or_global` lint input. A key an attacker
|
|
1582
|
+
* controls lets them rotate it per request and bypass the limit entirely,
|
|
1583
|
+
* defeating its purpose. A selector scoped by `ctx` (e.g. `ctx.auth.userId`,
|
|
1584
|
+
* `ctx.ip`), or one with no `args` reference at all (a fixed/global bucket —
|
|
1585
|
+
* the "no key" case this lint deliberately does not flag, to keep it low-FP),
|
|
1586
|
+
* is not recorded. Structurally identical to `AdvisorRatelimitKeySelector`.
|
|
1587
|
+
*/
|
|
1588
|
+
interface RatelimitKeySelectorIR {
|
|
1589
|
+
/** The `rateLimit`/`dbRateLimit` callee invoked. */
|
|
1590
|
+
callee: string;
|
|
1591
|
+
/** Export binding name of the procedure whose `.use(...)` chain carries the call. */
|
|
1592
|
+
exportName: string;
|
|
1593
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1594
|
+
file: string;
|
|
1595
|
+
/** The rate limit's `name` argument (the second positional argument), or `""` when not a string literal. */
|
|
1596
|
+
limitName: string;
|
|
1597
|
+
/** 1-based line of the `rateLimit`/`dbRateLimit` call, or `0` when unknown. */
|
|
1598
|
+
line: number;
|
|
1599
|
+
}
|
|
862
1600
|
/**
|
|
863
|
-
*
|
|
864
|
-
*
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
*
|
|
868
|
-
*
|
|
869
|
-
*
|
|
870
|
-
*
|
|
871
|
-
*
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
*/
|
|
877
|
-
|
|
1601
|
+
* One payload-derived privileged dispatch — a `ctx.run`/`context.run` back into a
|
|
1602
|
+
* Lunora function from inside a `defineQueue` push handler or a `defineWorkflow`
|
|
1603
|
+
* handler, whose args reference the handler's untrusted payload (`context.params`
|
|
1604
|
+
* for a workflow, a `for (… of batch.messages)` body for a queue) — the
|
|
1605
|
+
* `privileged_dispatch_unvalidated_payload` lint input. Both handler kinds run
|
|
1606
|
+
* under the **system identity** (RLS disabled), so forwarding attacker-influenced
|
|
1607
|
+
* payload into the dispatch bypasses the target's row policy. The resolved
|
|
1608
|
+
* `targetFile`/`targetExport` let the lint join RLS-procedure evidence and fire
|
|
1609
|
+
* only for RLS-gated targets. Structurally identical to `AdvisorPrivilegedDispatch`.
|
|
1610
|
+
*/
|
|
1611
|
+
interface PrivilegedDispatchIR {
|
|
1612
|
+
/** `"queue"` for a `defineQueue` handler, `"workflow"` for a `defineWorkflow` handler. */
|
|
1613
|
+
dispatchKind: "queue" | "workflow";
|
|
1614
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1615
|
+
file: string;
|
|
1616
|
+
/** Export binding name of the handler performing the dispatch. */
|
|
1617
|
+
handlerExport: string;
|
|
1618
|
+
/** 1-based line of the dispatch call, or `0` when unknown. */
|
|
1619
|
+
line: number;
|
|
1620
|
+
/** Export name of the dispatched target (`send` in `api.messages.send`). */
|
|
1621
|
+
targetExport: string;
|
|
1622
|
+
/** File path of the dispatched target relative to `lunora/` (`messages` in `api.messages.send`). */
|
|
1623
|
+
targetFile: string;
|
|
1624
|
+
}
|
|
878
1625
|
/**
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
*
|
|
882
|
-
*
|
|
883
|
-
*/
|
|
884
|
-
|
|
885
|
-
/**
|
|
886
|
-
|
|
1626
|
+
* One discovered `httpRoute.<verb>("/admin/…")` route on an admin/privileged-looking
|
|
1627
|
+
* path, with whether its builder chain references an auth/admin guard — the
|
|
1628
|
+
* `admin_route_without_guard` lint input. Structurally identical to
|
|
1629
|
+
* `AdvisorAdminRoute`.
|
|
1630
|
+
*/
|
|
1631
|
+
interface AdminRouteIR {
|
|
1632
|
+
/** Export binding name of the route handler. */
|
|
1633
|
+
exportName: string;
|
|
1634
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1635
|
+
file: string;
|
|
1636
|
+
/** HTTP verb the route binds to (uppercased), e.g. `"POST"`. */
|
|
1637
|
+
method: string;
|
|
1638
|
+
/** The route path, e.g. `/admin/users`. */
|
|
1639
|
+
path: string;
|
|
1640
|
+
/** `true` when the handler body references an auth/session/admin guard (`ctx.auth`, `getSession`, `requireAdmin`, …). */
|
|
1641
|
+
usesGuard: boolean;
|
|
1642
|
+
}
|
|
887
1643
|
/**
|
|
888
|
-
*
|
|
889
|
-
*
|
|
890
|
-
*
|
|
891
|
-
*
|
|
892
|
-
|
|
1644
|
+
* One tracked `ctx.storage.<bucket>.<method>(...)` upload/signing call — the
|
|
1645
|
+
* shared input for the storage config-hygiene security lints
|
|
1646
|
+
* (`storage_upload_without_content_type_allowlist`, `storage_upload_without_max_size`,
|
|
1647
|
+
* `storage_generate_upload_url_no_content_type_pin`, `storage_presigned_url_for_private_content`).
|
|
1648
|
+
* `upload`/`store` carry the `UploadOptions` guards (`allowedContentTypes` /
|
|
1649
|
+
* `maxSize`); `generateUploadUrl` carries the signed-PUT `contentType` pin;
|
|
1650
|
+
* `getPresignedUrl`/`getSignedUrl` carry a statically-known `expiresInSeconds`
|
|
1651
|
+
* literal. `presentKeys` is empty (and `expiresInSeconds` unset) when the
|
|
1652
|
+
* options argument was absent, a non-literal, or a spread — see `analyzable`.
|
|
1653
|
+
* Structurally identical to `AdvisorStorageUpload`.
|
|
1654
|
+
*/
|
|
1655
|
+
interface StorageUploadIR {
|
|
1656
|
+
/** `true` when the call's options-object argument (or its deliberate absence) was statically resolvable. */
|
|
1657
|
+
analyzable: boolean;
|
|
1658
|
+
/** Numeric literal value of an `expiresInSeconds` option, when statically known (`getSignedUrl` / `getPresignedUrl` only). */
|
|
1659
|
+
expiresInSeconds?: number;
|
|
1660
|
+
/** Export binding name of the procedure performing the call. */
|
|
1661
|
+
exportName: string;
|
|
1662
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1663
|
+
file: string;
|
|
1664
|
+
/** 1-based line of the call, or `0` when unknown. */
|
|
1665
|
+
line: number;
|
|
1666
|
+
/** The `ctx.storage` method invoked. */
|
|
1667
|
+
method: "generateUploadUrl" | "getPresignedUrl" | "getSignedUrl" | "store" | "upload";
|
|
1668
|
+
/** Options-object keys present at the call site (empty when not `analyzable`, or when no options argument was passed). */
|
|
1669
|
+
presentKeys: string[];
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* One discovered `httpAction`/`httpRoute` handler in `lunora/` that performs a
|
|
1673
|
+
* side effect (`ctx.runMutation` / `ctx.runAction` / a `ctx.db.{insert,patch,
|
|
1674
|
+
* replace,delete,insertManyUnsafe}` write) from the HTTP edge, with whether it
|
|
1675
|
+
* reads `ctx.auth` — the `http_action_missing_auth_guard` lint input. A handler
|
|
1676
|
+
* that mutates state or dispatches an action without ever consulting the request
|
|
1677
|
+
* identity is an unauthenticated write bypassing identity/RLS. Only handlers with
|
|
1678
|
+
* a statically-resolvable inline body and `ctx` binding are recorded (fail-safe
|
|
1679
|
+
* under-report); read-only handlers are never recorded. Structurally identical to
|
|
1680
|
+
* `AdvisorHttpActionGuard`.
|
|
1681
|
+
*/
|
|
1682
|
+
interface HttpActionGuardIR {
|
|
1683
|
+
/** Export binding name of the handler (or `"<module>"` when mounted inline / not a named binding). */
|
|
1684
|
+
exportName: string;
|
|
1685
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1686
|
+
file: string;
|
|
1687
|
+
/** Which HTTP surface the handler is: a raw `httpAction` or a typed `httpRoute` route. */
|
|
1688
|
+
kind: "httpAction" | "httpRoute";
|
|
1689
|
+
/** 1-based line of the handler call, or `0` when unknown. */
|
|
1690
|
+
line: number;
|
|
1691
|
+
/** For an `httpRoute`, the uppercased verb (`"POST"`); absent for a raw `httpAction`. */
|
|
1692
|
+
method?: string;
|
|
1693
|
+
/** `true` when the handler reads `ctx.auth` (a direct member access or a `const { auth } = ctx` destructure). */
|
|
1694
|
+
readsAuth: boolean;
|
|
1695
|
+
/** The first side effect found, as a stable label: `runMutation`, `runAction`, or `db.<method>`. */
|
|
1696
|
+
sideEffect: string;
|
|
1697
|
+
}
|
|
1698
|
+
/**
|
|
1699
|
+
* One response-header write, inside an `httpAction` handler, whose value is derived
|
|
1700
|
+
* from raw request input (`request.headers`, `request.url`/query, `await
|
|
1701
|
+
* request.json()`) with no CR/LF sanitizer — the
|
|
1702
|
+
* `http_action_response_header_injection` lint input. A `Request`-derived string
|
|
1703
|
+
* placed verbatim into a response header lets a caller smuggle `\r\n` and inject
|
|
1704
|
+
* extra headers or split the response (header injection / response splitting). Only
|
|
1705
|
+
* sites whose value is request-tainted AND unguarded are recorded: a value routed
|
|
1706
|
+
* through a CR/LF guard (`isSafeHeaderValue`), a URL/URI encoder
|
|
1707
|
+
* (`encodeURIComponent`/`encodeURI`), a numeric coercion (`Number`/`parseInt`/
|
|
1708
|
+
* `parseFloat`), or `btoa` is treated as safe and never recorded (`String(...)` /
|
|
1709
|
+
* `.toString()` are NOT sanitizers — they don't strip CR/LF). Structurally
|
|
1710
|
+
* identical to `AdvisorHttpHeaderWrite`.
|
|
1711
|
+
*/
|
|
1712
|
+
interface HttpHeaderWriteIR {
|
|
1713
|
+
/** Export binding name of the enclosing handler, or `"<module>"` when mounted inline. */
|
|
1714
|
+
exportName: string;
|
|
1715
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1716
|
+
file: string;
|
|
1717
|
+
/** The header name being written (`"location"`), or `""` when the key is not a string literal. */
|
|
1718
|
+
headerName: string;
|
|
1719
|
+
/** 1-based line of the request-tainted header value. */
|
|
1720
|
+
line: number;
|
|
1721
|
+
/** How the header was written. */
|
|
1722
|
+
via: "headers-append" | "headers-ctor" | "headers-set" | "response-init";
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* One rate-limit / Turnstile middleware call in `lunora/` — the
|
|
1726
|
+
* `ratelimit_middleware_fail_open` lint input. `rateLimit`/`dbRateLimit`
|
|
1727
|
+
* (`@lunora/ratelimit`) and `verifyTurnstileMiddleware` (`@lunora/auth`) each
|
|
1728
|
+
* accept a `failOpen` escape hatch that admits every request when the
|
|
1729
|
+
* limiter/siteverify is unavailable; `failOpen` is `true` only when the options
|
|
1730
|
+
* literal set it to the boolean literal `true` (anything else is fail-closed).
|
|
1731
|
+
* The lint escalates a fail-open guard to a finding when the guarded procedure
|
|
1732
|
+
* (`exportName`/`limitName`) looks auth/payment-sensitive. Structurally
|
|
1733
|
+
* identical to `AdvisorFailOpenGuard`.
|
|
1734
|
+
*/
|
|
1735
|
+
interface FailOpenGuardIR {
|
|
1736
|
+
/** The middleware factory at the call site: `rateLimit` / `dbRateLimit` / `verifyTurnstileMiddleware`. */
|
|
1737
|
+
callee: string;
|
|
1738
|
+
/** Export binding name of the procedure the guard is attached to, or `"<module>"` at file scope. */
|
|
1739
|
+
exportName: string;
|
|
1740
|
+
/** `true` only when the options literal set `failOpen: true` as a boolean literal; a non-literal or absent option is treated as fail-closed. */
|
|
1741
|
+
failOpen: boolean;
|
|
1742
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1743
|
+
file: string;
|
|
1744
|
+
/** The rate-limit `name` (second string argument) for `rateLimit`/`dbRateLimit`; `""` for `verifyTurnstileMiddleware`. */
|
|
1745
|
+
limitName: string;
|
|
1746
|
+
/** 1-based line of the middleware call, or `0` when unknown. */
|
|
1747
|
+
line: number;
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* One `ctx.flags.boolean("key", <boolean-literal>)` read in `lunora/` — the
|
|
1751
|
+
* `flag_gates_security_with_unsafe_default` lint input. OpenFeature returns the
|
|
1752
|
+
* `defaultValue` when the provider errors, so a fail-open default on a
|
|
1753
|
+
* security-shaped key silently opens access during an outage. Only reads with a
|
|
1754
|
+
* statically-known string key and boolean-literal default are recorded; the lint
|
|
1755
|
+
* owns the security-shape + polarity judgment. Structurally identical to
|
|
1756
|
+
* `AdvisorFlagSecurityDefault`.
|
|
1757
|
+
*/
|
|
1758
|
+
interface FlagSecurityDefaultIR {
|
|
1759
|
+
/** The boolean-literal default returned on a provider outage (fail-open value). */
|
|
1760
|
+
defaultValue: boolean;
|
|
1761
|
+
/** Export binding name of the procedure performing the flag read, or `"<module>"` at file scope. */
|
|
1762
|
+
exportName: string;
|
|
1763
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1764
|
+
file: string;
|
|
1765
|
+
/** The flag key — the first string-literal argument of `ctx.flags.boolean`. */
|
|
1766
|
+
key: string;
|
|
1767
|
+
/** 1-based line of the `ctx.flags.boolean` call, or `0` when unknown. */
|
|
1768
|
+
line: number;
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* One `ctx.flags` read lexically inside a `query(...)` handler — the
|
|
1772
|
+
* `flag_read_in_subscription` advisor lint input. Flag changes append nothing to
|
|
1773
|
+
* `__cdc_log`, so a subscribed query is never re-run when a flag flips and keeps
|
|
1774
|
+
* serving the branch it last picked; `useFlag` is the reactive path. Structurally
|
|
1775
|
+
* identical to the advisor's `AdvisorFlagRead` (same field set) so values pass
|
|
1776
|
+
* straight through `lintSchema` without conversion, exactly as `R2sqlCallIR` does.
|
|
1777
|
+
*
|
|
1778
|
+
* Only `query` handlers are recorded — unlike `R2sqlCallIR` / `NondeterministicCallIR`
|
|
1779
|
+
* there is no `kind` field, because `mutation`/`action` handlers run once and have
|
|
1780
|
+
* no subscription staleness to warn about, so the feeder drops them rather than
|
|
1781
|
+
* handing the lint rows it would discard.
|
|
1782
|
+
*/
|
|
1783
|
+
interface FlagReadIR {
|
|
1784
|
+
/** The accessed `ctx.flags` surface, e.g. `ctx.flags.boolean` / `ctx.flags.details.string`. */
|
|
1785
|
+
callee: string;
|
|
1786
|
+
/** Export binding name of the query performing the read. */
|
|
1787
|
+
exportName: string;
|
|
1788
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension (the api namespace). */
|
|
1789
|
+
file: string;
|
|
1790
|
+
/** 1-based line of the read, or `0` when unknown. */
|
|
1791
|
+
line: number;
|
|
1792
|
+
}
|
|
1793
|
+
/**
|
|
1794
|
+
* One `generateText` / `streamText` call in `lunora/` whose `tools` reach a
|
|
1795
|
+
* privileged side effect (a DB write, function dispatch, or outbound
|
|
1796
|
+
* fetch/mail/queue send). `userInputDerived` records whether the model input
|
|
1797
|
+
* (`prompt`/`messages`/`system`) flows from the handler's `args`; the
|
|
1798
|
+
* `ai_tool_side_effect_prompt_injection` lint fires only when it does.
|
|
1799
|
+
* Structurally identical to `AdvisorAiToolSideEffect`.
|
|
1800
|
+
*/
|
|
1801
|
+
interface AiToolSideEffectIR {
|
|
1802
|
+
/** Export binding name of the procedure performing the call. */
|
|
1803
|
+
exportName: string;
|
|
1804
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1805
|
+
file: string;
|
|
1806
|
+
/** 1-based line of the generation call, or `0` when unknown. */
|
|
1807
|
+
line: number;
|
|
1808
|
+
/** The generation entrypoint invoked. */
|
|
1809
|
+
method: "generateText" | "streamText";
|
|
1810
|
+
/** The privileged side-effect sink a model-callable tool reaches (`ctx.db.insert`, `ctx.run`, `ctx.fetch`, …). */
|
|
1811
|
+
sideEffect: string;
|
|
1812
|
+
/** `true` when a model-input option is derived from the handler's `args` (a bare `args.x`, or a name destructured from `args`). */
|
|
1813
|
+
userInputDerived: boolean;
|
|
1814
|
+
}
|
|
1815
|
+
/**
|
|
1816
|
+
* One `<receiver>.identity.<key>` claim read in `lunora/`, where `<receiver>` is
|
|
1817
|
+
* an RLS/mask policy `auth` (or `ctx.auth`/`context.auth`). `declared` records
|
|
1818
|
+
* whether `<key>` is in the app's `defineIdentity({ ... })` contract (or the
|
|
1819
|
+
* always-present `userId`); the `identity_undeclared_claim_trusted` lint fires on
|
|
1820
|
+
* the undeclared reads. Emitted only when a resolvable identity contract exists.
|
|
1821
|
+
* Structurally identical to `AdvisorIdentityClaimRead`.
|
|
1822
|
+
*/
|
|
1823
|
+
interface IdentityClaimReadIR {
|
|
1824
|
+
/** `true` when `key` is a declared claim (in the `defineIdentity` contract, or the always-present `userId`). */
|
|
1825
|
+
declared: boolean;
|
|
1826
|
+
/** Export binding name of the enclosing declaration (`<module>` at file scope). */
|
|
1827
|
+
exportName: string;
|
|
1828
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1829
|
+
file: string;
|
|
1830
|
+
/** The claim key read off the identity bag. */
|
|
1831
|
+
key: string;
|
|
1832
|
+
/** 1-based line of the read, or `0` when unknown. */
|
|
1833
|
+
line: number;
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* One payment webhook-adapter construction in `lunora/` (`createStripeAdapter` /
|
|
1837
|
+
* `createPolarAdapter` / `createAutumnAdapter` / `createDodoPaymentsAdapter`).
|
|
1838
|
+
* `toleranceSeconds` carries the statically-known `webhookToleranceSeconds`
|
|
1839
|
+
* replay window when it is a plain numeric literal; the payment-webhook
|
|
1840
|
+
* wide-tolerance lint fires when it exceeds a conservative ceiling. Structurally
|
|
1841
|
+
* identical to `AdvisorPaymentWebhook`.
|
|
1842
|
+
*/
|
|
1843
|
+
interface PaymentWebhookIR {
|
|
1844
|
+
/** The adapter factory invoked. */
|
|
1845
|
+
callee: "createAutumnAdapter" | "createDodoPaymentsAdapter" | "createPolarAdapter" | "createStripeAdapter";
|
|
1846
|
+
/** Export binding name of the enclosing declaration (`<module>` at file scope). */
|
|
1847
|
+
exportName: string;
|
|
1848
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1849
|
+
file: string;
|
|
1850
|
+
/** 1-based line of the construction, or `0` when unknown. */
|
|
1851
|
+
line: number;
|
|
1852
|
+
/** Statically-known `webhookToleranceSeconds` literal, when present and a plain numeric literal. */
|
|
1853
|
+
toleranceSeconds?: number;
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
* One `ctx.db.<table>.findMany({ includeDeleted })` list read whose
|
|
1857
|
+
* `includeDeleted` is either a hardcoded `true` or derived from the handler's
|
|
1858
|
+
* `args` — the `soft_delete_include_deleted_from_args` lint input. The lint joins
|
|
1859
|
+
* `table` against the schema's soft-delete tables and `visibility` against
|
|
1860
|
+
* `.public()` before flagging. Structurally identical to `AdvisorSoftDeleteRead`
|
|
1861
|
+
* so values pass straight through without conversion.
|
|
1862
|
+
*/
|
|
1863
|
+
interface SoftDeleteReadIR {
|
|
1864
|
+
/** Export binding name of the procedure performing the read. */
|
|
1865
|
+
exportName: string;
|
|
1866
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1867
|
+
file: string;
|
|
1868
|
+
/** `true` when `includeDeleted` was derived from the handler's `args` (any caller can flip it). */
|
|
1869
|
+
fromArgs: boolean;
|
|
1870
|
+
/** `true` when `includeDeleted` was a hardcoded `true` literal (always resurfaces soft-deleted rows). */
|
|
1871
|
+
hardcodedTrue: boolean;
|
|
1872
|
+
/** 1-based line of the read call. */
|
|
1873
|
+
line: number;
|
|
1874
|
+
/** Table read, or `""` when the table-arg form's first argument wasn't a string literal. */
|
|
1875
|
+
table: string;
|
|
1876
|
+
/** `"internal"` for `internalQuery` / `internalMutation` / `internalAction`. */
|
|
1877
|
+
visibility: "internal" | "public";
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
1880
|
+
* One `ctx.db.<table>.findMany({ with: { <rel> } })` relation-hydrating list read
|
|
1881
|
+
* — the `masked_relation_leak_via_with` lint input. Column masking does not
|
|
1882
|
+
* descend into `with`-hydrated relations, so a masked table surfaced only through
|
|
1883
|
+
* a `with` on an unprotected parent read is returned in the clear. The lint
|
|
1884
|
+
* resolves each relation accessor to its target table and joins it against the
|
|
1885
|
+
* discovered mask evidence before flagging. Structurally identical to
|
|
1886
|
+
* `AdvisorRelationLoad` so values pass straight through without conversion.
|
|
1887
|
+
*/
|
|
1888
|
+
interface RelationLoadIR {
|
|
1889
|
+
/** Export binding name of the procedure performing the read. */
|
|
1890
|
+
exportName: string;
|
|
1891
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1892
|
+
file: string;
|
|
1893
|
+
/** 1-based line of the read call. */
|
|
1894
|
+
line: number;
|
|
1895
|
+
/** Parent table the read targets, or `""` when the table-arg form's first argument wasn't a string literal. */
|
|
1896
|
+
parentTable: string;
|
|
1897
|
+
/** Relation accessor names named in the read's `with: { … }` map — matched against the parent table's declared relations. */
|
|
1898
|
+
relations: string[];
|
|
1899
|
+
/** `"internal"` for `internalQuery` / `internalMutation` / `internalAction`. */
|
|
1900
|
+
visibility: "internal" | "public";
|
|
1901
|
+
}
|
|
1902
|
+
/**
|
|
1903
|
+
* One `query` handler whose `return` hands back the raw rows of a table — the
|
|
1904
|
+
* result of a `ctx.db.<table>.findMany()` / `.findFirst()` / `.get()` read, or a
|
|
1905
|
+
* `ctx.db.query("<table>")…collect()` fluent chain — returned directly (or through
|
|
1906
|
+
* one local `const` hop) with no hand-built projection. The
|
|
1907
|
+
* `output_projection_missing_on_public_read` lint keeps only `visibility ===
|
|
1908
|
+
* "public"` rows with no `.output(...)` / `.use(mask(...))` on the chain, then
|
|
1909
|
+
* joins `table` against the schema and flags one whose columns are PII-named.
|
|
1910
|
+
* Structurally identical to `AdvisorRawRowReturn` so values pass straight through
|
|
1911
|
+
* without conversion.
|
|
1912
|
+
*/
|
|
1913
|
+
interface RawRowReturnIR {
|
|
1914
|
+
/** Export binding name of the query returning the raw rows. */
|
|
1915
|
+
exportName: string;
|
|
1916
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1917
|
+
file: string;
|
|
1918
|
+
/** 1-based line of the `return` (or concise-body) expression. */
|
|
1919
|
+
line: number;
|
|
1920
|
+
/** Table whose raw rows are returned, or `""` when the read's table wasn't a string literal. */
|
|
1921
|
+
table: string;
|
|
1922
|
+
/** `true` when the procedure's builder chain carries a `.use(mask(...))` step. */
|
|
1923
|
+
usesMask: boolean;
|
|
1924
|
+
/** `true` when the procedure's builder chain carries an `.output(...)` return-shape projection. */
|
|
1925
|
+
usesOutput: boolean;
|
|
1926
|
+
/** `"internal"` for `internalQuery`; `"public"` for `query`. */
|
|
1927
|
+
visibility: "internal" | "public";
|
|
1928
|
+
}
|
|
1929
|
+
/**
|
|
1930
|
+
* One `query`/`mutation` handler that gates a `ctx.db.get`/`patch`/`delete` on a
|
|
1931
|
+
* null-checked `ctx.db.normalizeId(table, id)` result — the
|
|
1932
|
+
* `normalize_id_used_as_authorization` lint input. `normalizeId` validates an id's
|
|
1933
|
+
* structural shape only (it never reads the database), so a non-null result proves
|
|
1934
|
+
* the id is well-formed, never that the caller owns the row; gating access on it is
|
|
1935
|
+
* an IDOR. The lint owns the negative proof — it keeps only `visibility === "public"`
|
|
1936
|
+
* rows with no `.use(rls(...))` and no ownership/identity mention (`mentionsOwnership`),
|
|
1937
|
+
* then joins `table` against the schema's RLS mode before flagging. Structurally
|
|
1938
|
+
* identical to `AdvisorNormalizeIdAuthorization` so values pass straight through.
|
|
1939
|
+
*/
|
|
1940
|
+
interface NormalizeIdAuthorizationIR {
|
|
1941
|
+
/** Export binding name of the procedure performing the normalize-then-access. */
|
|
1942
|
+
exportName: string;
|
|
1943
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1944
|
+
file: string;
|
|
1945
|
+
/** 1-based line of the `ctx.db.normalizeId(...)` call the access is gated on. */
|
|
1946
|
+
line: number;
|
|
1947
|
+
/** `true` when the handler anywhere reads an ownership-named identifier or `ctx.auth`/`ctx.identity`/… — an intervening ownership signal. */
|
|
1948
|
+
mentionsOwnership: boolean;
|
|
1949
|
+
/** The id-first `ctx.db` sink the normalized id reaches. */
|
|
1950
|
+
sinkMethod: "delete" | "get" | "patch";
|
|
1951
|
+
/** Table named in the `normalizeId` call, or `""` when its table argument wasn't a string literal. */
|
|
1952
|
+
table: string;
|
|
1953
|
+
/** `true` when the procedure's builder chain carries a `.use(rls(...))` step. */
|
|
1954
|
+
usesRls: boolean;
|
|
1955
|
+
/** `"internal"` for `internalQuery`/`internalMutation`; `"public"` for `query`/`mutation`. */
|
|
1956
|
+
visibility: "internal" | "public";
|
|
1957
|
+
}
|
|
1958
|
+
/**
|
|
1959
|
+
* One committed `wrangler.jsonc` `vars` entry whose value is a plaintext secret —
|
|
1960
|
+
* the `plaintext_secret_in_wrangler_vars` lint input. `vars` are baked into the
|
|
1961
|
+
* deployed Worker in cleartext and checked into source control, so a real API key
|
|
1962
|
+
* / token / private key there ships the secret to every reader of the repo and the
|
|
1963
|
+
* bundle; it belongs in a Secrets Store binding or `wrangler secret put`. Produced
|
|
1964
|
+
* by `@lunora/config` (which reads `wrangler.jsonc`), not a ts-morph feeder —
|
|
1965
|
+
* codegen only passes it through. Structurally identical to `AdvisorWranglerVariable`.
|
|
1966
|
+
*/
|
|
1967
|
+
interface WranglerVariableIR {
|
|
1968
|
+
/** The `wrangler.jsonc` file the var was read from, relative to the project root. */
|
|
1969
|
+
file: string;
|
|
1970
|
+
/** The offending `vars` key (e.g. `STRIPE_SECRET_KEY`). */
|
|
1971
|
+
key: string;
|
|
1972
|
+
/** Heuristic that matched, e.g. `stripe_live_key` / `private_key` / `secret_named_var`. */
|
|
1973
|
+
kind: string;
|
|
1974
|
+
/** Redacted preview of the value (first few chars + length) for the finding detail — never the full secret. */
|
|
1975
|
+
preview: string;
|
|
1976
|
+
}
|
|
1977
|
+
interface ProjectIR {
|
|
1978
|
+
crons: ReadonlyArray<CronJobIR>;
|
|
1979
|
+
functions: ReadonlyArray<FunctionIR>;
|
|
1980
|
+
/** Typed REST routes discovered from `httpRoute.<verb>(...)` builder chains. */
|
|
1981
|
+
httpRoutes: ReadonlyArray<HttpRouteIR>;
|
|
1982
|
+
migrations: ReadonlyArray<MigrationIR>;
|
|
1983
|
+
schema: SchemaIR;
|
|
1984
|
+
}
|
|
1985
|
+
/**
|
|
1986
|
+
* One import of a migrated-away platform's SDK still present in `lunora/`
|
|
1987
|
+
* source. Structurally identical to the advisor's `AdvisorStaleMigrationImport`
|
|
1988
|
+
* so it passes through the feeder without conversion.
|
|
1989
|
+
*/
|
|
1990
|
+
interface StaleMigrationImportIR {
|
|
1991
|
+
/** Source file relative to the lunora dir, no extension. */
|
|
1992
|
+
file: string;
|
|
1993
|
+
/** 1-based line of the import, or `0` when unknown. */
|
|
1994
|
+
line: number;
|
|
1995
|
+
/** The imported module, e.g. `@supabase/supabase-js`. */
|
|
1996
|
+
moduleSpecifier: string;
|
|
1997
|
+
/** Which migration guide covers this platform. */
|
|
1998
|
+
platform: "convex" | "firebase" | "supabase";
|
|
1999
|
+
}
|
|
2000
|
+
/**
|
|
2001
|
+
* Named inputs for {@link lintSchema}. Every feeder is a discrete key rather than
|
|
2002
|
+
* a positional argument: the feeder list grows every few releases and many IR
|
|
2003
|
+
* types are structurally similar (`{file, exportName, line}`-shaped evidence),
|
|
2004
|
+
* so a positional call was a silent-transposition hazard — swapping two adjacent
|
|
2005
|
+
* arguments could typecheck yet feed the wrong evidence to the wrong lint and
|
|
2006
|
+
* corrupt a security advisory. `schema` is the only required field; every other
|
|
2007
|
+
* feeder defaults to "not analyzed" when omitted.
|
|
2008
|
+
*/
|
|
2009
|
+
interface LintSchemaOptions {
|
|
2010
|
+
adminRoutes?: ReadonlyArray<AdminRouteIR>;
|
|
2011
|
+
aiRawRuns?: ReadonlyArray<AiRawRunIR>;
|
|
2012
|
+
aiToolSideEffects?: ReadonlyArray<AiToolSideEffectIR>;
|
|
2013
|
+
argumentDerivedFetches?: ReadonlyArray<ArgumentDerivedFetchIR>;
|
|
2014
|
+
argumentValidators?: ReadonlyArray<ArgumentValidatorIR>;
|
|
2015
|
+
authApiCalls?: ReadonlyArray<AuthApiCallIR>;
|
|
2016
|
+
authConfigs?: ReadonlyArray<AuthConfigIR>;
|
|
2017
|
+
browserUrlAccesses?: ReadonlyArray<BrowserUrlAccessIR>;
|
|
2018
|
+
configCalls?: ReadonlyArray<ConfigCallIR>;
|
|
2019
|
+
containerKeyAccesses?: ReadonlyArray<ContainerKeyAccessIR>;
|
|
2020
|
+
containerOverrides?: ReadonlyArray<ContainerOverrideIR>;
|
|
2021
|
+
containers?: ReadonlyArray<ContainerIR>;
|
|
2022
|
+
exportSinks?: ReadonlyArray<AdvisorExportSink>;
|
|
2023
|
+
failOpenGuards?: ReadonlyArray<FailOpenGuardIR>;
|
|
2024
|
+
flagReads?: ReadonlyArray<FlagReadIR>;
|
|
2025
|
+
flagSecurityDefaults?: ReadonlyArray<FlagSecurityDefaultIR>;
|
|
2026
|
+
geoIndexUsages?: ReadonlyArray<AdvisorGeoIndexUsage>;
|
|
2027
|
+
httpActionGuards?: ReadonlyArray<HttpActionGuardIR>;
|
|
2028
|
+
httpHeaderWrites?: ReadonlyArray<HttpHeaderWriteIR>;
|
|
2029
|
+
identityClaimReads?: ReadonlyArray<IdentityClaimReadIR>;
|
|
2030
|
+
imageDeliveryUrlAccesses?: ReadonlyArray<ImageDeliveryUrlAccessIR>;
|
|
2031
|
+
inserts?: ReadonlyArray<InsertWriteIR>;
|
|
2032
|
+
kvKeyAccesses?: ReadonlyArray<KvKeyAccessIR>;
|
|
2033
|
+
mailRecipientAccesses?: ReadonlyArray<MailRecipientAccessIR>;
|
|
2034
|
+
maskProcedures?: ReadonlyArray<MaskProcedureIR>;
|
|
2035
|
+
maskStrategies?: ReadonlyArray<MaskStrategyIR>;
|
|
2036
|
+
mutatorWrites?: ReadonlyArray<MutatorWriteIR>;
|
|
2037
|
+
nondeterministicCalls?: ReadonlyArray<NondeterministicCallIR>;
|
|
2038
|
+
normalizeIdAuthorizations?: ReadonlyArray<NormalizeIdAuthorizationIR>;
|
|
2039
|
+
notifyCalls?: ReadonlyArray<AdvisorNotifyCall>;
|
|
2040
|
+
notifyConfig?: AdvisorNotifyConfig;
|
|
2041
|
+
ownerFieldWrites?: ReadonlyArray<OwnerFieldWriteIR>;
|
|
2042
|
+
paymentWebhooks?: ReadonlyArray<PaymentWebhookIR>;
|
|
2043
|
+
privilegedDispatches?: ReadonlyArray<PrivilegedDispatchIR>;
|
|
2044
|
+
procedureProtections?: ReadonlyArray<ProcedureMiddlewareIR>;
|
|
2045
|
+
queries?: ReadonlyArray<QueryReadIR>;
|
|
2046
|
+
queues?: ReadonlyArray<QueueIR>;
|
|
2047
|
+
r2sqlCalls?: ReadonlyArray<R2sqlCallIR>;
|
|
2048
|
+
ratelimitKeySelectors?: ReadonlyArray<RatelimitKeySelectorIR>;
|
|
2049
|
+
rawRowReturns?: ReadonlyArray<RawRowReturnIR>;
|
|
2050
|
+
relationLoads?: ReadonlyArray<RelationLoadIR>;
|
|
2051
|
+
rlsProcedures?: ReadonlyArray<RlsProcedureIR>;
|
|
2052
|
+
schema: SchemaIR;
|
|
2053
|
+
secretLiterals?: ReadonlyArray<SecretLiteralIR>;
|
|
2054
|
+
shapes?: ReadonlyArray<ShapeIR>;
|
|
2055
|
+
softDeleteReads?: ReadonlyArray<SoftDeleteReadIR>;
|
|
2056
|
+
sqlInterpolations?: ReadonlyArray<SqlInterpolationIR>;
|
|
2057
|
+
staleMigrationImports?: ReadonlyArray<StaleMigrationImportIR>;
|
|
2058
|
+
storageKeyAccesses?: ReadonlyArray<StorageKeyAccessIR>;
|
|
2059
|
+
storageUploads?: ReadonlyArray<StorageUploadIR>;
|
|
2060
|
+
unrestrictedWhereBranches?: ReadonlyArray<UnrestrictedWhereBranchIR>;
|
|
2061
|
+
vectorNamespaceAccesses?: ReadonlyArray<VectorNamespaceAccessIR>;
|
|
2062
|
+
workflowCalls?: ReadonlyArray<WorkflowCallIR>;
|
|
2063
|
+
workflows?: ReadonlyArray<WorkflowIR>;
|
|
2064
|
+
wranglerVariables?: ReadonlyArray<WranglerVariableIR>;
|
|
2065
|
+
}
|
|
2066
|
+
/**
|
|
2067
|
+
* Normalize feeder options into the advisor's {@link LintContext} — the input
|
|
2068
|
+
* both `runAdvisor` and `scoreAdvisor` take. Shared by {@link lintSchema} so the
|
|
2069
|
+
* lint run and the scored map always see byte-identical evidence.
|
|
2070
|
+
*
|
|
2071
|
+
* Exported instead of a `mapSchema(options)` convenience that lints *and* scores:
|
|
2072
|
+
* such a wrapper would either re-run every rule or need a `findings` escape hatch
|
|
2073
|
+
* nothing could validate against its `options`, so mismatched findings would
|
|
2074
|
+
* silently produce a wrong map. Two lines at the call site buys that away:
|
|
2075
|
+
*
|
|
2076
|
+
* ```ts
|
|
2077
|
+
* const context = toAdvisorContext(options);
|
|
2078
|
+
* const map = scoreAdvisor(context.procedureProtections ?? [], runAdvisor(context, { source: "static" }));
|
|
2079
|
+
* ```
|
|
2080
|
+
*/
|
|
2081
|
+
declare const toAdvisorContext: (options: LintSchemaOptions) => LintContext;
|
|
2082
|
+
/**
|
|
2083
|
+
* Run the static lints against a discovered {@link SchemaIR} and the reads/writes/calls
|
|
2084
|
+
* found in function bodies: query reads feed `filter_without_index`, insert writes
|
|
2085
|
+
* feed `table_without_insert`, authApi calls feed `auth_api_call_without_headers`,
|
|
2086
|
+
* rls procedure snapshots feed `rls_uncovered_table`, mask procedure
|
|
2087
|
+
* snapshots feed `mask_uncovered_pii_column`, and per-column mask strategies
|
|
2088
|
+
* feed `mask_weak_hash_strategy_on_pii`; declared containers
|
|
2089
|
+
* feed the `container_*` lints; declared workflows (with their durable step labels)
|
|
2090
|
+
* + `ctx.workflows.get(...)` call sites feed the `workflow_unused` /
|
|
2091
|
+
* `workflow_unknown_target` / duplicate-step-name lints; non-deterministic
|
|
2092
|
+
* calls inside query/mutation handlers feed the `nondeterministic_query_mutation` lint
|
|
2093
|
+
* (all default empty for callers that don't analyze functions/containers/workflows).
|
|
2094
|
+
* The IR types are structurally identical to the advisor's evidence types so they
|
|
2095
|
+
* pass straight through without conversion. Returns the findings; surfacing them
|
|
2096
|
+
* (console, error overlay, studio Advisors table) is the caller's choice.
|
|
2097
|
+
*/
|
|
2098
|
+
declare const lintSchema: (options: LintSchemaOptions) => Finding[];
|
|
2099
|
+
/**
|
|
2100
|
+
* Render advisor findings as a single multi-line string for console surfacing:
|
|
2101
|
+
* a one-line summary header followed by one `[LEVEL] name: detail` line per
|
|
2102
|
+
* finding. Returns `""` when there are no findings.
|
|
2103
|
+
*/
|
|
2104
|
+
declare const formatAdvisories: (findings: ReadonlyArray<Finding>) => string;
|
|
2105
|
+
/**
|
|
2106
|
+
* The canonical capability list. **Order is load-bearing** for the `emit-app.ts`
|
|
2107
|
+
* long-tail: the fluent methods are emitted in the order the `appMethod`-bearing
|
|
2108
|
+
* rows appear here, so this array is ordered to reproduce the original
|
|
2109
|
+
* `LONG_TAIL` sequence (ai, analytics, browser, hyperdrive, images, kv, payment,
|
|
2110
|
+
* r2sql, vectors). The `serverCtxField` rows are referenced by name in the ctx
|
|
2111
|
+
* interface templates, so their order here is not output-affecting.
|
|
2112
|
+
*/
|
|
2113
|
+
declare const CAPABILITY_ROWS: readonly [{
|
|
2114
|
+
readonly contextProperty: "access";
|
|
2115
|
+
readonly key: "access";
|
|
2116
|
+
readonly moduleSpecifier: "@lunora/cloudflare-access";
|
|
2117
|
+
}, {
|
|
2118
|
+
readonly appMethod: {
|
|
2119
|
+
readonly configKey: "ai";
|
|
2120
|
+
readonly doc: "Override the Workers AI binding backing `ctx.ai` (defaults to `env.AI`).";
|
|
2121
|
+
readonly method: "ai";
|
|
2122
|
+
};
|
|
2123
|
+
readonly contextProperty: "ai";
|
|
2124
|
+
readonly key: "ai";
|
|
2125
|
+
readonly moduleSpecifier: "@lunora/ai";
|
|
2126
|
+
}, {
|
|
2127
|
+
readonly appMethod: {
|
|
2128
|
+
readonly configKey: "analytics";
|
|
2129
|
+
readonly doc: "Override the Analytics Engine dataset backing `ctx.analytics` (defaults to `env.ANALYTICS`).";
|
|
2130
|
+
readonly method: "analytics";
|
|
2131
|
+
};
|
|
2132
|
+
readonly contextProperty: "analytics";
|
|
2133
|
+
readonly key: "analytics";
|
|
2134
|
+
readonly moduleSpecifier: "@lunora/bindings/analytics";
|
|
2135
|
+
readonly serverCtxField: {
|
|
2136
|
+
readonly field: "\n /** Analytics Engine telemetry sink. Fire-and-forget and sampled; do not read it back in-handler. */\n readonly analytics: import(\"@lunora/bindings/analytics\").AnalyticsClient;";
|
|
2137
|
+
readonly tier: "every";
|
|
2138
|
+
};
|
|
2139
|
+
}, {
|
|
2140
|
+
readonly appMethod: {
|
|
2141
|
+
readonly configKey: "browser";
|
|
2142
|
+
readonly doc: "Override the Browser Rendering binding backing `ctx.browser` (defaults to `env.BROWSER`).";
|
|
2143
|
+
readonly method: "browser";
|
|
2144
|
+
};
|
|
2145
|
+
readonly contextProperty: "browser";
|
|
2146
|
+
readonly key: "browser";
|
|
2147
|
+
readonly moduleSpecifier: "@lunora/browser";
|
|
2148
|
+
readonly serverCtxField: {
|
|
2149
|
+
readonly field: "\n /** Browser Rendering (screenshots/PDF/scrape). Non-deterministic — available only in actions. */\n readonly browser: import(\"@lunora/browser\").Browser;";
|
|
2150
|
+
readonly tier: "action";
|
|
2151
|
+
};
|
|
2152
|
+
}, {
|
|
2153
|
+
readonly contextProperty: "containers";
|
|
2154
|
+
readonly key: "container";
|
|
2155
|
+
readonly moduleSpecifier: "@lunora/container";
|
|
2156
|
+
}, {
|
|
2157
|
+
readonly contextProperty: "flags";
|
|
2158
|
+
readonly key: "flags";
|
|
2159
|
+
readonly moduleSpecifier: "@lunora/flags";
|
|
2160
|
+
}, {
|
|
2161
|
+
readonly appMethod: {
|
|
2162
|
+
readonly configKey: "sql";
|
|
2163
|
+
readonly doc: "Wire the Hyperdrive SQL client backing `ctx.sql` — build it with `createHyperdrive` + `fromPostgresJs`/`fromNodePg`/`fromMysql2`.";
|
|
2164
|
+
readonly method: "hyperdrive";
|
|
2165
|
+
};
|
|
2166
|
+
readonly contextProperty: "sql";
|
|
2167
|
+
readonly key: "hyperdrive";
|
|
2168
|
+
readonly moduleSpecifier: "@lunora/hyperdrive";
|
|
2169
|
+
readonly serverCtxField: {
|
|
2170
|
+
readonly field: "\n /**\n * External database access via Hyperdrive. Non-deterministic — available only in actions. Writes here are NOT tracked by Lunora live queries; subscriptions will not re-run on external DB changes.\n */\n readonly sql: import(\"@lunora/hyperdrive\").SqlClient;";
|
|
2171
|
+
readonly tier: "action";
|
|
2172
|
+
};
|
|
2173
|
+
}, {
|
|
2174
|
+
readonly appMethod: {
|
|
2175
|
+
readonly configKey: "images";
|
|
2176
|
+
readonly doc: "Override the Images binding backing `ctx.images` (defaults to `env.IMAGES`).";
|
|
2177
|
+
readonly method: "images";
|
|
2178
|
+
};
|
|
2179
|
+
readonly contextProperty: "images";
|
|
2180
|
+
readonly key: "images";
|
|
2181
|
+
readonly moduleSpecifier: "@lunora/bindings/images";
|
|
2182
|
+
readonly serverCtxField: {
|
|
2183
|
+
readonly field: "\n /** Cloudflare Images transforms (resize/format/optimize). Non-deterministic — available only in actions. */\n readonly images: import(\"@lunora/bindings/images\").Images;";
|
|
2184
|
+
readonly tier: "action";
|
|
2185
|
+
};
|
|
2186
|
+
}, {
|
|
2187
|
+
readonly appMethod: {
|
|
2188
|
+
readonly configKey: "kv";
|
|
2189
|
+
readonly doc: "Override the Workers KV binding backing `ctx.kv` (defaults to `env.KV`).";
|
|
2190
|
+
readonly method: "kv";
|
|
2191
|
+
};
|
|
2192
|
+
readonly contextProperty: "kv";
|
|
2193
|
+
readonly key: "kv";
|
|
2194
|
+
readonly moduleSpecifier: "@lunora/bindings/kv";
|
|
2195
|
+
readonly serverCtxField: {
|
|
2196
|
+
readonly field: "\n readonly kv: import(\"@lunora/bindings/kv\").Kv;";
|
|
2197
|
+
readonly tier: "every";
|
|
2198
|
+
};
|
|
2199
|
+
}, {
|
|
2200
|
+
readonly key: "mail";
|
|
2201
|
+
readonly moduleSpecifier: "@lunora/mail";
|
|
2202
|
+
}, {
|
|
2203
|
+
readonly contextProperty: "notify";
|
|
2204
|
+
readonly key: "notify";
|
|
2205
|
+
readonly moduleSpecifier: "@lunora/notify";
|
|
2206
|
+
}, {
|
|
2207
|
+
readonly appMethod: {
|
|
2208
|
+
readonly configKey: "payment";
|
|
2209
|
+
readonly doc: "Wire the payment options backing `ctx.payments`.";
|
|
2210
|
+
readonly method: "payment";
|
|
2211
|
+
};
|
|
2212
|
+
readonly contextProperty: "payments";
|
|
2213
|
+
readonly key: "payments";
|
|
2214
|
+
readonly moduleSpecifier: "@lunora/payment";
|
|
2215
|
+
}, {
|
|
2216
|
+
readonly appMethod: {
|
|
2217
|
+
readonly configKey: "x402";
|
|
2218
|
+
readonly doc: "Wire the x402 agent-wallet pay rail backing `ctx.x402` — a payment-enabled `fetch` that answers `402` challenges under a mandatory spend policy (ActionCtx-only; spends real funds).";
|
|
2219
|
+
readonly method: "x402";
|
|
2220
|
+
};
|
|
2221
|
+
readonly contextProperty: "x402";
|
|
2222
|
+
readonly key: "x402";
|
|
2223
|
+
readonly moduleSpecifier: "@lunora/x402/pay";
|
|
2224
|
+
}, {
|
|
2225
|
+
readonly contextProperty: "pipelines";
|
|
2226
|
+
readonly key: "pipelines";
|
|
2227
|
+
readonly moduleSpecifier: "@lunora/bindings/pipelines";
|
|
2228
|
+
readonly serverCtxField: {
|
|
2229
|
+
readonly field: "\n /** Pipelines ingestion sink (durable, R2-backed). Fire-and-forget and batched; do not read it back in-handler. */\n readonly pipelines: import(\"@lunora/bindings/pipelines\").PipelineClient;";
|
|
2230
|
+
readonly tier: "action";
|
|
2231
|
+
};
|
|
2232
|
+
}, {
|
|
2233
|
+
readonly appMethod: {
|
|
2234
|
+
readonly configKey: "r2sql";
|
|
2235
|
+
readonly doc: "Wire the R2 SQL client backing `ctx.r2sql` — build it with `createR2Sql({ accountId, apiToken, bucket })` (defaults to env `R2_SQL_TOKEN` / `R2_SQL_ACCOUNT_ID` / `R2_SQL_BUCKET`).";
|
|
2236
|
+
readonly method: "r2sql";
|
|
2237
|
+
};
|
|
2238
|
+
readonly contextProperty: "r2sql";
|
|
2239
|
+
readonly key: "r2sql";
|
|
2240
|
+
readonly moduleSpecifier: "@lunora/bindings/r2sql";
|
|
2241
|
+
readonly serverCtxField: {
|
|
2242
|
+
readonly field: "\n /**\n * R2 SQL over Apache Iceberg tables (window functions, DISTINCT, set operations). Non-deterministic — available only in actions. Reads here are NOT tracked by Lunora live queries.\n */\n readonly r2sql: import(\"@lunora/bindings/r2sql\").R2SqlClient;";
|
|
2243
|
+
readonly tier: "action";
|
|
2244
|
+
};
|
|
2245
|
+
}, {
|
|
2246
|
+
readonly contextProperty: "scheduler";
|
|
2247
|
+
readonly key: "scheduler";
|
|
2248
|
+
readonly moduleSpecifier: "@lunora/scheduler";
|
|
2249
|
+
}, {
|
|
2250
|
+
readonly contextProperty: "storage";
|
|
2251
|
+
readonly key: "storage";
|
|
2252
|
+
readonly moduleSpecifier: "@lunora/storage";
|
|
2253
|
+
}, {
|
|
2254
|
+
readonly appMethod: {
|
|
2255
|
+
readonly configKey: "vectors";
|
|
2256
|
+
readonly doc: "Wire the Vectorize index map backing `ctx.vectors`.";
|
|
2257
|
+
readonly method: "vectors";
|
|
2258
|
+
};
|
|
2259
|
+
readonly contextProperty: "vectors";
|
|
2260
|
+
readonly key: "vectors";
|
|
2261
|
+
readonly moduleSpecifier: "@lunora/bindings/vectors";
|
|
2262
|
+
}, {
|
|
2263
|
+
readonly contextProperty: "workflows";
|
|
2264
|
+
readonly key: "workflows";
|
|
2265
|
+
readonly moduleSpecifier: "@lunora/workflow";
|
|
2266
|
+
}];
|
|
2267
|
+
/** The literal union of every capability id — the single source of truth for `FeatureUsage`'s keys (so they cannot drift). */
|
|
2268
|
+
type CapabilityKey = (typeof CAPABILITY_ROWS)[number]["key"];
|
|
2269
|
+
/** The default codegen target — today's behavior, byte-identical goldens. */
|
|
2270
|
+
declare const DEFAULT_TARGET = "cloudflare";
|
|
2271
|
+
/**
|
|
2272
|
+
* Read `target` from `<projectRoot>/lunora.json`.
|
|
2273
|
+
*
|
|
2274
|
+
* This lives in `@lunora/codegen` rather than `@lunora/config` — where the rest
|
|
2275
|
+
* of the `lunora.json` reading lives — because `@lunora/config` depends on
|
|
2276
|
+
* `@lunora/codegen`, not the reverse. Putting it there and importing it here
|
|
2277
|
+
* would invert that edge, so config delegates to this instead and there is
|
|
2278
|
+
* still exactly one parser for the key.
|
|
2279
|
+
*
|
|
2280
|
+
* Best-effort and deliberately unvalidated: a missing file, malformed JSONC, or
|
|
2281
|
+
* a non-string value all collapse to `undefined`, because those are shape
|
|
2282
|
+
* errors rather than a name the user meant. An unrecognized *name* is returned
|
|
2283
|
+
* as-is so the caller's registry lookup rejects it — swallowing a typo into the
|
|
2284
|
+
* default would ship an app to the wrong provider.
|
|
2285
|
+
* @param projectRoot Directory containing `lunora.json`.
|
|
2286
|
+
* @returns the declared target, or `undefined` when none is usable.
|
|
2287
|
+
*/
|
|
2288
|
+
declare const readProjectTarget: (projectRoot: string) => string | undefined;
|
|
2289
|
+
/**
|
|
2290
|
+
* The target codegen should emit for: an explicit option wins, then
|
|
2291
|
+
* `lunora.json`, then the default.
|
|
2292
|
+
*
|
|
2293
|
+
* `runCodegen` applies this itself so a caller that forgets to pass a target
|
|
2294
|
+
* still emits the surface the project declared. That default matters more than
|
|
2295
|
+
* it looks: a call site that silently omits the target emits the *default*
|
|
2296
|
+
* surface with no diagnostic to notice, and the mismatch only shows up at
|
|
2297
|
+
* runtime on the deployed app.
|
|
2298
|
+
* @param projectRoot Directory containing `lunora.json`.
|
|
2299
|
+
* @param explicit A caller-supplied target, if any.
|
|
2300
|
+
* @returns the resolved target id — not guaranteed to be registered.
|
|
2301
|
+
*/
|
|
2302
|
+
declare const resolveCodegenTarget: (projectRoot: string, explicit?: string) => string;
|
|
2303
|
+
/**
|
|
2304
|
+
* The target ids codegen can gate against.
|
|
2305
|
+
*
|
|
2306
|
+
* `@lunora/config`'s driver registry (`deployTargetIds`) used to assert
|
|
2307
|
+
* equality against this — "two id spaces for one concept" — on the theory that
|
|
2308
|
+
* a target with a matrix but no driver "gates a surface nothing can deploy."
|
|
2309
|
+
* Plan 234 found that reasoning incomplete by registering `node` here: a
|
|
2310
|
+
* codegen-gateable target and a deployable target are genuinely different
|
|
2311
|
+
* questions, and a spike/dev-only host answers the first "yes" and the second
|
|
2312
|
+
* "not yet" without that being a bug in either registry. See
|
|
2313
|
+
* `plans/234-node-host-findings.md` for the finding and `@lunora/config`'s
|
|
2314
|
+
* `project-config.test.ts` for where the now-relaxed invariant lives.
|
|
2315
|
+
* @returns the registered matrix ids, sorted.
|
|
2316
|
+
*/
|
|
2317
|
+
declare const platformMatrixIds: () => ReadonlyArray<string>;
|
|
2318
|
+
/** An advisor-style diagnostic about a target's platform capabilities. */
|
|
2319
|
+
interface PlatformDiagnostic {
|
|
2320
|
+
/** The codegen capability this concerns, when it is feature-specific. */
|
|
2321
|
+
feature?: CapabilityKey;
|
|
2322
|
+
/** Severity. All three names are errors — each drops or misdirects an emitted surface. */
|
|
2323
|
+
level: "error" | "warn";
|
|
2324
|
+
/** Human-readable explanation of the gap. */
|
|
2325
|
+
message: string;
|
|
2326
|
+
/** The lint id: `platform_unsupported_feature`, `platform_undeclared_feature`, or `platform_unknown_target`. */
|
|
2327
|
+
name: "platform_undeclared_feature" | "platform_unknown_target" | "platform_unsupported_feature";
|
|
2328
|
+
/** How to resolve it. */
|
|
2329
|
+
remediation: string;
|
|
2330
|
+
/** The requested deploy target. */
|
|
2331
|
+
target: string;
|
|
2332
|
+
}
|
|
2333
|
+
/**
|
|
2334
|
+
* Committed, tracked baseline file holding the blessed structural schema
|
|
2335
|
+
* snapshot the pre-deploy drift gate diffs against. Lives in `lunora/` (NOT the
|
|
2336
|
+
* gitignored `_generated/`) so it is committed alongside `schema.ts`. Leading
|
|
2337
|
+
* dot keeps it tucked away next to the schema it describes.
|
|
2338
|
+
*/
|
|
2339
|
+
declare const SCHEMA_SNAPSHOT_FILENAME = ".lunora-schema.json";
|
|
2340
|
+
/**
|
|
2341
|
+
* Walk up from `startPath` until we find a `tsconfig.json` or hit the file
|
|
2342
|
+
* system root. Returns the absolute path to the tsconfig, or `undefined`.
|
|
2343
|
+
*
|
|
2344
|
+
* Exported (see the bottom-of-file `export { findTsconfig }`) so a long-lived
|
|
2345
|
+
* caller (the Vite dev-loop's cached-Project invalidation) can ask "which
|
|
2346
|
+
* tsconfig would {@link createCodegenProject} resolve right now?" without
|
|
2347
|
+
* duplicating the walk — recomputing it per call is a handful of `existsSync`
|
|
2348
|
+
* checks, negligible next to a Project rebuild.
|
|
2349
|
+
*/
|
|
2350
|
+
declare const findTsconfig: (startPath: string) => string | undefined;
|
|
2351
|
+
/**
|
|
2352
|
+
* Construct the ts-morph `Project` codegen discovers over. Prefers the user's
|
|
2353
|
+
* `tsconfig.json` (when one is found walking up from `lunoraDirectory`) so
|
|
2354
|
+
* cross-file type resolution and path aliases work; falls back to an isolated
|
|
2355
|
+
* project otherwise. This is the exact construction {@link runCodegen} uses
|
|
2356
|
+
* when no `project` is injected — exported so a long-lived caller (the Vite
|
|
2357
|
+
* dev-loop) can build one once and reuse it across runs via
|
|
2358
|
+
* {@link refreshCodegenProject} instead of re-parsing the user's whole TS
|
|
2359
|
+
* program on every save.
|
|
2360
|
+
*/
|
|
2361
|
+
declare const createCodegenProject: (lunoraDirectory: string) => Project;
|
|
2362
|
+
/**
|
|
2363
|
+
* Synchronise a reused {@link createCodegenProject} Project with the current
|
|
2364
|
+
* on-disk state of `lunoraDirectory`, so the next {@link runCodegen} sees the
|
|
2365
|
+
* same files a freshly-constructed Project would — without re-parsing the whole
|
|
2366
|
+
* TS program. Adds any on-disk source file the Project doesn't yet have, and
|
|
2367
|
+
* `refreshFromFileSystemSync()`es the ones it does (picking up edits); then
|
|
2368
|
+
* removes Project source files under `lunoraDirectory` that no longer exist on
|
|
2369
|
+
* disk (the classic stale-deleted-file cache bug).
|
|
2370
|
+
*
|
|
2371
|
+
* Files outside `lunoraDirectory` — e.g. a shared validator or type pulled in
|
|
2372
|
+
* via the user's tsconfig — are also `refreshFromFileSystemSync()`ed, but only
|
|
2373
|
+
* the ones already loaded into the Project; none are added. `resolveValidatorAlias`
|
|
2374
|
+
* (parse-validator.ts) follows `getAliasedSymbol()` across module boundaries, so a
|
|
2375
|
+
* validator defined outside `lunoraDirectory` is genuinely read from whatever
|
|
2376
|
+
* source the Project currently holds — leaving those files stale made a reused
|
|
2377
|
+
* Project (the Vite dev loop) silently disagree with a fresh one (`lunora
|
|
2378
|
+
* codegen`) about the same source. `node_modules` is excluded: its `.d.ts` set
|
|
2379
|
+
* dominates the file count and never changes mid dev-loop, so refreshing it would
|
|
2380
|
+
* reinstate close to the full re-parse cost this cache exists to avoid.
|
|
2381
|
+
*/
|
|
2382
|
+
declare const refreshCodegenProject: (project: Project, lunoraDirectory: string) => void;
|
|
2383
|
+
/**
|
|
2384
|
+
* Top-level codegen entry. Parses `<projectRoot>/lunora/schema.ts` and every
|
|
2385
|
+
* function file under `<projectRoot>/lunora/`, then writes
|
|
2386
|
+
* `_generated/{api,server,dataModel}.ts` next to them.
|
|
2387
|
+
*
|
|
2388
|
+
* When `LUNORA_CODEGEN_TIMING` is set (truthy), a single diagnostic summary
|
|
2389
|
+
* line is written to stderr with the total wall time and the discovery-vs-emit
|
|
2390
|
+
* split — opt-in instrumentation that is otherwise zero-cost and side-effect-free
|
|
2391
|
+
* on the returned {@link CodegenResult}.
|
|
2392
|
+
*/
|
|
2393
|
+
declare const runCodegen: (options: CodegenOptions) => CodegenResult;
|
|
2394
|
+
interface CodegenOptions {
|
|
2395
|
+
/**
|
|
2396
|
+
* Which machine-readable API spec(s) to emit into `_generated/`.
|
|
2397
|
+
*
|
|
2398
|
+
* `"openapi"` (the default) writes only `openapi.json` (OpenAPI 3.1; covers
|
|
2399
|
+
* both the RPC functions and `httpRouter()` REST routes). `"openrpc"` writes
|
|
2400
|
+
* only `openrpc.json` (OpenRPC 1.x; the RPC functions only — OpenRPC cannot
|
|
2401
|
+
* represent REST routes). `"both"` writes both files; `"none"` writes neither.
|
|
2402
|
+
*
|
|
2403
|
+
* Regardless of the choice, `CodegenResult.generated.openApi` and `.openRpc`
|
|
2404
|
+
* always carry the rendered string (computation is cheap and pure); only the
|
|
2405
|
+
* on-disk write is gated by this option.
|
|
2406
|
+
*/
|
|
2407
|
+
apiSpec?: "both" | "none" | "openapi" | "openrpc";
|
|
2408
|
+
/**
|
|
2409
|
+
* When true, run discovery + emit (so any schema/function parse error
|
|
2410
|
+
* surfaces) but skip writing files to `_generated/`. The returned
|
|
2411
|
+
* `outputDirectory` is still the path that *would* have been written.
|
|
2412
|
+
*/
|
|
2413
|
+
dryRun?: boolean;
|
|
2414
|
+
/**
|
|
2415
|
+
* Run the static schema advisor (unindexed FKs, …) during codegen.
|
|
2416
|
+
* Defaults to `true`. When `false`, `CodegenResult.advisories` is empty.
|
|
2417
|
+
* Computed regardless of `dryRun`; codegen never prints them — see
|
|
2418
|
+
* {@link CodegenResult.advisories}.
|
|
2419
|
+
*/
|
|
2420
|
+
lint?: boolean;
|
|
2421
|
+
/** Override the lunora subdirectory name. Defaults to `"lunora"`. */
|
|
2422
|
+
lunoraDirectory?: string;
|
|
2423
|
+
/**
|
|
2424
|
+
* Reuse a previously-constructed ts-morph {@link Project} instead of building
|
|
2425
|
+
* a fresh one each run. The caller owns refreshing its source files from disk
|
|
2426
|
+
* (see {@link refreshCodegenProject}) — codegen does not re-read changed files
|
|
2427
|
+
* off an injected Project. Built via {@link createCodegenProject} when absent.
|
|
2428
|
+
* Used by the Vite dev-loop to avoid re-parsing the whole TS program on every
|
|
2429
|
+
* save; omit it (CLI one-shot path) to get the default fresh-Project behaviour.
|
|
2430
|
+
*/
|
|
2431
|
+
project?: Project;
|
|
2432
|
+
/** Project root containing the `lunora/` directory. */
|
|
2433
|
+
projectRoot: string;
|
|
2434
|
+
/**
|
|
2435
|
+
* The deploy target codegen tailors the emitted `ctx.*` surface to.
|
|
2436
|
+
* Defaults to `"cloudflare"` — whose capability matrix marks every feature
|
|
2437
|
+
* native or emulated, so the default output is unchanged (byte-identical
|
|
2438
|
+
* goldens). A target that marks a used feature unsupported omits its
|
|
2439
|
+
* `ctx.*` surface and reports it in {@link CodegenResult.platformDiagnostics}.
|
|
2440
|
+
* Only `"cloudflare"` is registered until other per-target `@lunora/platform`
|
|
2441
|
+
* matrices land; an unknown target emits the full surface un-gated and a
|
|
2442
|
+
* `platform_unknown_target` diagnostic.
|
|
2443
|
+
*/
|
|
2444
|
+
target?: string;
|
|
2445
|
+
/**
|
|
2446
|
+
* Re-bless the committed schema-drift baseline (`lunora/.lunora-schema.json`)
|
|
2447
|
+
* with the current structural snapshot. The baseline is ALWAYS written on
|
|
2448
|
+
* first capture (when the file is absent); set this to overwrite an existing
|
|
2449
|
+
* one — e.g. after the developer has added the data migration that justifies
|
|
2450
|
+
* a breaking change. Ignored when `dryRun` is true.
|
|
2451
|
+
*/
|
|
2452
|
+
updateSchemaBaseline?: boolean;
|
|
2453
|
+
/**
|
|
2454
|
+
* Committed `wrangler.jsonc` `vars` entries that hold plaintext secrets — the
|
|
2455
|
+
* `plaintext_secret_in_wrangler_vars` lint input. Produced by `@lunora/config`
|
|
2456
|
+
* (which reads `wrangler.jsonc`) and threaded through by the CLI / Vite plugin;
|
|
2457
|
+
* codegen only forwards it to the advisor. Absent when no wrangler config is
|
|
2458
|
+
* present or the caller doesn't scan it.
|
|
2459
|
+
*/
|
|
2460
|
+
wranglerVariables?: ReadonlyArray<WranglerVariableIR>;
|
|
2461
|
+
}
|
|
2462
|
+
interface CodegenResult {
|
|
2463
|
+
/**
|
|
2464
|
+
* The normalized advisor evidence the findings were produced from, so a
|
|
2465
|
+
* caller can score it into a health map (`scoreAdvisor`) without re-running
|
|
2466
|
+
* discovery. `undefined` under `lint: false`.
|
|
2467
|
+
*
|
|
2468
|
+
* Deliberately not scored here: the map carries a `generatedAt` stamp, and
|
|
2469
|
+
* codegen's result stays a pure function of the sources.
|
|
2470
|
+
*/
|
|
2471
|
+
advisorContext?: LintContext;
|
|
2472
|
+
/**
|
|
2473
|
+
* Static schema advisor findings (e.g. unindexed foreign keys) produced
|
|
2474
|
+
* this run. Empty when `lint` is `false` or the schema is clean. Codegen
|
|
2475
|
+
* does not print these itself — each caller presents them through its own
|
|
2476
|
+
* channel (the CLI logger, the vite overlay, the studio Advisors table).
|
|
2477
|
+
* `formatAdvisories` is exported for a plain multi-line rendering.
|
|
2478
|
+
*/
|
|
2479
|
+
advisories: ReadonlyArray<Finding>;
|
|
2480
|
+
/**
|
|
2481
|
+
* Agents discovered from `defineAgent` exports in `lunora/agents.ts` — the
|
|
2482
|
+
* list the config layer reconciles into wrangler's `workflows[]` array (an
|
|
2483
|
+
* agent compiles onto a Cloudflare Workflow). Agents are NOT Durable Objects,
|
|
2484
|
+
* so this adds no binding or migration. Empty when the project declares none.
|
|
2485
|
+
*/
|
|
2486
|
+
agents: ReadonlyArray<AgentIR>;
|
|
2487
|
+
/**
|
|
2488
|
+
* Containers discovered from `defineContainer` exports in
|
|
2489
|
+
* `lunora/containers.ts` — the list the config layer reconciles into
|
|
2490
|
+
* wrangler's `containers[]`, `CONTAINER_*` Durable Object bindings, and
|
|
2491
|
+
* migration classes. Empty when the project declares no containers.
|
|
2492
|
+
*/
|
|
2493
|
+
containers: ReadonlyArray<ContainerIR>;
|
|
2494
|
+
/**
|
|
2495
|
+
* Deduplicated cron schedules discovered from `cronJobs()` definitions —
|
|
2496
|
+
* the array the vite plugin reconciles into `wrangler.jsonc`'s
|
|
2497
|
+
* `triggers.crons`. Empty when the project declares no crons.
|
|
2498
|
+
*/
|
|
2499
|
+
cronTriggers: ReadonlyArray<string>;
|
|
2500
|
+
generated: {
|
|
2501
|
+
/** WorkflowEntrypoint classes for declared agents (`_generated/agents.ts`); `""` (and not written) when no agents are declared. */
|
|
2502
|
+
agents: string;
|
|
2503
|
+
api: string;
|
|
2504
|
+
/** Fluent worker-composition builder (`_generated/app.ts`) — `defineApp()`. Always written. */
|
|
2505
|
+
app: string;
|
|
2506
|
+
/** Partial-replication collection factories (`_generated/collections.ts`); `""` (and not written) unless the project declares shapes and installs `@lunora/db`. */
|
|
2507
|
+
collections: string;
|
|
2508
|
+
/** Container DO classes (`_generated/containers.ts`); `""` (and not written) when no containers are declared. */
|
|
2509
|
+
containers: string;
|
|
2510
|
+
crons: string;
|
|
2511
|
+
dataModel: string;
|
|
2512
|
+
drizzleGlobal: string;
|
|
2513
|
+
drizzleShard: string;
|
|
2514
|
+
functions: string;
|
|
2515
|
+
/** OpenAPI 3.1.0 document (`_generated/openapi.json`), pretty-printed JSON. */
|
|
2516
|
+
openApi: string;
|
|
2517
|
+
/**
|
|
2518
|
+
* OpenAPI document as an importable TS module (`_generated/openapi.ts`) —
|
|
2519
|
+
* `export const openApiSpec`, the worker imports it for
|
|
2520
|
+
* `createWorker({ openApiSpec })`. Same document as `openApi`. Written
|
|
2521
|
+
* alongside `openapi.json` whenever `apiSpec` includes `openapi`.
|
|
2522
|
+
*/
|
|
2523
|
+
openApiModule: string;
|
|
2524
|
+
/** OpenRPC 1.x document (`_generated/openrpc.json`), pretty-printed JSON. Always computed; written only when `apiSpec` includes `openrpc`. */
|
|
2525
|
+
openRpc: string;
|
|
2526
|
+
/**
|
|
2527
|
+
* OpenRPC document as an importable TS module (`_generated/openrpc.ts`) —
|
|
2528
|
+
* `export const openRpcSpec`, for `createWorker({ openRpcSpec })`. Same
|
|
2529
|
+
* document as `openRpc`. Written alongside `openrpc.json` whenever
|
|
2530
|
+
* `apiSpec` includes `openrpc`.
|
|
2531
|
+
*/
|
|
2532
|
+
openRpcModule: string;
|
|
2533
|
+
/** Push-consumer queue registry (`_generated/queues.ts`); `""` (and not written) when no push queues are declared. */
|
|
2534
|
+
queues: string;
|
|
2535
|
+
/** Project-bound seed client (`_generated/seed.ts`); `""` (and not written) when `@lunora/seed` is not a declared dependency. */
|
|
2536
|
+
seed: string;
|
|
2537
|
+
server: string;
|
|
2538
|
+
shard: string;
|
|
2539
|
+
/** Static vector-index registry (`_generated/vectors.ts`) — `LUNORA_VECTOR_INDEXES`. Empty array body when the schema declares none. */
|
|
2540
|
+
vectors: string;
|
|
2541
|
+
/** WorkflowEntrypoint classes (`_generated/workflows.ts`); `""` (and not written) when no workflows are declared. */
|
|
2542
|
+
workflows: string;
|
|
2543
|
+
};
|
|
2544
|
+
outputDirectory: string;
|
|
2545
|
+
/**
|
|
2546
|
+
* Portability diagnostics for the requested {@link CodegenOptions.target}:
|
|
2547
|
+
* `ctx.*` features the app uses that the target does not support (omitted
|
|
2548
|
+
* from the emitted surface), or an unknown target. Empty for a
|
|
2549
|
+
* fully-supported app on the default Cloudflare target. Presentation is the
|
|
2550
|
+
* caller's job, like {@link CodegenResult.advisories}.
|
|
2551
|
+
*/
|
|
2552
|
+
platformDiagnostics: ReadonlyArray<PlatformDiagnostic>;
|
|
2553
|
+
/**
|
|
2554
|
+
* Queues discovered from `defineQueue` exports in `lunora/queues.ts` — the
|
|
2555
|
+
* list the config layer reconciles into wrangler's `queues.producers[]` /
|
|
2556
|
+
* `queues.consumers[]`. Queues are NOT Durable Objects, so this adds no
|
|
2557
|
+
* binding or migration. Empty when the project declares no queues.
|
|
2558
|
+
*/
|
|
2559
|
+
queues: ReadonlyArray<QueueIR>;
|
|
2560
|
+
/**
|
|
2561
|
+
* The CURRENT structural schema snapshot computed this run (tables + field
|
|
2562
|
+
* kinds/optionality + indexes/relations/shard mode + declared migration ids).
|
|
2563
|
+
* The pre-deploy drift gate diffs this against the committed baseline read
|
|
2564
|
+
* from {@link CodegenResult.schemaSnapshotPath}. Always present, even on a
|
|
2565
|
+
* `dryRun`.
|
|
2566
|
+
*/
|
|
2567
|
+
schemaSnapshot: SchemaSnapshot;
|
|
2568
|
+
/** Absolute path of the committed baseline file (`lunora/.lunora-schema.json`). */
|
|
2569
|
+
schemaSnapshotPath: string;
|
|
2570
|
+
/**
|
|
2571
|
+
* Workflows discovered from `defineWorkflow` exports in
|
|
2572
|
+
* `lunora/workflows.ts` — the list the config layer reconciles into
|
|
2573
|
+
* wrangler's `workflows[]` array. Workflows are NOT Durable Objects, so this
|
|
2574
|
+
* adds no binding or migration. Empty when the project declares no workflows.
|
|
2575
|
+
*/
|
|
2576
|
+
workflows: ReadonlyArray<WorkflowIR>;
|
|
2577
|
+
}
|
|
2578
|
+
/** Deduplicated, sorted names of every ERROR-level advisory in `advisories`. */
|
|
2579
|
+
declare const errorAdvisoryNames: (advisories: ReadonlyArray<Pick<Finding, "level" | "name">>) => ReadonlyArray<string>;
|
|
2580
|
+
/** Deduplicated, sorted names of every error-level platform diagnostic in `platformDiagnostics`. */
|
|
2581
|
+
declare const errorPlatformDiagnosticNames: (platformDiagnostics: ReadonlyArray<Pick<PlatformDiagnostic, "level" | "name">>) => ReadonlyArray<string>;
|
|
2582
|
+
/**
|
|
2583
|
+
* Convenience read combining both categories from a full {@link CodegenResult}
|
|
2584
|
+
* — the shape the Vite plugin's `buildBlockingMessage` needs, which (unlike
|
|
2585
|
+
* the CLI's `lunora codegen`/`lunora deploy`) folds ERROR-level advisories and
|
|
2586
|
+
* platform diagnostics into a single blocking message with no strict/CI
|
|
2587
|
+
* opt-out.
|
|
2588
|
+
*/
|
|
2589
|
+
declare const describeErrorLevelFindings: (result: Pick<CodegenResult, "advisories" | "platformDiagnostics">) => {
|
|
2590
|
+
advisoryNames: ReadonlyArray<string>;
|
|
2591
|
+
platformDiagnosticNames: ReadonlyArray<string>;
|
|
2592
|
+
};
|
|
2593
|
+
/**
|
|
2594
|
+
* An error thrown by codegen discovery when the user's schema or function
|
|
2595
|
+
* source has a structural problem that can be pinpointed to a specific source
|
|
2596
|
+
* location. A `LunoraError` subclass (`code: "CODEGEN_DIAGNOSTIC"`); the `file`,
|
|
2597
|
+
* `line`, and `column` properties (also passed through as the base `loc`) mirror
|
|
2598
|
+
* what Vite's error-overlay `loc` field expects so the browser can display the
|
|
2599
|
+
* exact spot.
|
|
2600
|
+
*/
|
|
2601
|
+
declare class CodegenDiagnosticError extends LunoraError {
|
|
2602
|
+
readonly column: number;
|
|
2603
|
+
readonly file: string;
|
|
2604
|
+
readonly line: number;
|
|
2605
|
+
constructor(message: string, file: string, line: number, column: number);
|
|
2606
|
+
}
|
|
2607
|
+
/**
|
|
2608
|
+
* Build a {@link CodegenDiagnosticError} whose message includes the source
|
|
2609
|
+
* location and whose `file`/`line`/`column` properties are set from the
|
|
2610
|
+
* ts-morph `Node`'s position in its source file.
|
|
2611
|
+
*
|
|
2612
|
+
* Message format: `@lunora/codegen: <detail> (<file>:<line>:<column>)`
|
|
2613
|
+
*
|
|
2614
|
+
* `meta` is merged onto the returned error for callers that also carry the
|
|
2615
|
+
* project-wide `LunoraError` envelope (`code`/`name`/`status`) — it never
|
|
2616
|
+
* touches `file`/`line`/`column`, and the error stays an instance of
|
|
2617
|
+
* {@link CodegenDiagnosticError} so the Vite overlay's `instanceof` location
|
|
2618
|
+
* lookup is unaffected.
|
|
2619
|
+
*/
|
|
2620
|
+
declare const diagnosticAt: (node: Node, detail: string, meta?: Record<string, unknown>) => CodegenDiagnosticError;
|
|
2621
|
+
/** The only file agents may be declared in — mirrors `lunora/workflows.ts`. */
|
|
2622
|
+
declare const AGENTS_FILENAME = "agents.ts";
|
|
2623
|
+
/**
|
|
2624
|
+
* Discover every agent the project declares: exported `defineAgent()` calls in
|
|
2625
|
+
* `lunora/agents.ts`. Returns `[]` when the file doesn't exist. Only four things
|
|
2626
|
+
* are read statically — the optional `name` override (wrangler `workflows[].name`),
|
|
2627
|
+
* the optional `publicRun` opt-in (the `agents:agentRun` capability gate), the
|
|
2628
|
+
* presence of a `voice` block (which turns on the voice-session Durable Object),
|
|
2629
|
+
* and the presence of an `onEmail` mapper (which wires the worker `email()`
|
|
2630
|
+
* handler); the rest of the agent config (model / tools / memory / voice models /
|
|
2631
|
+
* the `onEmail` closure body) is runtime-only, so codegen never evaluates it.
|
|
2632
|
+
*/
|
|
2633
|
+
declare const discoverAgents: (project: Project, lunoraDirectory: string) => AgentIR[];
|
|
2634
|
+
/**
|
|
2635
|
+
* Discover `ctx.authApi.<method>(...)` (and bare `authApi.<method>(...)`) calls
|
|
2636
|
+
* under the lunora source directory and attribute each to the exported function
|
|
2637
|
+
* (and file) performing it. Calls outside an exported declaration are dropped.
|
|
2638
|
+
*/
|
|
2639
|
+
declare const discoverAuthApiCalls: (project: Project, lunoraDirectory: string) => AuthApiCallIR[];
|
|
2640
|
+
/** The only file containers may be declared in — mirrors `lunora/crons.ts`. */
|
|
2641
|
+
declare const CONTAINERS_FILENAME = "containers.ts";
|
|
2642
|
+
/**
|
|
2643
|
+
* Discover every container the project declares: exported `defineContainer()`
|
|
2644
|
+
* calls in `lunora/containers.ts`. Returns `[]` when the file doesn't exist.
|
|
2645
|
+
* Wrangler-relevant fields (`image`, `instanceType`, `maxInstances`, `name`)
|
|
2646
|
+
* must be static literals; runtime-only fields (`env`, `sleepAfter`, …) may be
|
|
2647
|
+
* any expression since the generated class imports the definition object.
|
|
2648
|
+
*/
|
|
2649
|
+
declare const discoverContainers: (project: Project, lunoraDirectory: string) => ContainerIR[];
|
|
2650
|
+
/**
|
|
2651
|
+
* Scan every `.ts` file under `lunoraDir` for `cronJobs()` builder registrations
|
|
2652
|
+
* (`crons.interval(...)`, `crons.daily(...)`, `crons.cron(...)`, …) and lift them
|
|
2653
|
+
* into {@link CronJobIR}. Schedules are compiled to standard cron expressions;
|
|
2654
|
+
* function references are resolved to their `namespace:fn` dispatch path, while a
|
|
2655
|
+
* `workflows.NAME` / `agents.NAME` reference (or a bare identifier naming a
|
|
2656
|
+
* declared workflow) resolves to a durable workflow start. Names must be unique
|
|
2657
|
+
* across the project.
|
|
2658
|
+
*/
|
|
2659
|
+
declare const discoverCrons: (project: Project, lunoraDirectory: string, workflows?: ReadonlyArray<WorkflowIR>, agents?: ReadonlyArray<AgentIR>) => CronJobIR[];
|
|
2660
|
+
/** The only file a feature-flag provider may be declared in — mirrors `lunora/queues.ts`. */
|
|
2661
|
+
declare const FLAGS_FILENAME = "flags.ts";
|
|
2662
|
+
/**
|
|
2663
|
+
* Discover the feature-flag provider a project declares in `lunora/flags.ts`.
|
|
2664
|
+
* Returns `undefined` when the file doesn't exist (the app has no flags). The
|
|
2665
|
+
* read is metadata-only and lenient: codegen wires `ctx.flags` purely from the
|
|
2666
|
+
* file's *existence* (`run-codegen.ts`) and imports the real module for the
|
|
2667
|
+
* provider value — this IR exists solely so the config layer can reconcile the
|
|
2668
|
+
* wrangler `flagship` binding for the Flagship binding-mode provider. Anything
|
|
2669
|
+
* it can't read statically degrades to a `custom` provider (no binding), never
|
|
2670
|
+
* a thrown error.
|
|
2671
|
+
*/
|
|
2672
|
+
declare const discoverFlags: (project: Project, lunoraDirectory: string) => FlagsIR | undefined;
|
|
2673
|
+
/**
|
|
2674
|
+
* Scan all .ts files under `lunoraDir` (skipping `_generated/` and `schema.ts`)
|
|
2675
|
+
* for top-level `export const x = query/mutation/action({...})` registrations.
|
|
2676
|
+
*/
|
|
2677
|
+
declare const discoverFunctions: (project: Project, lunoraDirectory: string) => FunctionIR[];
|
|
2678
|
+
/**
|
|
2679
|
+
* Scan all `.ts` files under `lunoraDir` (skipping `_generated/` and `schema.ts`)
|
|
2680
|
+
* for `export const x = httpRoute.<verb>(...)…handler(...)` typed REST routes.
|
|
2681
|
+
* These are the headline OpenAPI target: each becomes a real `paths` entry.
|
|
2682
|
+
*/
|
|
2683
|
+
declare const discoverHttpRoutes: (project: Project, lunoraDirectory: string) => HttpRouteIR[];
|
|
2684
|
+
/**
|
|
2685
|
+
* Discover `ctx.db.insert("table", …)` writes under the lunora source directory
|
|
2686
|
+
* and attribute each to the exported function (and file) performing it. Calls
|
|
2687
|
+
* with a non-literal table argument, or outside an exported declaration, are
|
|
2688
|
+
* dropped (`table === ""` / no enclosing export).
|
|
2689
|
+
*/
|
|
2690
|
+
declare const discoverInserts: (project: Project, lunoraDirectory: string) => InsertWriteIR[];
|
|
2691
|
+
/**
|
|
2692
|
+
* Discover masking usage for every exported Lunora procedure under the lunora
|
|
2693
|
+
* source directory — the column-level twin of `discoverRlsProcedures`. For each
|
|
2694
|
+
* procedure, records whether its builder chain includes `.use(mask(...))`, which
|
|
2695
|
+
* `(table, column)` pairs that mask declares, and which tables it reads/writes
|
|
2696
|
+
* through `ctx.db`. Feeds the `mask_uncovered_pii_column` advisor lint.
|
|
2697
|
+
*/
|
|
2698
|
+
declare const discoverMaskProcedures: (project: Project, lunoraDirectory: string) => MaskProcedureIR[];
|
|
2699
|
+
/**
|
|
2700
|
+
* Scan all `.ts` files under `lunoraDir` for top-level
|
|
2701
|
+
* `export const x = defineMigration({...})` declarations and lift them into
|
|
2702
|
+
* {@link MigrationIR}. `id` must be a static string literal (it's the registry
|
|
2703
|
+
* key); `table` is best-effort and left `""` when not a literal.
|
|
2704
|
+
*/
|
|
2705
|
+
declare const discoverMigrations: (project: Project, lunoraDirectory: string) => MigrationIR[];
|
|
2706
|
+
/** The only file custom mutators may be declared in — mirrors `lunora/queues.ts`. */
|
|
2707
|
+
declare const MUTATORS_FILENAME = "mutators.ts";
|
|
2708
|
+
/**
|
|
2709
|
+
* Discover every custom mutator the project declares: exported
|
|
2710
|
+
* `defineMutator()` calls in `lunora/mutators.ts`. Returns `[]` when the file
|
|
2711
|
+
* doesn't exist. The export binding plus the declared `args` / `server` return
|
|
2712
|
+
* type are lifted — enough to emit a typed `api.mutators.<name>` reference —
|
|
2713
|
+
* while the runtime object still carries the authoritative `server` impl +
|
|
2714
|
+
* `handler`, so codegen never evaluates the body. The client `client` impl is
|
|
2715
|
+
* split into the browser bundle separately.
|
|
2716
|
+
*/
|
|
2717
|
+
declare const discoverMutators: (project: Project, lunoraDirectory: string) => MutatorIR[];
|
|
2718
|
+
/**
|
|
2719
|
+
* Discover non-deterministic API calls (`Date.now`, `new Date()`, `Date()`,
|
|
2720
|
+
* `Math.random`, `crypto.randomUUID`, `crypto.getRandomValues` — including
|
|
2721
|
+
* `globalThis`/`self`/`window`-prefixed receivers — and `fetch`) lexically inside
|
|
2722
|
+
* the handler body of every exported `query(...)` / `mutation(...)` registration
|
|
2723
|
+
* under the lunora source directory — the `nondeterministic_query_mutation` lint
|
|
2724
|
+
* input. `action(...)` (and `stream(...)`) registrations are intentionally
|
|
2725
|
+
* skipped: actions run exactly once and may use ambient APIs freely.
|
|
2726
|
+
*
|
|
2727
|
+
* Traversal is scoped to the handler node (not the whole declaration), mirroring
|
|
2728
|
+
* how the auth-api / insert feeders attribute calls — so a call in a sibling
|
|
2729
|
+
* helper outside the handler, or in a nested `action(...)` passed elsewhere, is
|
|
2730
|
+
* not attributed to the query/mutation. One {@link NondeterministicCallIR} is
|
|
2731
|
+
* produced per call site.
|
|
2732
|
+
*/
|
|
2733
|
+
declare const discoverNondeterministicCalls: (project: Project, lunoraDirectory: string) => NondeterministicCallIR[];
|
|
2734
|
+
/** The only file a `@lunora/notify` provider may be declared in — mirrors `lunora/flags.ts`. */
|
|
2735
|
+
declare const NOTIFY_FILENAME = "notify.ts";
|
|
2736
|
+
/**
|
|
2737
|
+
* Discover `ctx.notify` / `ctx.push` sends lexically inside the handler body of
|
|
2738
|
+
* every exported `query(...)` / `mutation(...)` registration under the lunora
|
|
2739
|
+
* source directory — the `notify_send_outside_action` lint input. `action(...)`
|
|
2740
|
+
* (and `stream(...)`) registrations are intentionally skipped: a notification
|
|
2741
|
+
* send is external I/O that belongs in actions. One {@link AdvisorNotifyCall} is
|
|
2742
|
+
* produced per send site.
|
|
2743
|
+
*/
|
|
2744
|
+
declare const discoverNotifyCalls: (project: Project, lunoraDirectory: string) => AdvisorNotifyCall[];
|
|
2745
|
+
/**
|
|
2746
|
+
* Discover which push channels the project's `lunora/notify.ts` default export
|
|
2747
|
+
* (`defineNotify({...})`) wires plus whether any handler sends a push — the
|
|
2748
|
+
* `notify_missing_push_config` lint input. Returns `undefined` when the file is
|
|
2749
|
+
* absent (the app declares no notify config). The read is metadata-only and
|
|
2750
|
+
* lenient (like `discoverFlags`): a `webPush`/`fcm` property's mere presence
|
|
2751
|
+
* counts as the channel being wired; a non-literal config degrades to "unwired"
|
|
2752
|
+
* rather than throwing.
|
|
2753
|
+
*/
|
|
2754
|
+
declare const discoverNotifyConfig: (project: Project, lunoraDirectory: string) => AdvisorNotifyConfig | undefined;
|
|
2755
|
+
/**
|
|
2756
|
+
* Every package name the project declares a dependency on, read from the
|
|
2757
|
+
* `package.json` at the project root across all four dependency fields
|
|
2758
|
+
* (`dependencies`, `devDependencies`, `peerDependencies`,
|
|
2759
|
+
* `optionalDependencies`).
|
|
2760
|
+
*
|
|
2761
|
+
* Returns `undefined` — NOT an empty set — when the manifest is absent or
|
|
2762
|
+
* unparseable, which is the whole reason this exists. Studio nav gating can
|
|
2763
|
+
* treat "declares nothing" and "cannot tell" the same way, but a check that
|
|
2764
|
+
* errors on a missing package cannot: conflating them makes it fire on every
|
|
2765
|
+
* project without a root `package.json` (the codegen fixtures, an embedded
|
|
2766
|
+
* schema, a tool driving `runCodegen` directly).
|
|
2767
|
+
*/
|
|
2768
|
+
declare const readPackageDependencies: (projectRoot: string) => Set<string> | undefined;
|
|
2769
|
+
/**
|
|
2770
|
+
* Discover every `ctx.db.query("table")…` read under the lunora source directory
|
|
2771
|
+
* and reduce each to a {@link QueryReadIR}.
|
|
2772
|
+
*
|
|
2773
|
+
* Reads without a `.filter()` are kept too. They are never
|
|
2774
|
+
* `filter_without_index` candidates (that lint gates on `hasFilter`), but an
|
|
2775
|
+
* unfiltered, unindexed `.collect()` is the read `unbounded_collect` exists for
|
|
2776
|
+
* — and dropping it here is precisely why nothing could see it.
|
|
2777
|
+
*/
|
|
2778
|
+
declare const discoverQueries: (project: Project, lunoraDirectory: string) => QueryReadIR[];
|
|
2779
|
+
/** The only file queues may be declared in — mirrors `lunora/workflows.ts`. */
|
|
2780
|
+
declare const QUEUES_FILENAME = "queues.ts";
|
|
2781
|
+
/**
|
|
2782
|
+
* Discover every queue the project declares: exported `defineQueue()` calls in
|
|
2783
|
+
* `lunora/queues.ts`. Returns `[]` when the file doesn't exist. Only the
|
|
2784
|
+
* wrangler-relevant literals (`name`/`mode`/batch tuning) are read; the handler
|
|
2785
|
+
* body is runtime-only, so codegen never evaluates it.
|
|
2786
|
+
*/
|
|
893
2787
|
declare const discoverQueues: (project: Project, lunoraDirectory: string) => QueueIR[];
|
|
894
2788
|
/**
|
|
895
|
-
* Discover `ctx.r2sql` accesses lexically inside the handler body of every
|
|
896
|
-
* exported `query(...)` / `mutation(...)` registration under the lunora source
|
|
897
|
-
* directory — the `r2sql_outside_action` lint input. `action(...)` (and
|
|
898
|
-
* `stream(...)`) registrations are intentionally skipped: R2 SQL is the
|
|
899
|
-
* external, non-reactive surface that belongs in actions.
|
|
900
|
-
*
|
|
901
|
-
* Traversal is scoped to the handler node (not the whole declaration), mirroring
|
|
902
|
-
* `discoverNondeterministicCalls` — so a `ctx.r2sql` touch in a sibling helper
|
|
903
|
-
* outside the handler is not attributed to the query/mutation. One
|
|
904
|
-
* {@link R2sqlCallIR} is produced per access site.
|
|
905
|
-
*/
|
|
2789
|
+
* Discover `ctx.r2sql` accesses lexically inside the handler body of every
|
|
2790
|
+
* exported `query(...)` / `mutation(...)` registration under the lunora source
|
|
2791
|
+
* directory — the `r2sql_outside_action` lint input. `action(...)` (and
|
|
2792
|
+
* `stream(...)`) registrations are intentionally skipped: R2 SQL is the
|
|
2793
|
+
* external, non-reactive surface that belongs in actions.
|
|
2794
|
+
*
|
|
2795
|
+
* Traversal is scoped to the handler node (not the whole declaration), mirroring
|
|
2796
|
+
* `discoverNondeterministicCalls` — so a `ctx.r2sql` touch in a sibling helper
|
|
2797
|
+
* outside the handler is not attributed to the query/mutation. One
|
|
2798
|
+
* {@link R2sqlCallIR} is produced per access site.
|
|
2799
|
+
*/
|
|
906
2800
|
declare const discoverR2sqlCalls: (project: Project, lunoraDirectory: string) => R2sqlCallIR[];
|
|
907
2801
|
declare const discoverRlsProcedures: (project: Project, lunoraDirectory: string) => RlsProcedureIR[];
|
|
908
2802
|
/**
|
|
909
|
-
* Aggregate the schema-wide RLS metadata the studio's read-only inspector reads:
|
|
910
|
-
* every statically-discovered `(table, on, procedure)` policy entry plus every
|
|
911
|
-
* role declared via `rls(policies, { roles })`. Walks the same builder chains as
|
|
912
|
-
* {@link discoverRlsProcedures} but extracts the richer `{ on }` operation +
|
|
913
|
-
* role/permission shape rather than the lint's table-name set.
|
|
914
|
-
*
|
|
915
|
-
* Only the **builder** form (`c.use(rls(...)).query(...)`) can declare policies,
|
|
916
|
-
* so bare-factory procedures contribute nothing. The `when` predicate is never
|
|
917
|
-
* read — it's an opaque JS closure whose logic belongs in code, not the UI.
|
|
918
|
-
* Roles are deduped by name (first declaration wins) so a role registered on
|
|
919
|
-
* several procedures lists once.
|
|
920
|
-
*/
|
|
2803
|
+
* Aggregate the schema-wide RLS metadata the studio's read-only inspector reads:
|
|
2804
|
+
* every statically-discovered `(table, on, procedure)` policy entry plus every
|
|
2805
|
+
* role declared via `rls(policies, { roles })`. Walks the same builder chains as
|
|
2806
|
+
* {@link discoverRlsProcedures} but extracts the richer `{ on }` operation +
|
|
2807
|
+
* role/permission shape rather than the lint's table-name set.
|
|
2808
|
+
*
|
|
2809
|
+
* Only the **builder** form (`c.use(rls(...)).query(...)`) can declare policies,
|
|
2810
|
+
* so bare-factory procedures contribute nothing. The `when` predicate is never
|
|
2811
|
+
* read — it's an opaque JS closure whose logic belongs in code, not the UI.
|
|
2812
|
+
* Roles are deduped by name (first declaration wins) so a role registered on
|
|
2813
|
+
* several procedures lists once.
|
|
2814
|
+
*/
|
|
921
2815
|
declare const discoverRlsMetadata: (project: Project, lunoraDirectory: string) => RlsMetadataIR;
|
|
922
2816
|
/**
|
|
923
|
-
*
|
|
924
|
-
*
|
|
925
|
-
|
|
926
|
-
|
|
2817
|
+
* Which sandbox tools a project imports from `@lunora/agent` (main entry or the
|
|
2818
|
+
* `/sandbox` subpath), detected by NAMED value import. Drives two things:
|
|
2819
|
+
* registering the `sandbox:invoke` dispatcher (either tool) and provisioning the
|
|
2820
|
+
* `BROWSER` wrangler binding (`browserTool` — the browser op runs on
|
|
2821
|
+
* `ctx.browser` inside the dispatcher).
|
|
2822
|
+
*/
|
|
2823
|
+
interface SandboxUsage {
|
|
2824
|
+
/** `import { browserTool } from "@lunora/agent"` (or `/sandbox`) appears in `lunora/`. */
|
|
2825
|
+
usesSandboxBrowser: boolean;
|
|
2826
|
+
/** `import { containerTool } from "@lunora/agent"` (or `/sandbox`) appears in `lunora/`. */
|
|
2827
|
+
usesSandboxContainer: boolean;
|
|
2828
|
+
}
|
|
2829
|
+
declare const discoverSandboxUsage: (project: Project, lunoraDirectory: string) => SandboxUsage;
|
|
927
2830
|
/**
|
|
928
|
-
*
|
|
929
|
-
*
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
*/
|
|
2831
|
+
* Load `<projectRoot>/lunora/schema.ts`, find `defineSchema({...})`, and
|
|
2832
|
+
* return a structural IR. Throws if the file or call cannot be found.
|
|
2833
|
+
*/
|
|
2834
|
+
declare const discoverSchema: (project: Project, schemaPath: string, projectRoot?: string) => SchemaIR;
|
|
2835
|
+
/** The only file shapes may be declared in — mirrors `lunora/queues.ts`. */
|
|
2836
|
+
declare const SHAPES_FILENAME = "shapes.ts";
|
|
2837
|
+
/**
|
|
2838
|
+
* Discover every replication shape the project declares: exported
|
|
2839
|
+
* `defineShape()` calls in `lunora/shapes.ts`. Returns `[]` when the file
|
|
2840
|
+
* doesn't exist. Only the export binding is lifted — the runtime object carries
|
|
2841
|
+
* the authoritative `table`/`columns`/`compileWhere`, so codegen never
|
|
2842
|
+
* evaluates the predicate.
|
|
2843
|
+
*/
|
|
2844
|
+
declare const discoverShapes: (project: Project, lunoraDirectory: string) => ShapeIR[];
|
|
2845
|
+
/**
|
|
2846
|
+
* Aggregate the schema-wide storage-rule metadata the studio's inspector reads:
|
|
2847
|
+
* every statically-discovered `(bucket, on, prefix, procedure)` entry across all
|
|
2848
|
+
* `.use(storageRules(...))` chains. Only the builder form can declare rules, so
|
|
2849
|
+
* bare-factory procedures contribute nothing.
|
|
2850
|
+
*/
|
|
933
2851
|
declare const discoverStorageRulesMetadata: (project: Project, lunoraDirectory: string) => StorageRulesMetadataIR;
|
|
934
2852
|
/** The only file workflows may be declared in — mirrors `lunora/containers.ts`. */
|
|
935
2853
|
declare const WORKFLOWS_FILENAME = "workflows.ts";
|
|
936
2854
|
/**
|
|
937
|
-
* Discover every workflow the project declares: exported `defineWorkflow()`
|
|
938
|
-
* calls in `lunora/workflows.ts`. Returns `[]` when the file doesn't exist. The
|
|
939
|
-
* only wrangler-relevant literal is the optional `name` override; the workflow
|
|
940
|
-
* body is runtime-only, so codegen never evaluates it.
|
|
941
|
-
*/
|
|
2855
|
+
* Discover every workflow the project declares: exported `defineWorkflow()`
|
|
2856
|
+
* calls in `lunora/workflows.ts`. Returns `[]` when the file doesn't exist. The
|
|
2857
|
+
* only wrangler-relevant literal is the optional `name` override; the workflow
|
|
2858
|
+
* body is runtime-only, so codegen never evaluates it.
|
|
2859
|
+
*/
|
|
942
2860
|
declare const discoverWorkflows: (project: Project, lunoraDirectory: string) => WorkflowIR[];
|
|
943
2861
|
declare const GENERATED_HEADER = "// GENERATED by @lunora/codegen — do not edit.\n// Run `lunora codegen` to regenerate.\n\n";
|
|
944
|
-
/** Emit `_generated/dataModel.ts` — `Doc
|
|
945
|
-
declare const emitDataModel: (schema: SchemaIR
|
|
946
|
-
/**
|
|
947
|
-
* Emit `_generated/api.ts` — the typed `api.*` registry (public functions), the
|
|
948
|
-
* `internal.*` registry, and (when the project declares
|
|
949
|
-
* `workflows.*` reference
|
|
950
|
-
* at runtime (the `__lunoraRef` is identical); visibility is enforced
|
|
951
|
-
* server-side at dispatch, not in the reference. Splitting the *types* keeps
|
|
952
|
-
* internal functions off the client-facing `api` surface.
|
|
953
|
-
*/
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
2862
|
+
/** Emit `_generated/dataModel.ts` — `Doc<"name">` + `Id<"name">` for every table. */
|
|
2863
|
+
declare const emitDataModel: (schema: SchemaIR) => string;
|
|
2864
|
+
/**
|
|
2865
|
+
* Emit `_generated/api.ts` — the typed `api.*` registry (public functions), the
|
|
2866
|
+
* `internal.*` registry, and (when the project declares them) the typed
|
|
2867
|
+
* `workflows.*` / `agents.*` scheduler-target reference objects. `api`/`internal` are the same `anyApi` proxy
|
|
2868
|
+
* at runtime (the `__lunoraRef` is identical); visibility is enforced
|
|
2869
|
+
* server-side at dispatch, not in the reference. Splitting the *types* keeps
|
|
2870
|
+
* internal functions off the client-facing `api` surface.
|
|
2871
|
+
*/
|
|
2872
|
+
interface EmitApiOptions {
|
|
2873
|
+
agents?: ReadonlyArray<AgentIR>;
|
|
2874
|
+
functions: ReadonlyArray<FunctionIR>;
|
|
2875
|
+
/** Typed REST routes; only `.stream()` (SSE) routes emit a `httpStreams.*` reference. */
|
|
2876
|
+
httpRoutes?: ReadonlyArray<HttpRouteIR>;
|
|
2877
|
+
/** Custom mutators (`lunora/mutators.ts`) — emitted as `api.mutators.*` so a client `serverRef` is compile-checked. */
|
|
2878
|
+
mutators?: ReadonlyArray<MutatorIR>;
|
|
2879
|
+
useUmbrella?: boolean;
|
|
2880
|
+
workflows?: ReadonlyArray<WorkflowIR>;
|
|
2881
|
+
}
|
|
2882
|
+
declare const emitApi: (options: EmitApiOptions) => string;
|
|
2883
|
+
/**
|
|
2884
|
+
* Emit `_generated/collections.ts` — a typed TanStack DB binding per `defineShape`
|
|
2885
|
+
* in `lunora/shapes.ts` (the local-first partial-replication surface).
|
|
2886
|
+
*
|
|
2887
|
+
* Each shape emits **two** entry points.
|
|
2888
|
+
*
|
|
2889
|
+
* `<shape>CollectionOptions(options)` is the composable form: it returns the full
|
|
2890
|
+
* `LunoraCollectionOptions` — `config` for `createCollection`, plus `checkpoints`
|
|
2891
|
+
* (which `bindMutators` gates optimistic overlays on) and `scope`. This is what an app
|
|
2892
|
+
* with custom mutators needs, and what the old single-factory form made impossible: it
|
|
2893
|
+
* built the collection internally and dropped `checkpoints` on the floor, so there was
|
|
2894
|
+
* no way to wire mutators to the collection codegen produced.
|
|
2895
|
+
*
|
|
2896
|
+
* `<shape>Collection(options)` is the convenience form for a read-only collection: it
|
|
2897
|
+
* returns `{ checkpoints, collection, scope }` rather than a bare `Collection`, so the
|
|
2898
|
+
* sync controls stay reachable even from the short path.
|
|
2899
|
+
*
|
|
2900
|
+
* Both are typed: `args` comes from the shape's own validators (a parameterless
|
|
2901
|
+
* shape takes none), rows resolve to `Doc<"table">` when the shape names its table
|
|
2902
|
+
* with a literal, and `shardKey` / `getKey` / `load` / `onError` / `checkpoints` are
|
|
2903
|
+
* all threadable — a sharded table needs `shardKey` for its watermark to land in the
|
|
2904
|
+
* right bucket, and a server-minted `_id` that differs from the app's natural key
|
|
2905
|
+
* needs `getKey`.
|
|
2906
|
+
*
|
|
2907
|
+
* Returns `""` (so `writeIfPresent` skips the file) unless the project both
|
|
2908
|
+
* declares shapes AND installs `@lunora/db` — the add-on that ships
|
|
2909
|
+
* `lunoraCollectionOptions`. `@lunora/db` stays a scoped install even under the
|
|
2910
|
+
* `lunorash` umbrella (an opt-in add-on, like `@lunora/auth`), so its import is
|
|
2911
|
+
* always `@lunora/db/collections`; only the in-umbrella `@lunora/client` import
|
|
2912
|
+
* is remapped to `lunorash/client`.
|
|
2913
|
+
*/
|
|
2914
|
+
declare const emitCollections: (shapes: ReadonlyArray<ShapeIR>, hasDatabase: boolean, useUmbrella?: boolean) => string;
|
|
965
2915
|
interface EmitServerOptions {
|
|
2916
|
+
/** Agents declared via `defineAgent` exports — wires the typed `ctx.agents` producers onto Mutation/Action contexts. */
|
|
2917
|
+
agents?: ReadonlyArray<AgentIR>;
|
|
966
2918
|
containers?: ReadonlyArray<ContainerIR>;
|
|
2919
|
+
/**
|
|
2920
|
+
* The single `defineEnv(...)` contract declared in `lunora/env.ts`. When
|
|
2921
|
+
* present, `ctx.env` is typed as the validated `InferEnv` shape (recovered
|
|
2922
|
+
* via `ReturnType` over the accessor's `typeof`). `undefined` leaves `ctx.env`
|
|
2923
|
+
* the base optional binding record — byte-identical to today.
|
|
2924
|
+
*/
|
|
2925
|
+
env?: EnvIR;
|
|
2926
|
+
/**
|
|
2927
|
+
* A `lunora/` source reads `ctx.access` — wires the verified Cloudflare Access
|
|
2928
|
+
* facade (`@lunora/cloudflare-access/context`) onto every ctx. Distinct from
|
|
2929
|
+
* `emitApp`'s `hasAccess` (which gates the worker's `.access()` resolveIdentity
|
|
2930
|
+
* method); this one gates the per-request `ctx.access` read surface.
|
|
2931
|
+
*/
|
|
2932
|
+
hasAccessFacade?: boolean;
|
|
967
2933
|
hasAi?: boolean;
|
|
968
2934
|
/** A `lunora/` source uses `@lunora/bindings/analytics` / `ctx.analytics` — wires the write helper onto every ctx. */
|
|
969
2935
|
hasAnalytics?: boolean;
|
|
970
2936
|
/** A `lunora/` source uses `@lunora/browser` / `ctx.browser` — wires `ctx.browser` onto ActionCtx only. */
|
|
971
2937
|
hasBrowser?: boolean;
|
|
2938
|
+
/** The project declares `lunora/flags.ts` — wires `ctx.flags` (OpenFeature) onto every ctx. */
|
|
2939
|
+
hasFlags?: boolean;
|
|
972
2940
|
/** A `lunora/` source uses `@lunora/hyperdrive` / `ctx.sql` — wires `ctx.sql` onto ActionCtx only. */
|
|
973
2941
|
hasHyperdrive?: boolean;
|
|
974
2942
|
/** A `lunora/` source uses `@lunora/bindings/images` / `ctx.images` — wires `ctx.images` onto ActionCtx only. */
|
|
975
2943
|
hasImages?: boolean;
|
|
976
2944
|
/** A `lunora/` source uses `@lunora/bindings/kv` / `ctx.kv` — wires `ctx.kv` onto every ctx. */
|
|
977
2945
|
hasKv?: boolean;
|
|
2946
|
+
/** The project declares `lunora/notify.ts` — wires `ctx.notify` + its `ctx.push` alias (`@lunora/notify`) onto every ctx. */
|
|
2947
|
+
hasNotify?: boolean;
|
|
978
2948
|
hasPayments?: boolean;
|
|
979
2949
|
/** A `lunora/` source uses `@lunora/bindings/pipelines` / `ctx.pipelines` — wires `ctx.pipelines` onto ActionCtx only. */
|
|
980
2950
|
hasPipelines?: boolean;
|
|
981
2951
|
/** A `lunora/` source uses `@lunora/bindings/r2sql` / `ctx.r2sql` — wires `ctx.r2sql` onto ActionCtx only. */
|
|
982
2952
|
hasR2sql?: boolean;
|
|
2953
|
+
/** A `lunora/` source uses `@lunora/x402/pay` / `ctx.x402` — wires the agent-wallet pay rail onto ActionCtx only. */
|
|
2954
|
+
hasX402?: boolean;
|
|
2955
|
+
/**
|
|
2956
|
+
* The single `defineIdentity(...)` claim contract declared in
|
|
2957
|
+
* `lunora/identity.ts` (Plan 080). When present, `ctx.auth.getIdentity()`,
|
|
2958
|
+
* the RLS policy `ctx.auth.identity`, and the shard-authorization hooks
|
|
2959
|
+
* narrow to the declared shape (recovered via `InferIdentity` over the
|
|
2960
|
+
* contract's `typeof`). `undefined` keeps the identity an untyped bag —
|
|
2961
|
+
* byte-identical to today.
|
|
2962
|
+
*/
|
|
2963
|
+
identity?: IdentityIR;
|
|
983
2964
|
/** Queues declared via `defineQueue` exports — wires the typed `ctx.queues` producers onto Mutation/Action contexts. */
|
|
984
2965
|
queues?: ReadonlyArray<QueueIR>;
|
|
985
2966
|
schema?: SchemaIR;
|
|
@@ -988,169 +2969,179 @@ interface EmitServerOptions {
|
|
|
988
2969
|
useUmbrella?: boolean;
|
|
989
2970
|
workflows?: ReadonlyArray<WorkflowIR>;
|
|
990
2971
|
}
|
|
991
|
-
declare const emitServer: ({
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
* every field declared `v.storage(...)` (unwrapping `v.optional(...)`). The
|
|
1012
|
-
* generated shard hands this to the base `storageColumns` hook so the admin
|
|
1013
|
-
* `storageReferences` read can join R2 objects back to the rows that own them
|
|
1014
|
-
* (and flag orphans — objects no row references). Only scalar storage columns
|
|
1015
|
-
* are emitted; an array-of-storage field can't be matched by an equality scan,
|
|
1016
|
-
* so it is skipped here.
|
|
1017
|
-
*/
|
|
1018
|
-
/**
|
|
1019
|
-
* Emit `_generated/containers.ts` — one container-enabled Durable Object class
|
|
1020
|
-
* per `defineContainer` export, each a thin subclass of `LunoraContainer`
|
|
1021
|
-
* (`@lunora/container/do`) constructed with the user's definition object. The
|
|
1022
|
-
* worker entry must re-export these classes: wrangler requires every
|
|
1023
|
-
* `containers[].class_name` to be exported by the deployed worker. Returns ""
|
|
1024
|
-
* when the project declares no containers (the file is not written then).
|
|
1025
|
-
*/
|
|
2972
|
+
declare const emitServer: ({ agents, containers, env, hasAccessFacade, hasAi, hasAnalytics, hasBrowser, hasFlags, hasHyperdrive, hasImages, hasKv, hasNotify, hasPayments, hasPipelines, hasR2sql, hasX402, identity, queues, schema, storageRuleBuckets, useUmbrella, workflows }?: EmitServerOptions) => string;
|
|
2973
|
+
interface EmitFunctionsOptions {
|
|
2974
|
+
agents?: ReadonlyArray<AgentIR>;
|
|
2975
|
+
functions: ReadonlyArray<FunctionIR>;
|
|
2976
|
+
migrations?: ReadonlyArray<MigrationIR>;
|
|
2977
|
+
mutators?: ReadonlyArray<MutatorIR>;
|
|
2978
|
+
shapes?: ReadonlyArray<ShapeIR>;
|
|
2979
|
+
/** Import of a sandbox tool (`browserTool`/`containerTool`) auto-registers the `sandbox:invoke` action. */
|
|
2980
|
+
usesSandbox?: boolean;
|
|
2981
|
+
useUmbrella?: boolean;
|
|
2982
|
+
}
|
|
2983
|
+
declare const emitFunctions: (options: EmitFunctionsOptions) => string;
|
|
2984
|
+
/**
|
|
2985
|
+
* Emit `_generated/containers.ts` — one container-enabled Durable Object class
|
|
2986
|
+
* per `defineContainer` export, each a thin subclass of `LunoraContainer`
|
|
2987
|
+
* (`@lunora/container/do`) constructed with the user's definition object. The
|
|
2988
|
+
* worker entry must re-export these classes: wrangler requires every
|
|
2989
|
+
* `containers[].class_name` to be exported by the deployed worker. Returns ""
|
|
2990
|
+
* when the project declares no containers (the file is not written then).
|
|
2991
|
+
*/
|
|
1026
2992
|
declare const emitContainers: (containers: ReadonlyArray<ContainerIR>, jurisdiction?: JurisdictionIR) => string;
|
|
1027
2993
|
/**
|
|
1028
|
-
* Emit `_generated/workflows.ts` — one `WorkflowEntrypoint` class per
|
|
1029
|
-
* `defineWorkflow` export, each a thin subclass of `LunoraWorkflow`
|
|
1030
|
-
* (`@lunora/workflow/do`) constructed with the user's definition object. The
|
|
1031
|
-
* worker entry must re-export these classes: wrangler requires every
|
|
1032
|
-
* `workflows[].class_name` to be exported by the deployed worker. Returns ""
|
|
1033
|
-
* when the project declares no workflows (the file is not written then).
|
|
1034
|
-
*/
|
|
2994
|
+
* Emit `_generated/workflows.ts` — one `WorkflowEntrypoint` class per
|
|
2995
|
+
* `defineWorkflow` export, each a thin subclass of `LunoraWorkflow`
|
|
2996
|
+
* (`@lunora/workflow/do`) constructed with the user's definition object. The
|
|
2997
|
+
* worker entry must re-export these classes: wrangler requires every
|
|
2998
|
+
* `workflows[].class_name` to be exported by the deployed worker. Returns ""
|
|
2999
|
+
* when the project declares no workflows (the file is not written then).
|
|
3000
|
+
*/
|
|
1035
3001
|
declare const emitWorkflows: (workflows: ReadonlyArray<WorkflowIR>) => string;
|
|
1036
3002
|
/**
|
|
1037
|
-
* Emit `_generated/
|
|
1038
|
-
*
|
|
1039
|
-
*
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1043
|
-
|
|
3003
|
+
* Emit `_generated/agents.ts` — one `WorkflowEntrypoint` class per `defineAgent`
|
|
3004
|
+
* export, each a thin subclass of `LunoraWorkflow` (`@lunora/workflow/do`)
|
|
3005
|
+
* constructed with the compiled agent tool-loop (`compileAgentWorkflow`). Like
|
|
3006
|
+
* `_generated/workflows.ts`, the worker entry must re-export these classes:
|
|
3007
|
+
* wrangler requires every `workflows[].class_name` to be exported by the
|
|
3008
|
+
* deployed worker. Returns "" when the project declares no agents (the file is
|
|
3009
|
+
* not written then).
|
|
3010
|
+
*/
|
|
3011
|
+
declare const emitAgents: (agents: ReadonlyArray<AgentIR>) => string;
|
|
1044
3012
|
interface EmitShardOptions {
|
|
1045
3013
|
advisories?: ReadonlyArray<Finding>;
|
|
3014
|
+
/** Every declared procedure — the health map's denominator, served via `getAdvisorProcedures`. */
|
|
3015
|
+
advisorProcedures?: ReadonlyArray<AdvisorProcedureProtection>;
|
|
3016
|
+
/** Agents declared via `defineAgent` exports in `lunora/agents.ts` — wires the typed `ctx.agents` producers. */
|
|
3017
|
+
agents?: ReadonlyArray<AgentIR>;
|
|
1046
3018
|
containers?: ReadonlyArray<ContainerIR>;
|
|
3019
|
+
/** The single `defineEnv(...)` contract declared in `lunora/env.ts` — applies the accessor to the worker `env` to populate `ctx.env`. */
|
|
3020
|
+
env?: EnvIR;
|
|
3021
|
+
/** Statically-discovered `ctx.flags.<type>("key")` reads — the studio Flags page + reactive evaluation iterate these. */
|
|
3022
|
+
flagKeys?: ReadonlyArray<{
|
|
3023
|
+
key: string;
|
|
3024
|
+
type: "boolean" | "number" | "object" | "string";
|
|
3025
|
+
}>;
|
|
3026
|
+
/** A `lunora/` source reads `ctx.access` — wires the verified Cloudflare Access facade onto every ctx. */
|
|
3027
|
+
hasAccessFacade?: boolean;
|
|
1047
3028
|
hasAi?: boolean;
|
|
1048
3029
|
/** A `lunora/` source reads `ctx.analytics` — wires the Analytics Engine write helper onto every ctx. */
|
|
1049
3030
|
hasAnalytics?: boolean;
|
|
1050
3031
|
/** A `lunora/` source reads `ctx.browser` — wires `ctx.browser` onto the ActionCtx only. */
|
|
1051
3032
|
hasBrowser?: boolean;
|
|
3033
|
+
/** The project declares `lunora/flags.ts` — wires `ctx.flags` (OpenFeature) onto every ctx. */
|
|
3034
|
+
hasFlags?: boolean;
|
|
1052
3035
|
/** A `lunora/` source reads `ctx.sql` (Hyperdrive) — wires `ctx.sql` onto the ActionCtx only. */
|
|
1053
3036
|
hasHyperdrive?: boolean;
|
|
1054
3037
|
/** A `lunora/` source reads `ctx.images` — wires `ctx.images` onto the ActionCtx only. */
|
|
1055
3038
|
hasImages?: boolean;
|
|
1056
3039
|
/** A `lunora/` source reads `ctx.kv` — wires `ctx.kv` onto every ctx. */
|
|
1057
3040
|
hasKv?: boolean;
|
|
3041
|
+
/** The project declares `lunora/notify.ts` — wires `ctx.notify` + its `ctx.push` alias (`@lunora/notify`) onto every ctx. */
|
|
3042
|
+
hasNotify?: boolean;
|
|
1058
3043
|
hasPayments?: boolean;
|
|
1059
3044
|
/** A `lunora/` source reads `ctx.pipelines` — wires `ctx.pipelines` onto the ActionCtx only. */
|
|
1060
3045
|
hasPipelines?: boolean;
|
|
1061
3046
|
/** A `lunora/` source reads `ctx.r2sql` (R2 SQL) — wires `ctx.r2sql` onto the ActionCtx only. */
|
|
1062
3047
|
hasR2sql?: boolean;
|
|
3048
|
+
/** A `lunora/` source reads `ctx.x402` — wires the agent-wallet pay rail onto the ActionCtx only. */
|
|
3049
|
+
hasX402?: boolean;
|
|
1063
3050
|
maskMetadata?: MaskMetadataIR;
|
|
3051
|
+
/** Custom mutators declared via `defineMutator` in `lunora/mutators.ts` — wires the `isCustomMutator` push-protocol override. */
|
|
3052
|
+
mutators?: ReadonlyArray<MutatorIR>;
|
|
1064
3053
|
/** Queues declared via `defineQueue` exports in `lunora/queues.ts` — wires the typed `ctx.queues` producers. */
|
|
1065
3054
|
queues?: ReadonlyArray<QueueIR>;
|
|
1066
3055
|
rlsMetadata?: RlsMetadataIR;
|
|
1067
3056
|
schema: SchemaIR;
|
|
3057
|
+
/**
|
|
3058
|
+
* The structural snapshot the pre-deploy drift gate diffs against, threaded
|
|
3059
|
+
* in so the emitted DO records it in `__lunora_schema_history` on cold start
|
|
3060
|
+
* (plan 200 — the Studio's schema-version timeline). Optional so an emitter
|
|
3061
|
+
* caller that has no snapshot (tests, fixtures) emits the pre-ledger shape
|
|
3062
|
+
* unchanged.
|
|
3063
|
+
*/
|
|
3064
|
+
schemaSnapshot?: SchemaSnapshot;
|
|
3065
|
+
/** Replication shapes declared via `defineShape` in `lunora/shapes.ts` — wires the `resolveShape` subscription override. */
|
|
3066
|
+
shapes?: ReadonlyArray<ShapeIR>;
|
|
1068
3067
|
storageRules?: StorageRulesMetadataIR;
|
|
1069
3068
|
studioFeatures?: StudioFeaturesResult;
|
|
1070
3069
|
/** The project depends on the `lunora` umbrella — import base packages via its subpaths. */
|
|
1071
3070
|
useUmbrella?: boolean;
|
|
1072
3071
|
workflows?: ReadonlyArray<WorkflowIR>;
|
|
1073
3072
|
}
|
|
1074
|
-
declare const emitShard: ({
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
hasR2sql,
|
|
1086
|
-
maskMetadata,
|
|
1087
|
-
queues,
|
|
1088
|
-
rlsMetadata,
|
|
1089
|
-
schema,
|
|
1090
|
-
storageRules,
|
|
1091
|
-
studioFeatures,
|
|
1092
|
-
useUmbrella,
|
|
1093
|
-
workflows
|
|
1094
|
-
}: EmitShardOptions) => string;
|
|
1095
|
-
/**
|
|
1096
|
-
* Emit drizzle `sqliteTable` definitions for the project schema, split into
|
|
1097
|
-
* `global` (D1-backed) and `shard` (DO-SQLite-backed) buckets. Tables marked
|
|
1098
|
-
* `.global()` go in the global file; everything else (default root + `.shardBy()`)
|
|
1099
|
-
* goes in the shard file.
|
|
1100
|
-
*
|
|
1101
|
-
* `searchIndexes` are intentionally not emitted — drizzle has no `sqliteTable`
|
|
1102
|
-
* abstraction for FTS5 virtual tables. FTS plumbing is handled by the runtime
|
|
1103
|
-
* outside of drizzle.
|
|
1104
|
-
*/
|
|
3073
|
+
declare const emitShard: ({ advisories, advisorProcedures, agents, containers, env, flagKeys, hasAccessFacade, hasAi, hasAnalytics, hasBrowser, hasFlags, hasHyperdrive, hasImages, hasKv, hasNotify, hasPayments, hasPipelines, hasR2sql, hasX402, maskMetadata, mutators, queues, rlsMetadata, schema, schemaSnapshot, shapes, storageRules, studioFeatures, useUmbrella, workflows }: EmitShardOptions) => string;
|
|
3074
|
+
/**
|
|
3075
|
+
* Emit drizzle `sqliteTable` definitions for the project schema, split into
|
|
3076
|
+
* `global` (D1-backed) and `shard` (DO-SQLite-backed) buckets. Tables marked
|
|
3077
|
+
* `.global()` go in the global file; everything else (default root + `.shardBy()`)
|
|
3078
|
+
* goes in the shard file.
|
|
3079
|
+
*
|
|
3080
|
+
* `searchIndexes` are intentionally not emitted — drizzle has no `sqliteTable`
|
|
3081
|
+
* abstraction for FTS5 virtual tables. FTS plumbing is handled by the runtime
|
|
3082
|
+
* outside of drizzle.
|
|
3083
|
+
*/
|
|
1105
3084
|
declare const emitDrizzleSchema: (schema: SchemaIR, useUmbrella?: boolean) => {
|
|
1106
3085
|
global: string;
|
|
1107
3086
|
shard: string;
|
|
1108
3087
|
};
|
|
1109
3088
|
/**
|
|
1110
|
-
* Emit `_generated/crons.ts` from the discovered cron jobs.
|
|
1111
|
-
*
|
|
1112
|
-
* `LUNORA_CRON_TRIGGERS` is the deduplicated schedule array — what lands in
|
|
1113
|
-
* wrangler's `triggers.crons` (the vite plugin reconciles it into
|
|
1114
|
-
* `wrangler.jsonc`, and {@link emitWranglerCronTriggers} renders the same list
|
|
1115
|
-
* for the CLI / docs).
|
|
1116
|
-
*
|
|
1117
|
-
* `LUNORA_CRONS` is the dispatcher map keyed by cron expression, each value a
|
|
1118
|
-
* list of `{ name, functionPath, args }`. Cloudflare's `scheduled()` handler
|
|
1119
|
-
* receives only the cron string, so multiple jobs sharing one expression must
|
|
1120
|
-
* all fire — hence a list per key rather than a single entry.
|
|
1121
|
-
*
|
|
1122
|
-
* Jobs arrive pre-sorted by name (deterministic output); the trigger array
|
|
1123
|
-
* preserves first-seen order of distinct expressions.
|
|
1124
|
-
*
|
|
1125
|
-
* Cloudflare caps a Worker at **3 Cron Triggers** (i.e. 3 distinct cron
|
|
1126
|
-
* expressions). Because the dispatcher fires every job sharing an expression,
|
|
1127
|
-
* many jobs can ride a single trigger — only the count of *distinct* schedules
|
|
1128
|
-
* matters. `lunora codegen` warns when that count exceeds the limit; for
|
|
1129
|
-
* finer-grained scheduling use Durable Object alarms (`@lunora/scheduler`),
|
|
1130
|
-
* which have no such cap.
|
|
1131
|
-
*/
|
|
3089
|
+
* Emit `_generated/crons.ts` from the discovered cron jobs.
|
|
3090
|
+
*
|
|
3091
|
+
* `LUNORA_CRON_TRIGGERS` is the deduplicated schedule array — what lands in
|
|
3092
|
+
* wrangler's `triggers.crons` (the vite plugin reconciles it into
|
|
3093
|
+
* `wrangler.jsonc`, and {@link emitWranglerCronTriggers} renders the same list
|
|
3094
|
+
* for the CLI / docs).
|
|
3095
|
+
*
|
|
3096
|
+
* `LUNORA_CRONS` is the dispatcher map keyed by cron expression, each value a
|
|
3097
|
+
* list of `{ name, functionPath, args }`. Cloudflare's `scheduled()` handler
|
|
3098
|
+
* receives only the cron string, so multiple jobs sharing one expression must
|
|
3099
|
+
* all fire — hence a list per key rather than a single entry.
|
|
3100
|
+
*
|
|
3101
|
+
* Jobs arrive pre-sorted by name (deterministic output); the trigger array
|
|
3102
|
+
* preserves first-seen order of distinct expressions.
|
|
3103
|
+
*
|
|
3104
|
+
* Cloudflare caps a Worker at **3 Cron Triggers** (i.e. 3 distinct cron
|
|
3105
|
+
* expressions). Because the dispatcher fires every job sharing an expression,
|
|
3106
|
+
* many jobs can ride a single trigger — only the count of *distinct* schedules
|
|
3107
|
+
* matters. `lunora codegen` warns when that count exceeds the limit; for
|
|
3108
|
+
* finer-grained scheduling use Durable Object alarms (`@lunora/scheduler`),
|
|
3109
|
+
* which have no such cap.
|
|
3110
|
+
*/
|
|
1132
3111
|
declare const emitCrons: (crons: ReadonlyArray<CronJobIR>) => string;
|
|
1133
3112
|
/**
|
|
1134
|
-
* Render `_generated/vectors.ts` — the static registry of every vector index
|
|
1135
|
-
* declared in `schema.ts` (inline `.vectorize()` columns + standalone
|
|
1136
|
-
* `defineVectorIndex()` definitions).
|
|
1137
|
-
*
|
|
1138
|
-
* Cloudflare Vectorize exposes no way to enumerate an account's indexes at
|
|
1139
|
-
* runtime — a binding can `describe()` itself but the worker can't ask "which
|
|
1140
|
-
* indexes exist". So this generated array is the source of truth the studio's
|
|
1141
|
-
* vector browser lists, and the worker's admin route pairs each entry with its
|
|
1142
|
-
* live `describe()` stats. Entries are sorted by name for deterministic output.
|
|
1143
|
-
*/
|
|
3113
|
+
* Render `_generated/vectors.ts` — the static registry of every vector index
|
|
3114
|
+
* declared in `schema.ts` (inline `.vectorize()` columns + standalone
|
|
3115
|
+
* `defineVectorIndex()` definitions).
|
|
3116
|
+
*
|
|
3117
|
+
* Cloudflare Vectorize exposes no way to enumerate an account's indexes at
|
|
3118
|
+
* runtime — a binding can `describe()` itself but the worker can't ask "which
|
|
3119
|
+
* indexes exist". So this generated array is the source of truth the studio's
|
|
3120
|
+
* vector browser lists, and the worker's admin route pairs each entry with its
|
|
3121
|
+
* live `describe()` stats. Entries are sorted by name for deterministic output.
|
|
3122
|
+
*/
|
|
1144
3123
|
declare const emitVectors: (vectorIndexes: ReadonlyArray<VectorIndexIR>) => string;
|
|
1145
3124
|
/**
|
|
1146
|
-
* Render the deduplicated cron schedules as a JSON fragment suitable for
|
|
1147
|
-
* splicing into `wrangler.jsonc`'s `triggers.crons`. The vite plugin uses this
|
|
1148
|
-
* (plus the parsed wrangler config) to reconcile generated triggers without the
|
|
1149
|
-
* user hand-editing the file.
|
|
1150
|
-
*/
|
|
3125
|
+
* Render the deduplicated cron schedules as a JSON fragment suitable for
|
|
3126
|
+
* splicing into `wrangler.jsonc`'s `triggers.crons`. The vite plugin uses this
|
|
3127
|
+
* (plus the parsed wrangler config) to reconcile generated triggers without the
|
|
3128
|
+
* user hand-editing the file.
|
|
3129
|
+
*/
|
|
1151
3130
|
declare const emitWranglerCronTriggers: (crons: ReadonlyArray<CronJobIR>) => string[];
|
|
1152
3131
|
/** Which capability methods the generated `defineApp` builder exposes — one flag per package-backed feature the app actually uses. */
|
|
1153
3132
|
interface EmitAppOptions {
|
|
3133
|
+
/**
|
|
3134
|
+
* Inbound-email agents (`defineAgent({ onEmail })`) → wire the worker's
|
|
3135
|
+
* top-level `email()` handler to `dispatchAgentEmail(...)` (from
|
|
3136
|
+
* `@lunora/agent/inbound`), so received mail starts a durable run. Empty/absent
|
|
3137
|
+
* ⇒ no wiring, byte-identical output for email-free (and agent-free) projects.
|
|
3138
|
+
*/
|
|
3139
|
+
emailAgents?: ReadonlyArray<{
|
|
3140
|
+
bindingName: string;
|
|
3141
|
+
exportName: string;
|
|
3142
|
+
}>;
|
|
3143
|
+
/** App depends on `@lunora/cloudflare-access` → emit `.access()` (wire the Cloudflare Access `resolveIdentity`, composed ahead of `@lunora/auth` when both are present). */
|
|
3144
|
+
hasAccess: boolean;
|
|
1154
3145
|
/** App uses `@lunora/ai` / `ctx.ai` → emit `.ai()` (override the Workers AI binding backing `ctx.ai`). */
|
|
1155
3146
|
hasAi: boolean;
|
|
1156
3147
|
/** App uses `@lunora/bindings/analytics` / `ctx.analytics` → emit `.analytics()` (override the dataset backing `ctx.analytics`). */
|
|
@@ -1171,6 +3162,8 @@ interface EmitAppOptions {
|
|
|
1171
3162
|
hasImages: boolean;
|
|
1172
3163
|
/** App uses `@lunora/bindings/kv` / `ctx.kv` → emit `.kv()`. */
|
|
1173
3164
|
hasKv: boolean;
|
|
3165
|
+
/** App declares `lunora/notify.ts` (`@lunora/notify`) → wire `options.notifySubscriptionStore` so the studio Notifications page can read registered devices. */
|
|
3166
|
+
hasNotify: boolean;
|
|
1174
3167
|
/** App uses `@lunora/payment` / `ctx.payments` → emit `.payment()`. */
|
|
1175
3168
|
hasPayments: boolean;
|
|
1176
3169
|
/** App declares push queues (`defineQueue`) → wire `LUNORA_QUEUE_REGISTRY` into the worker's `queue()` consumer entry. */
|
|
@@ -1185,30 +3178,45 @@ interface EmitAppOptions {
|
|
|
1185
3178
|
hasVectors: boolean;
|
|
1186
3179
|
/** App declares Cloudflare Workflows (`defineWorkflow`) → wire `options.workflowsClient` so the studio's workflow-instance proxy can reach the CF REST API. */
|
|
1187
3180
|
hasWorkflow: boolean;
|
|
3181
|
+
/** App uses `@lunora/x402/pay` / `ctx.x402` → emit `.x402()` (wire the agent-wallet pay rail). */
|
|
3182
|
+
hasX402: boolean;
|
|
3183
|
+
/** The single `defineIdentity(...)` contract in `lunora/identity.ts` (Plan 080) → import it as a VALUE and wire `options.identity`, so the runtime trust boundary validates every resolved identity before it becomes `ctx.auth`. `undefined` ⇒ no wiring, byte-identical output. */
|
|
3184
|
+
identity?: IdentityIR;
|
|
1188
3185
|
/** Schema declares `.jurisdiction("…")` → pin every DO the worker reaches (shards, fan-out, scheduler, containers) to the Cloudflare data-residency jurisdiction. */
|
|
1189
3186
|
jurisdiction?: JurisdictionIR;
|
|
1190
3187
|
/** Project depends on the unscoped `lunorash` umbrella → import the runtime via `lunorash/runtime` instead of `@lunora/runtime`. */
|
|
1191
3188
|
useUmbrella: boolean;
|
|
3189
|
+
/**
|
|
3190
|
+
* Voice-enabled agents (`defineAgent({ voice: … })`) → wire
|
|
3191
|
+
* `options.voiceAgents`, mapping each agent's export name to its `VOICE_*`
|
|
3192
|
+
* Durable Object namespace binding so the runtime exposes
|
|
3193
|
+
* `/_lunora/voice/<exportName>`. Empty/absent ⇒ no wiring, byte-identical
|
|
3194
|
+
* output for voice-free (and agent-free) projects.
|
|
3195
|
+
*/
|
|
3196
|
+
voiceAgents?: ReadonlyArray<{
|
|
3197
|
+
bindingName: string;
|
|
3198
|
+
exportName: string;
|
|
3199
|
+
}>;
|
|
1192
3200
|
/** An OpenAPI spec is emitted (`openapi.ts`) → wire `openApiSpec` into the worker. */
|
|
1193
3201
|
wantsOpenApi: boolean;
|
|
1194
3202
|
/** An OpenRPC spec is emitted (`openrpc.ts`) → wire `openRpcSpec` into the worker. */
|
|
1195
3203
|
wantsOpenRpc: boolean;
|
|
1196
3204
|
}
|
|
1197
3205
|
/**
|
|
1198
|
-
* Emit `_generated/app.ts` — a fluent, feature-specialized worker-composition
|
|
1199
|
-
* builder. Only the methods for capabilities THIS app uses are emitted, so the
|
|
1200
|
-
* builder's type surface (IntelliSense) lists exactly what can be configured.
|
|
1201
|
-
*
|
|
1202
|
-
* Each capability declaration is fanned into BOTH runtime surfaces: the DO-side
|
|
1203
|
-
* `createShardDO(...)` factory that backs `ctx.*`, and the worker-side
|
|
1204
|
-
* `createWorker(...)` options that back the studio/admin endpoints — so storage
|
|
1205
|
-
* / scheduler / global are declared once instead of twice. The builder is pure
|
|
1206
|
-
* sugar over the public `createWorker` / `createShardDO`; both stay usable.
|
|
1207
|
-
*
|
|
1208
|
-
* Lives in generated code (not `@lunora/runtime`, which is dependency-free) so
|
|
1209
|
-
* it can import the add-on packages the app installed (`@lunora/auth`,
|
|
1210
|
-
* `@lunora/storage`, …) directly.
|
|
1211
|
-
*/
|
|
3206
|
+
* Emit `_generated/app.ts` — a fluent, feature-specialized worker-composition
|
|
3207
|
+
* builder. Only the methods for capabilities THIS app uses are emitted, so the
|
|
3208
|
+
* builder's type surface (IntelliSense) lists exactly what can be configured.
|
|
3209
|
+
*
|
|
3210
|
+
* Each capability declaration is fanned into BOTH runtime surfaces: the DO-side
|
|
3211
|
+
* `createShardDO(...)` factory that backs `ctx.*`, and the worker-side
|
|
3212
|
+
* `createWorker(...)` options that back the studio/admin endpoints — so storage
|
|
3213
|
+
* / scheduler / global are declared once instead of twice. The builder is pure
|
|
3214
|
+
* sugar over the public `createWorker` / `createShardDO`; both stay usable.
|
|
3215
|
+
*
|
|
3216
|
+
* Lives in generated code (not `@lunora/runtime`, which is dependency-free) so
|
|
3217
|
+
* it can import the add-on packages the app installed (`@lunora/auth`,
|
|
3218
|
+
* `@lunora/storage`, …) directly.
|
|
3219
|
+
*/
|
|
1212
3220
|
declare const emitApp: (options: EmitAppOptions) => string;
|
|
1213
3221
|
/** Inputs the OpenAPI emitter needs from a codegen run. */
|
|
1214
3222
|
interface OpenApiEmitInput {
|
|
@@ -1218,40 +3226,40 @@ interface OpenApiEmitInput {
|
|
|
1218
3226
|
version?: string;
|
|
1219
3227
|
}
|
|
1220
3228
|
/**
|
|
1221
|
-
* Emit an OpenAPI 3.1.0 document covering both Lunora function surfaces.
|
|
1222
|
-
*
|
|
1223
|
-
* `httpRouter()` typed REST routes become real `paths` keyed by their method +
|
|
1224
|
-
* URL, with query/path parameters and JSON request bodies derived from their
|
|
1225
|
-
* `v.*` validators, and a response schema from `.output()` when declared.
|
|
1226
|
-
*
|
|
1227
|
-
* RPC `query`/`mutation`/`action` functions become one operation each on
|
|
1228
|
-
* `POST /_lunora/rpc` (disambiguated by a `#functionPath` path fragment), with a
|
|
1229
|
-
* requestBody pinning `functionPath` + typed `args`. `internal`/`stream`
|
|
1230
|
-
* functions are excluded (unreachable / not invocable on the external RPC path).
|
|
1231
|
-
*
|
|
1232
|
-
* Operations are grouped into `tags` by file namespace, and every operation
|
|
1233
|
-
* references a reusable `LunoraError` error-response component enumerating the
|
|
1234
|
-
* standard error codes. Borrows oRPC's per-procedure-operation + tag-grouping +
|
|
1235
|
-
* internal-filtering structure; the JSON Schema dialect matches `@lunora/values`
|
|
1236
|
-
* (Draft 2020-12). Returns the document as a plain object (the single source of
|
|
1237
|
-
* truth `emitOpenApi` stringifies and `emitOpenApiModule` inlines, so the
|
|
1238
|
-
* `.json` and `.ts` artifacts can never drift).
|
|
1239
|
-
*/
|
|
3229
|
+
* Emit an OpenAPI 3.1.0 document covering both Lunora function surfaces.
|
|
3230
|
+
*
|
|
3231
|
+
* `httpRouter()` typed REST routes become real `paths` keyed by their method +
|
|
3232
|
+
* URL, with query/path parameters and JSON request bodies derived from their
|
|
3233
|
+
* `v.*` validators, and a response schema from `.output()` when declared.
|
|
3234
|
+
*
|
|
3235
|
+
* RPC `query`/`mutation`/`action` functions become one operation each on
|
|
3236
|
+
* `POST /_lunora/rpc` (disambiguated by a `#functionPath` path fragment), with a
|
|
3237
|
+
* requestBody pinning `functionPath` + typed `args`. `internal`/`stream`
|
|
3238
|
+
* functions are excluded (unreachable / not invocable on the external RPC path).
|
|
3239
|
+
*
|
|
3240
|
+
* Operations are grouped into `tags` by file namespace, and every operation
|
|
3241
|
+
* references a reusable `LunoraError` error-response component enumerating the
|
|
3242
|
+
* standard error codes. Borrows oRPC's per-procedure-operation + tag-grouping +
|
|
3243
|
+
* internal-filtering structure; the JSON Schema dialect matches `@lunora/values`
|
|
3244
|
+
* (Draft 2020-12). Returns the document as a plain object (the single source of
|
|
3245
|
+
* truth `emitOpenApi` stringifies and `emitOpenApiModule` inlines, so the
|
|
3246
|
+
* `.json` and `.ts` artifacts can never drift).
|
|
3247
|
+
*/
|
|
1240
3248
|
declare const buildOpenApiDocument: (input: OpenApiEmitInput) => Record<string, unknown>;
|
|
1241
3249
|
/**
|
|
1242
|
-
* Emit the OpenAPI 3.1 document as a pretty-printed JSON string
|
|
1243
|
-
* (`_generated/openapi.json`) — the portable artifact for external tooling.
|
|
1244
|
-
*/
|
|
3250
|
+
* Emit the OpenAPI 3.1 document as a pretty-printed JSON string
|
|
3251
|
+
* (`_generated/openapi.json`) — the portable artifact for external tooling.
|
|
3252
|
+
*/
|
|
1245
3253
|
declare const emitOpenApi: (input: OpenApiEmitInput) => string;
|
|
1246
3254
|
/**
|
|
1247
|
-
* Emit the OpenAPI document as an importable TS module
|
|
1248
|
-
* (`_generated/openapi.ts`) the worker entry imports and passes to
|
|
1249
|
-
* `createWorker({ openApiSpec })`. The document object literal is inlined
|
|
1250
|
-
* verbatim (same `JSON.stringify` form the `.json` uses), so the `.ts` and
|
|
1251
|
-
* `.json` are byte-identical content and regenerate together — closing the gap
|
|
1252
|
-
* where a Worker cannot read the JSON file at runtime. `document_` is the object
|
|
1253
|
-
* returned by {@link buildOpenApiDocument} (reused, never recomputed).
|
|
1254
|
-
*/
|
|
3255
|
+
* Emit the OpenAPI document as an importable TS module
|
|
3256
|
+
* (`_generated/openapi.ts`) the worker entry imports and passes to
|
|
3257
|
+
* `createWorker({ openApiSpec })`. The document object literal is inlined
|
|
3258
|
+
* verbatim (same `JSON.stringify` form the `.json` uses), so the `.ts` and
|
|
3259
|
+
* `.json` are byte-identical content and regenerate together — closing the gap
|
|
3260
|
+
* where a Worker cannot read the JSON file at runtime. `document_` is the object
|
|
3261
|
+
* returned by {@link buildOpenApiDocument} (reused, never recomputed).
|
|
3262
|
+
*/
|
|
1255
3263
|
declare const emitOpenApiModule: (document_: Record<string, unknown>) => string;
|
|
1256
3264
|
/** The OpenRPC dialect version this emitter targets. */
|
|
1257
3265
|
declare const OPENRPC_VERSION = "1.3.2";
|
|
@@ -1262,140 +3270,71 @@ interface OpenRpcEmitInput {
|
|
|
1262
3270
|
version?: string;
|
|
1263
3271
|
}
|
|
1264
3272
|
/**
|
|
1265
|
-
* Emit an OpenRPC 1.x document describing Lunora's JSON-RPC surface.
|
|
1266
|
-
*
|
|
1267
|
-
* Only the RPC `query`/`mutation`/`action` functions become `methods` — one per
|
|
1268
|
-
* function, `name` = `file:fn`. `internal` (off the external RPC path) and
|
|
1269
|
-
* `stream` (not invocable over the RPC envelope) are excluded, the same filter
|
|
1270
|
-
* the OpenAPI emitter applies. Each method's single `args` param is typed from
|
|
1271
|
-
* the function's `v.*` validators (`argsObjectSchema`); `result` is the
|
|
1272
|
-
* `.output()` schema when declared, else a best-effort inferred schema. The
|
|
1273
|
-
* standard `LunoraError` codes ride along under each method's `errors`.
|
|
1274
|
-
*
|
|
1275
|
-
* `httpRouter()` typed REST routes are deliberately omitted — OpenRPC is
|
|
1276
|
-
* RPC-only and cannot represent REST paths; the OpenAPI document is the spec
|
|
1277
|
-
* that covers the REST surface. Methods are sorted by name for stable output.
|
|
1278
|
-
* Returns the document as a plain object (the single source of truth
|
|
1279
|
-
* `emitOpenRpc` stringifies and `emitOpenRpcModule` inlines, so the `.json` and
|
|
1280
|
-
* `.ts` artifacts can never drift).
|
|
1281
|
-
*/
|
|
3273
|
+
* Emit an OpenRPC 1.x document describing Lunora's JSON-RPC surface.
|
|
3274
|
+
*
|
|
3275
|
+
* Only the RPC `query`/`mutation`/`action` functions become `methods` — one per
|
|
3276
|
+
* function, `name` = `file:fn`. `internal` (off the external RPC path) and
|
|
3277
|
+
* `stream` (not invocable over the RPC envelope) are excluded, the same filter
|
|
3278
|
+
* the OpenAPI emitter applies. Each method's single `args` param is typed from
|
|
3279
|
+
* the function's `v.*` validators (`argsObjectSchema`); `result` is the
|
|
3280
|
+
* `.output()` schema when declared, else a best-effort inferred schema. The
|
|
3281
|
+
* standard `LunoraError` codes ride along under each method's `errors`.
|
|
3282
|
+
*
|
|
3283
|
+
* `httpRouter()` typed REST routes are deliberately omitted — OpenRPC is
|
|
3284
|
+
* RPC-only and cannot represent REST paths; the OpenAPI document is the spec
|
|
3285
|
+
* that covers the REST surface. Methods are sorted by name for stable output.
|
|
3286
|
+
* Returns the document as a plain object (the single source of truth
|
|
3287
|
+
* `emitOpenRpc` stringifies and `emitOpenRpcModule` inlines, so the `.json` and
|
|
3288
|
+
* `.ts` artifacts can never drift).
|
|
3289
|
+
*/
|
|
1282
3290
|
declare const buildOpenRpcDocument: (input: OpenRpcEmitInput) => Record<string, unknown>;
|
|
1283
3291
|
/**
|
|
1284
|
-
* Emit the OpenRPC 1.x document as a pretty-printed JSON string
|
|
1285
|
-
* (`_generated/openrpc.json`) — the portable artifact for external tooling.
|
|
1286
|
-
*/
|
|
3292
|
+
* Emit the OpenRPC 1.x document as a pretty-printed JSON string
|
|
3293
|
+
* (`_generated/openrpc.json`) — the portable artifact for external tooling.
|
|
3294
|
+
*/
|
|
1287
3295
|
declare const emitOpenRpc: (input: OpenRpcEmitInput) => string;
|
|
1288
3296
|
/**
|
|
1289
|
-
* Emit the OpenRPC document as an importable TS module
|
|
1290
|
-
* (`_generated/openrpc.ts`) the worker entry imports and passes to
|
|
1291
|
-
* `createWorker({ openRpcSpec })`. The document object literal is inlined
|
|
1292
|
-
* verbatim (same `JSON.stringify` form the `.json` uses), so the `.ts` and
|
|
1293
|
-
* `.json` are byte-identical content and regenerate together. `document_` is
|
|
1294
|
-
* the object returned by {@link buildOpenRpcDocument} (reused, never recomputed).
|
|
1295
|
-
*/
|
|
3297
|
+
* Emit the OpenRPC document as an importable TS module
|
|
3298
|
+
* (`_generated/openrpc.ts`) the worker entry imports and passes to
|
|
3299
|
+
* `createWorker({ openRpcSpec })`. The document object literal is inlined
|
|
3300
|
+
* verbatim (same `JSON.stringify` form the `.json` uses), so the `.ts` and
|
|
3301
|
+
* `.json` are byte-identical content and regenerate together. `document_` is
|
|
3302
|
+
* the object returned by {@link buildOpenRpcDocument} (reused, never recomputed).
|
|
3303
|
+
*/
|
|
1296
3304
|
declare const emitOpenRpcModule: (document_: Record<string, unknown>) => string;
|
|
1297
|
-
/** Current snapshot format version. Bumped if the structural shape below changes. */
|
|
1298
|
-
declare const SCHEMA_SNAPSHOT_VERSION: 1;
|
|
1299
|
-
/** A single field's structural shape: its value kind and whether it is optional. */
|
|
1300
|
-
interface FieldSnapshot {
|
|
1301
|
-
/** The validator kind (`string`, `number`, `id`, `object`, …) after unwrapping `v.optional`. */
|
|
1302
|
-
kind: string;
|
|
1303
|
-
/** True when declared `v.optional(...)` — accepts `undefined` / absent on insert. */
|
|
1304
|
-
optional: boolean;
|
|
1305
|
-
}
|
|
1306
|
-
/** A single secondary index's structural shape. */
|
|
1307
|
-
interface IndexSnapshot {
|
|
1308
|
-
fields: ReadonlyArray<string>;
|
|
1309
|
-
unique: boolean;
|
|
1310
|
-
}
|
|
1311
|
-
/** A single relation's structural shape. */
|
|
1312
|
-
interface RelationSnapshot {
|
|
1313
|
-
field: string;
|
|
1314
|
-
kind: "many" | "one";
|
|
1315
|
-
table: string;
|
|
1316
|
-
}
|
|
1317
|
-
/** Structural snapshot of one table. */
|
|
1318
|
-
interface TableSnapshot {
|
|
1319
|
-
/** Field name → {@link FieldSnapshot}, in declared order. */
|
|
1320
|
-
fields: Record<string, FieldSnapshot>;
|
|
1321
|
-
/** Index name → {@link IndexSnapshot}. */
|
|
1322
|
-
indexes: Record<string, IndexSnapshot>;
|
|
1323
|
-
/** Relation accessor name → {@link RelationSnapshot}. */
|
|
1324
|
-
relations: Record<string, RelationSnapshot>;
|
|
1325
|
-
/**
|
|
1326
|
-
* `"root"` (default single-DO), `"global"` (D1-replicated), or
|
|
1327
|
-
* `"shardBy:<field>"` (partitioned). Encoded as a string so the snapshot
|
|
1328
|
-
* stays a plain JSON-stable value.
|
|
1329
|
-
*/
|
|
1330
|
-
shardMode: string;
|
|
1331
|
-
}
|
|
1332
|
-
/** The committed baseline — a deterministic structural view of the whole schema. */
|
|
1333
|
-
interface SchemaSnapshot {
|
|
1334
|
-
/**
|
|
1335
|
-
* Cloudflare DO data-residency jurisdiction declared via `.jurisdiction("…")`,
|
|
1336
|
-
* or absent. Tracked because changing it strands all existing Durable Object
|
|
1337
|
-
* data (a DO name maps to a different ID per jurisdiction). Optional, so old
|
|
1338
|
-
* baselines written before this field parse cleanly (absent ⇒ undefined).
|
|
1339
|
-
*
|
|
1340
|
-
* Typed as a plain `string` (not the authoring union) on purpose: this is
|
|
1341
|
-
* STORED data that a newer Lunora may have written with a jurisdiction this
|
|
1342
|
-
* version doesn't yet know. Preserving the raw value keeps the breaking
|
|
1343
|
-
* `changedJurisdiction` diff correct under a downgrade — coercing an unknown
|
|
1344
|
-
* value to `undefined` would fail OPEN and hide the most destructive change.
|
|
1345
|
-
*/
|
|
1346
|
-
jurisdiction?: string;
|
|
1347
|
-
/** Sorted list of every declared `defineMigration` id at capture time. */
|
|
1348
|
-
migrationIds: ReadonlyArray<string>;
|
|
1349
|
-
/** Table name → {@link TableSnapshot}, keys sorted for stable serialization. */
|
|
1350
|
-
tables: Record<string, TableSnapshot>;
|
|
1351
|
-
version: typeof SCHEMA_SNAPSHOT_VERSION;
|
|
1352
|
-
}
|
|
1353
|
-
/** One classified structural change between the baseline and the current schema. */
|
|
1354
|
-
interface DriftChange {
|
|
1355
|
-
/** `"breaking"` changes need a data migration; `"safe"` changes are additive. */
|
|
1356
|
-
severity: "breaking" | "safe";
|
|
1357
|
-
/** Human-readable, actionable description (used in the gate message). */
|
|
1358
|
-
summary: string;
|
|
1359
|
-
/** A machine-readable change discriminator. */
|
|
1360
|
-
type: "addedIndex" | "addedOptionalField" | "addedRelation" | "addedRequiredField" | "addedTable" | "changedJurisdiction" | "changedFieldKind" | "changedIndex" | "changedShardMode" | "fieldOptionalToRequired" | "fieldRequiredToOptional" | "removedField" | "removedIndex" | "removedRelation" | "removedTable";
|
|
1361
|
-
}
|
|
1362
|
-
/** The result of diffing two snapshots: every classified change. */
|
|
1363
|
-
interface SchemaDrift {
|
|
1364
|
-
/** Every classified change, in a stable order (added/changed per table, then removals). */
|
|
1365
|
-
changes: ReadonlyArray<DriftChange>;
|
|
1366
|
-
}
|
|
1367
3305
|
/**
|
|
1368
|
-
* Build a {@link SchemaSnapshot} from a parsed {@link SchemaIR} and the set of
|
|
1369
|
-
* declared migration ids. Tables and migration ids are sorted so the emitted
|
|
1370
|
-
* JSON is byte-stable across runs (no spurious diffs /
|
|
1371
|
-
|
|
3306
|
+
* Build a {@link SchemaSnapshot} from a parsed {@link SchemaIR} and the set of
|
|
3307
|
+
* declared migration ids. Tables and migration ids are sorted so the emitted
|
|
3308
|
+
* JSON is byte-stable across runs AND across machines (no spurious diffs /
|
|
3309
|
+
* churn) — see `sortKeys` in `shared/schema-snapshot.ts` for why that ordering
|
|
3310
|
+
* must not be locale-aware.
|
|
3311
|
+
*
|
|
3312
|
+
* Field / index / relation keys are deliberately NOT sorted: they are emitted in
|
|
3313
|
+
* declaration order from the schema source, which is already deterministic for a
|
|
3314
|
+
* given source file and keeps the snapshot readable next to the schema it mirrors.
|
|
3315
|
+
*/
|
|
1372
3316
|
declare const buildSchemaSnapshot: (schema: SchemaIR, migrationIds: ReadonlyArray<string>) => SchemaSnapshot;
|
|
1373
|
-
/** Serialize a snapshot to the exact bytes written to `lunora/.lunora-schema.json` (trailing newline). */
|
|
1374
|
-
declare const serializeSchemaSnapshot: (snapshot: SchemaSnapshot) => string;
|
|
1375
3317
|
/**
|
|
1376
|
-
* Thrown by {@link parseSchemaSnapshot} when the baseline file exists but is
|
|
1377
|
-
* malformed (bad JSON / wrong version / invalid table shape). Lets the CLI gate
|
|
1378
|
-
* treat a corrupt baseline as a hard error rather than silently degrading to a
|
|
1379
|
-
* "first capture" that would mask drift and then overwrite the bad file.
|
|
1380
|
-
*/
|
|
1381
|
-
declare class SchemaSnapshotParseError extends
|
|
1382
|
-
|
|
1383
|
-
}
|
|
1384
|
-
/**
|
|
1385
|
-
* Parse a committed snapshot file. Returns `undefined` ONLY when the content is
|
|
1386
|
-
* absent/empty; throws {@link SchemaSnapshotParseError} when content is present
|
|
1387
|
-
* but malformed (bad JSON, wrong version, or structurally-invalid tables) so the
|
|
1388
|
-
* caller can distinguish "no baseline yet" (a legitimate first capture) from "a
|
|
1389
|
-
* corrupt baseline" (which must not be silently treated as a first capture).
|
|
1390
|
-
*/
|
|
1391
|
-
declare const parseSchemaSnapshot: (content: string | undefined) => SchemaSnapshot | undefined;
|
|
3318
|
+
* Thrown by {@link parseSchemaSnapshot} when the baseline file exists but is
|
|
3319
|
+
* malformed (bad JSON / wrong version / invalid table shape). Lets the CLI gate
|
|
3320
|
+
* treat a corrupt baseline as a hard error rather than silently degrading to a
|
|
3321
|
+
* "first capture" that would mask drift and then overwrite the bad file.
|
|
3322
|
+
*/
|
|
3323
|
+
declare class SchemaSnapshotParseError extends LunoraError {
|
|
3324
|
+
constructor(message: string);
|
|
3325
|
+
}
|
|
1392
3326
|
/**
|
|
1393
|
-
*
|
|
1394
|
-
*
|
|
1395
|
-
*
|
|
1396
|
-
*
|
|
1397
|
-
|
|
1398
|
-
|
|
3327
|
+
* Parse a committed snapshot file. Returns `undefined` ONLY when the content is
|
|
3328
|
+
* absent/empty; throws {@link SchemaSnapshotParseError} when content is present
|
|
3329
|
+
* but malformed (bad JSON, wrong version, or structurally-invalid tables) so the
|
|
3330
|
+
* caller can distinguish "no baseline yet" (a legitimate first capture) from "a
|
|
3331
|
+
* corrupt baseline" (which must not be silently treated as a first capture).
|
|
3332
|
+
*
|
|
3333
|
+
* The parsing itself lives in `shared/schema-snapshot.ts` (the Studio reads the
|
|
3334
|
+
* same JSON out of the DO ledger); this wrapper only applies the CLI's policy of
|
|
3335
|
+
* treating a malformed baseline as fatal.
|
|
3336
|
+
*/
|
|
3337
|
+
declare const parseSchemaSnapshot: (content: string | undefined) => SchemaSnapshot | undefined;
|
|
1399
3338
|
/** The decision the pre-deploy gate returns. */
|
|
1400
3339
|
interface SchemaDriftDecision {
|
|
1401
3340
|
/** True when the deploy must be blocked (breaking drift with no new migration, and no override). */
|
|
@@ -1405,273 +3344,339 @@ interface SchemaDriftDecision {
|
|
|
1405
3344
|
/** Migration ids declared now but absent from the baseline — proof a migration was added. */
|
|
1406
3345
|
newMigrationIds: ReadonlyArray<string>;
|
|
1407
3346
|
/**
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
3347
|
+
* A multi-line, actionable explanation. Always present; empty string when
|
|
3348
|
+
* there is no drift at all. Mirrors the D1-placeholder guard's message style.
|
|
3349
|
+
*/
|
|
1411
3350
|
reason: string;
|
|
1412
3351
|
}
|
|
1413
3352
|
/**
|
|
1414
|
-
* Decide whether breaking schema drift should block a deploy.
|
|
1415
|
-
*
|
|
1416
|
-
* Blocks only when the baseline exists (a first-ever capture is never blocking),
|
|
1417
|
-
* there is at least one `breaking` change, no NEW migration id was added since
|
|
1418
|
-
* the baseline, and the `allowDrift` override is not set. Safe-only drift (or
|
|
1419
|
-
* breaking drift accompanied by a new migration id) passes.
|
|
1420
|
-
*/
|
|
3353
|
+
* Decide whether breaking schema drift should block a deploy.
|
|
3354
|
+
*
|
|
3355
|
+
* Blocks only when the baseline exists (a first-ever capture is never blocking),
|
|
3356
|
+
* there is at least one `breaking` change, no NEW migration id was added since
|
|
3357
|
+
* the baseline, and the `allowDrift` override is not set. Safe-only drift (or
|
|
3358
|
+
* breaking drift accompanied by a new migration id) passes.
|
|
3359
|
+
*/
|
|
1421
3360
|
declare const evaluateSchemaDrift: (options: {
|
|
1422
3361
|
allowDrift?: boolean;
|
|
1423
3362
|
baseline: SchemaSnapshot | undefined;
|
|
3363
|
+
/** The command printing the remediation, so it names only flags that command accepts. */
|
|
3364
|
+
command?: string;
|
|
1424
3365
|
current: SchemaSnapshot;
|
|
1425
3366
|
}) => SchemaDriftDecision;
|
|
1426
3367
|
/**
|
|
1427
|
-
*
|
|
1428
|
-
*
|
|
1429
|
-
*
|
|
1430
|
-
*
|
|
1431
|
-
*/
|
|
1432
|
-
declare const
|
|
3368
|
+
* Convert a {@link SchemaIR} into a synthetic runtime {@link Schema} carrying just
|
|
3369
|
+
* the `tables[name].shape` surface `@lunora/seed` introspects. System columns
|
|
3370
|
+
* (`_id`, `_creationTime`) are absent from the IR shape, exactly as the seed
|
|
3371
|
+
* engine expects (it assigns `_id` itself).
|
|
3372
|
+
*/
|
|
3373
|
+
declare const schemaFromIr: (ir: SchemaIR) => Schema;
|
|
1433
3374
|
/**
|
|
1434
|
-
*
|
|
1435
|
-
*
|
|
1436
|
-
*
|
|
1437
|
-
*
|
|
1438
|
-
*
|
|
1439
|
-
*
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
*/
|
|
1443
|
-
declare const createCodegenProject: (lunoraDirectory: string) => Project;
|
|
3375
|
+
* Convert a codegen {@link ValidatorIR} into a JSON Schema node. A thin wrapper
|
|
3376
|
+
* over the shared {@link jsonSchemaFromNode} core (from `@lunora/values`) with the
|
|
3377
|
+
* IR-backed {@link irReader}, so the kind→schema mapping is the *same* algorithm
|
|
3378
|
+
* `@lunora/values`' `toJsonSchema` runs — codegen never instantiates the runtime
|
|
3379
|
+
* `v.*` objects, it only holds the reflected IR. Shared by the OpenAPI and
|
|
3380
|
+
* OpenRPC emitters so both surfaces speak one JSON Schema dialect.
|
|
3381
|
+
*/
|
|
3382
|
+
declare const validatorIrToJsonSchema: (validator: ValidatorIR) => JsonSchema;
|
|
1444
3383
|
/**
|
|
1445
|
-
*
|
|
1446
|
-
*
|
|
1447
|
-
*
|
|
1448
|
-
*
|
|
1449
|
-
*
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
*
|
|
1453
|
-
* Files outside `lunoraDirectory` (e.g. those pulled in by the user's tsconfig)
|
|
1454
|
-
* are left untouched — they back type resolution and rarely change in the
|
|
1455
|
-
* dev-loop; a tsconfig change invalidates the whole cached Project upstream.
|
|
1456
|
-
*/
|
|
1457
|
-
declare const refreshCodegenProject: (project: Project, lunoraDirectory: string) => void;
|
|
3384
|
+
* The machine-readable `LunoraError` codes Lunora emits on the RPC + REST
|
|
3385
|
+
* surfaces, enumerated from `@lunora/server`'s `CODE_STATUS` map plus the
|
|
3386
|
+
* runtime/DO dispatch codes (`FUNCTION_NOT_FOUND`, `PAYLOAD_TOO_LARGE`,
|
|
3387
|
+
* `METHOD_NOT_ALLOWED`, the `*_NOT_CONFIGURED` admin gates, …). The list documents
|
|
3388
|
+
* the contract; clients switch on `error.code`. Kept sorted for stable output.
|
|
3389
|
+
*/
|
|
3390
|
+
declare const LUNORA_ERROR_CODES: ReadonlyArray<string>;
|
|
1458
3391
|
/**
|
|
1459
|
-
*
|
|
1460
|
-
*
|
|
1461
|
-
*
|
|
1462
|
-
*
|
|
1463
|
-
*
|
|
1464
|
-
*
|
|
1465
|
-
*
|
|
1466
|
-
*
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
3392
|
+
* Language-agnostic half of SDK generation: turn an OpenRPC document
|
|
3393
|
+
* (`_generated/openrpc.json`, see {@link file://../openrpc.ts}) into the parsed
|
|
3394
|
+
* shape every per-language target renders from.
|
|
3395
|
+
*
|
|
3396
|
+
* Nothing here knows about a target language. A target supplies its own member
|
|
3397
|
+
* naming and templates (see {@link file://./targets}); everything that would
|
|
3398
|
+
* otherwise be re-derived per language — how a `functionPath` splits, which
|
|
3399
|
+
* runtime verb a kind maps to, whether a schema is real or the untyped
|
|
3400
|
+
* placeholder, how namespaces group and sort — lives here exactly once.
|
|
3401
|
+
*
|
|
3402
|
+
* That single-source rule is not stylistic. `paths.ts` documents the same
|
|
3403
|
+
* discipline for namespaces ("if these ever disagree, runtime dispatch silently
|
|
3404
|
+
* misses functions"), and the failure mode here is the same in a new costume: a
|
|
3405
|
+
* target that re-derives a model name renders an import pointing at a class
|
|
3406
|
+
* quicktype never emitted.
|
|
3407
|
+
*
|
|
3408
|
+
* Deriving names in one place is necessary but NOT sufficient, because only
|
|
3409
|
+
* half the decision is ours: quicktype chooses whether a predicted name becomes
|
|
3410
|
+
* a declared type, and different backends answer differently for the same
|
|
3411
|
+
* schema. {@link withDeclaredModels} reconciles the two halves before a target
|
|
3412
|
+
* renders anything.
|
|
3413
|
+
*/
|
|
3414
|
+
/** One OpenRPC method as {@link file://../openrpc.ts} emits it. */
|
|
3415
|
+
interface OpenRpcMethod {
|
|
3416
|
+
name: string;
|
|
3417
|
+
params?: ReadonlyArray<{
|
|
3418
|
+
name: string;
|
|
3419
|
+
schema?: Record<string, unknown>;
|
|
3420
|
+
}>;
|
|
3421
|
+
result?: {
|
|
3422
|
+
name: string;
|
|
3423
|
+
schema?: Record<string, unknown>;
|
|
3424
|
+
};
|
|
3425
|
+
summary?: string;
|
|
3426
|
+
"x-lunora-function-kind"?: string;
|
|
3427
|
+
}
|
|
3428
|
+
/** The `_generated/openrpc.json` document. */
|
|
3429
|
+
interface OpenRpcDocument {
|
|
3430
|
+
info?: {
|
|
3431
|
+
title?: string;
|
|
3432
|
+
version?: string;
|
|
3433
|
+
};
|
|
3434
|
+
methods: ReadonlyArray<OpenRpcMethod>;
|
|
3435
|
+
}
|
|
3436
|
+
/** The runtime verbs a generated SDK can call. Mirrors the client transports. */
|
|
3437
|
+
type RuntimeVerb = "action" | "mutation" | "query";
|
|
3438
|
+
/** One RPC function, parsed and language-neutral. */
|
|
3439
|
+
interface SdkMethod {
|
|
1489
3440
|
/**
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
3441
|
+
* Where this function's ARGUMENT nulls mean "unset" and where they mean
|
|
3442
|
+
* "null" — see {@link ModelNullPaths}.
|
|
3443
|
+
*
|
|
3444
|
+
* On the method rather than on a shared render input because it is a
|
|
3445
|
+
* per-method fact and the parser already holds the schema it comes from. Read
|
|
3446
|
+
* by the three targets whose rendered models cannot tell the two apart
|
|
3447
|
+
* (ruby, rust, swift); the other five have a marker of their own and ignore
|
|
3448
|
+
* it.
|
|
3449
|
+
*/
|
|
3450
|
+
argsNullPaths: ModelNullPaths;
|
|
1498
3451
|
/**
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
3452
|
+
* Generated args model name, or `undefined` when NO model could be named for
|
|
3453
|
+
* this function's arguments.
|
|
3454
|
+
*
|
|
3455
|
+
* `undefined` does NOT mean "takes no arguments" — see {@link SdkMethod.takesArgs}.
|
|
3456
|
+
* A schema carrying a `v.bigint()` or `v.bytes()` gets no model deliberately
|
|
3457
|
+
* (`hasUnrepresentableWireType`), and a backend that cannot name a shape leaves
|
|
3458
|
+
* it undeclared. Both still take arguments, just untyped ones.
|
|
3459
|
+
*/
|
|
3460
|
+
argsType: string | undefined;
|
|
3461
|
+
/** Raw exported function name (`"list"`), before any naming convention. */
|
|
3462
|
+
functionName: string;
|
|
3463
|
+
/** The wire identifier (`"messages:list"`), emitted verbatim into calls. */
|
|
3464
|
+
functionPath: string;
|
|
3465
|
+
/** Raw file namespace (`"messages"`), before any naming convention. */
|
|
3466
|
+
namespace: string;
|
|
3467
|
+
/** Generated result model name, or `undefined` while the result is untyped. */
|
|
3468
|
+
resultType: string | undefined;
|
|
3469
|
+
/** Human summary for the doc comment. */
|
|
3470
|
+
summary: string;
|
|
1509
3471
|
/**
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
3472
|
+
* Whether this function declares arguments at all, independent of whether a
|
|
3473
|
+
* model could be named for them.
|
|
3474
|
+
*
|
|
3475
|
+
* A target emits three shapes from this: a TYPED parameter when `argsType` is
|
|
3476
|
+
* set, an UNTYPED wire-shaped parameter when it is not but this is true, and no
|
|
3477
|
+
* parameter at all when this is false. Collapsing the middle case into the last
|
|
3478
|
+
* is what made `v.bigint()` functions uncallable with arguments.
|
|
3479
|
+
*/
|
|
3480
|
+
takesArgs: boolean;
|
|
3481
|
+
/** Which runtime verb this dispatches to. */
|
|
3482
|
+
verb: RuntimeVerb;
|
|
1517
3483
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
3484
|
+
/** One namespace's functions, sorted. */
|
|
3485
|
+
interface SdkNamespace {
|
|
3486
|
+
methods: ReadonlyArray<SdkMethod>;
|
|
3487
|
+
/** Raw namespace (`"messages"`); a target applies its own casing. */
|
|
3488
|
+
name: string;
|
|
3489
|
+
}
|
|
3490
|
+
/**
|
|
3491
|
+
* True when a schema actually describes a shape.
|
|
3492
|
+
*
|
|
3493
|
+
* `openrpc.ts` emits a description-only placeholder for any function without a
|
|
3494
|
+
* declared `.output()` (the return type is TS-inferred and absent from the IR).
|
|
3495
|
+
* A placeholder must never become a generated model: quicktype would render an
|
|
3496
|
+
* empty type, and the surface would decode every response into it — silently
|
|
3497
|
+
* discarding the real payload rather than leaving it untyped.
|
|
3498
|
+
*/
|
|
3499
|
+
declare const isTypedSchema: (schema: Record<string, unknown> | undefined) => boolean;
|
|
3500
|
+
/**
|
|
3501
|
+
* A path from a model's root to one property. `*` stands for every element of an
|
|
3502
|
+
* array or every value of a record, neither of which has named positions.
|
|
3503
|
+
*/
|
|
3504
|
+
type SchemaPath = ReadonlyArray<string>;
|
|
3505
|
+
/**
|
|
3506
|
+
* Where a model's nulls mean different things — the one fact a generated model
|
|
3507
|
+
* flattens away, and the reason three ports could not send a `v.nullable()`
|
|
3508
|
+
* argument at all.
|
|
3509
|
+
*
|
|
3510
|
+
* An unset `v.optional()` and a `v.nullable()` set to null are the SAME value in
|
|
3511
|
+
* every generated model (a nil field), and opposite things on the wire: the
|
|
3512
|
+
* validator rejects an explicit null for the first and requires the key present
|
|
3513
|
+
* for the second. Neither Ruby, Rust nor Swift renders a marker telling them
|
|
3514
|
+
* apart, so the distinction is computed here, from the schema, where `required`
|
|
3515
|
+
* still exists — and handed to the targets that need it.
|
|
3516
|
+
*
|
|
3517
|
+
* Two lists rather than one because the ports need opposite operations. Ruby and
|
|
3518
|
+
* Rust project a whole value tree and prune nulls, so they prune at
|
|
3519
|
+
* {@link ModelNullPaths.optional} and nowhere else — which also stops them
|
|
3520
|
+
* dropping a legitimate null inside a record or an array, as a blanket prune
|
|
3521
|
+
* does. Swift's `JSONEncoder` has already dropped every struct-property nil
|
|
3522
|
+
* before the transport sees a tree, so it restores nulls at
|
|
3523
|
+
* {@link ModelNullPaths.nullable} instead: an absent key at a required path can
|
|
3524
|
+
* only have been a nil, so putting the null back is exact.
|
|
3525
|
+
*
|
|
3526
|
+
* A `$ref` is NOT resolved. `openrpc.ts` inlines everything it emits, so this
|
|
3527
|
+
* never comes up for a generated document — but `--spec` accepts a hand-written
|
|
3528
|
+
* one, and there a `$ref`'d sub-object contributes no paths at all: the ports
|
|
3529
|
+
* that prune would send its unset optionals as null, and Swift would not restore
|
|
3530
|
+
* its nullables. Inline the schema, or teach this to follow the pointer.
|
|
3531
|
+
*
|
|
3532
|
+
* Both lists name PROPERTIES only. A record's values and an array's elements can
|
|
3533
|
+
* be null too, but no port drops one: the pruning ports prune at `optional`
|
|
3534
|
+
* paths, which a `*` position can never be, and `JSONEncoder` drops a nil only
|
|
3535
|
+
* from a struct property — a nil inside a dictionary or array encodes as null.
|
|
3536
|
+
* Listing a `*` leaf would also make Swift's restore INVENT record keys that
|
|
3537
|
+
* were never there, which is why the walk records the path it descends through
|
|
3538
|
+
* but never the `*` position itself.
|
|
3539
|
+
*/
|
|
3540
|
+
interface ModelNullPaths {
|
|
3541
|
+
/** Required properties that permit null — a null there is a VALUE and must survive. */
|
|
3542
|
+
nullable: ReadonlyArray<SchemaPath>;
|
|
3543
|
+
/** Properties absent from their object's `required` — a null there means UNSET. */
|
|
3544
|
+
optional: ReadonlyArray<SchemaPath>;
|
|
3545
|
+
}
|
|
3546
|
+
/** What a target renders from. */
|
|
3547
|
+
interface SdkRenderInput {
|
|
1527
3548
|
/**
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
3549
|
+
* The rendered model source, to be written as the target's model file.
|
|
3550
|
+
*
|
|
3551
|
+
* Empty for a target that emits its own model FILES via
|
|
3552
|
+
* {@link SdkTarget.renderModels} — those are already written, and this string
|
|
3553
|
+
* exists only so the declared-model reconciliation reads one shape.
|
|
3554
|
+
*/
|
|
3555
|
+
models: string;
|
|
3556
|
+
/** Namespaces and their functions, already sorted. */
|
|
3557
|
+
namespaces: ReadonlyArray<SdkNamespace>;
|
|
3558
|
+
}
|
|
3559
|
+
/**
|
|
3560
|
+
* One directory or file of the hand-written transport, and where it lands in the
|
|
3561
|
+
* output.
|
|
3562
|
+
*
|
|
3563
|
+
* `from` is relative to `sdks/<target id>/` and `to` is relative to `--out`. The
|
|
3564
|
+
* two differ because a repo layout and a consumable layout are not the same
|
|
3565
|
+
* shape: the Ruby transport lives under `lib/` so `ruby -Ilib` works in the
|
|
3566
|
+
* repo, while a vendored copy has no `lib` to point at, and the Rust transport
|
|
3567
|
+
* is the repo's root crate but a nested one in the output.
|
|
3568
|
+
*
|
|
3569
|
+
* Only the runtime is listed. A transport's own conformance suite and its
|
|
3570
|
+
* `generated_check/` sample are deliberately absent — they assert against
|
|
3571
|
+
* `protocol/fixtures/`, which is not copied, so vendoring them would ship a user
|
|
3572
|
+
* a test suite that cannot run.
|
|
3573
|
+
*/
|
|
3574
|
+
interface SdkVendorEntry {
|
|
3575
|
+
/** Path under `sdks/<id>/`. A directory is copied recursively. */
|
|
3576
|
+
from: string;
|
|
3577
|
+
/** Destination path under `--out`. */
|
|
3578
|
+
to: string;
|
|
3579
|
+
}
|
|
3580
|
+
/** A language target. One per `--lang` value. */
|
|
3581
|
+
interface SdkTarget {
|
|
3582
|
+
/** The `--lang` value (`"python"`, `"go"`, …). */
|
|
3583
|
+
id: string;
|
|
1534
3584
|
/**
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
crons: string;
|
|
1546
|
-
dataModel: string;
|
|
1547
|
-
drizzleGlobal: string;
|
|
1548
|
-
drizzleShard: string;
|
|
1549
|
-
functions: string; /** OpenAPI 3.1.0 document (`_generated/openapi.json`), pretty-printed JSON. */
|
|
1550
|
-
openApi: string;
|
|
1551
|
-
/**
|
|
1552
|
-
* OpenAPI document as an importable TS module (`_generated/openapi.ts`) —
|
|
1553
|
-
* `export const openApiSpec`, the worker imports it for
|
|
1554
|
-
* `createWorker({ openApiSpec })`. Same document as `openApi`. Written
|
|
1555
|
-
* alongside `openapi.json` whenever `apiSpec` includes `openapi`.
|
|
1556
|
-
*/
|
|
1557
|
-
openApiModule: string;
|
|
1558
|
-
/** OpenRPC 1.x document (`_generated/openrpc.json`), pretty-printed JSON. Always computed; written only when `apiSpec` includes `openrpc`. */
|
|
1559
|
-
openRpc: string;
|
|
1560
|
-
/**
|
|
1561
|
-
* OpenRPC document as an importable TS module (`_generated/openrpc.ts`) —
|
|
1562
|
-
* `export const openRpcSpec`, for `createWorker({ openRpcSpec })`. Same
|
|
1563
|
-
* document as `openRpc`. Written alongside `openrpc.json` whenever
|
|
1564
|
-
* `apiSpec` includes `openrpc`.
|
|
1565
|
-
*/
|
|
1566
|
-
openRpcModule: string;
|
|
1567
|
-
/** Push-consumer queue registry (`_generated/queues.ts`); `""` (and not written) when no push queues are declared. */
|
|
1568
|
-
queues: string;
|
|
1569
|
-
/** Project-bound seed client (`_generated/seed.ts`); `""` (and not written) when `@lunora/seed` is not a declared dependency. */
|
|
1570
|
-
seed: string;
|
|
1571
|
-
server: string;
|
|
1572
|
-
shard: string; /** Static vector-index registry (`_generated/vectors.ts`) — `LUNORA_VECTOR_INDEXES`. Empty array body when the schema declares none. */
|
|
1573
|
-
vectors: string;
|
|
1574
|
-
/** WorkflowEntrypoint classes (`_generated/workflows.ts`); `""` (and not written) when no workflows are declared. */
|
|
1575
|
-
workflows: string;
|
|
3585
|
+
* The quicktype backend + renderer options that produce this target's
|
|
3586
|
+
* models, or absent when the target emits its own (see
|
|
3587
|
+
* {@link SdkTarget.renderModels}) or none at all.
|
|
3588
|
+
*
|
|
3589
|
+
* `LanguageName` is quicktype's own union, so a target naming a backend
|
|
3590
|
+
* quicktype does not ship fails to compile rather than at run time.
|
|
3591
|
+
*/
|
|
3592
|
+
quicktype?: {
|
|
3593
|
+
lang: LanguageName;
|
|
3594
|
+
rendererOptions?: Record<string, string>;
|
|
1576
3595
|
};
|
|
1577
|
-
outputDirectory: string;
|
|
1578
3596
|
/**
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
3597
|
+
* Render the SDK. Returns file contents keyed by path relative to the
|
|
3598
|
+
* output directory (nested paths are created as needed).
|
|
3599
|
+
*
|
|
3600
|
+
* This includes the BUILD MANIFEST the layout needs — `go.mod`, `Cargo.toml`,
|
|
3601
|
+
* `Package.swift`, a crate root — because those name the vendored transport
|
|
3602
|
+
* and are therefore part of "how this language resolves the copy", not
|
|
3603
|
+
* something a consumer should have to write. Languages that resolve by
|
|
3604
|
+
* directory (Python, Ruby, Java, Kotlin) emit no manifest.
|
|
3605
|
+
*/
|
|
3606
|
+
render: (input: SdkRenderInput) => Record<string, string>;
|
|
1585
3607
|
/**
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
3608
|
+
* Emit this target's models from the schema directly, INSTEAD of quicktype,
|
|
3609
|
+
* as file contents keyed by path relative to the output directory.
|
|
3610
|
+
*
|
|
3611
|
+
* Present only for the two JVM targets, and the exception is earned rather
|
|
3612
|
+
* than a preference: quicktype's Java and Kotlin backends rename properties
|
|
3613
|
+
* and, under `just-types`, emit no mapping metadata, so a model they render
|
|
3614
|
+
* cannot be projected back onto the wire — and the only complete mapping
|
|
3615
|
+
* they offer requires a Jackson / Klaxon / kotlinx dependency, which is the
|
|
3616
|
+
* one thing these JDK-only transports are defined not to have.
|
|
3617
|
+
* `targets/java.ts` records every option that was measured.
|
|
3618
|
+
*
|
|
3619
|
+
* A MAP rather than the single string quicktype returns, because Java takes
|
|
3620
|
+
* one file per class: its single-file render is not compilable Java at all.
|
|
3621
|
+
* The values are still joined for {@link SdkRenderInput.models}, so the
|
|
3622
|
+
* declared-model reconciliation is the same code for every target.
|
|
3623
|
+
*/
|
|
3624
|
+
renderModels?: (document: OpenRpcDocument) => Record<string, string>;
|
|
1595
3625
|
/**
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
*
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
* Convert a codegen {@link ValidatorIR} into a JSON Schema node. A thin wrapper
|
|
1612
|
-
* over the shared {@link jsonSchemaFromNode} core (from `@lunora/values`) with the
|
|
1613
|
-
* IR-backed {@link irReader}, so the kind→schema mapping is the *same* algorithm
|
|
1614
|
-
* `@lunora/values`' `toJsonSchema` runs — codegen never instantiates the runtime
|
|
1615
|
-
* `v.*` objects, it only holds the reflected IR. Shared by the OpenAPI and
|
|
1616
|
-
* OpenRPC emitters so both surfaces speak one JSON Schema dialect.
|
|
1617
|
-
*/
|
|
1618
|
-
declare const validatorIrToJsonSchema: (validator: ValidatorIR) => JsonSchema;
|
|
1619
|
-
/** Build `{ type: "object", properties, required }` from an IR shape (mirrors `@lunora/values`' object mapping). */
|
|
1620
|
-
|
|
1621
|
-
/**
|
|
1622
|
-
* The machine-readable `LunoraError` codes Lunora emits on the RPC + REST
|
|
1623
|
-
* surfaces, enumerated from `@lunora/server`'s `CODE_STATUS` map plus the
|
|
1624
|
-
* runtime/DO dispatch codes (`FUNCTION_NOT_FOUND`, `PAYLOAD_TOO_LARGE`,
|
|
1625
|
-
* `METHOD_NOT_ALLOWED`, the `*_NOT_CONFIGURED` admin gates, …). The list documents
|
|
1626
|
-
* the contract; clients switch on `error.code`. Kept sorted for stable output.
|
|
1627
|
-
*/
|
|
1628
|
-
declare const LUNORA_ERROR_CODES: ReadonlyArray<string>;
|
|
1629
|
-
/**
|
|
1630
|
-
* Lunora-specific error→solution hints. A small rule table mapping the exact
|
|
1631
|
-
* messages Lunora throws — codegen/schema diagnostics, worker-entry export gaps,
|
|
1632
|
-
* and the runtime data-layer conflicts — to an actionable fix.
|
|
1633
|
-
*
|
|
1634
|
-
* This table is the single source of truth shared by every consumer that wants
|
|
1635
|
-
* to turn a raw Lunora error into a fix hint: the Vite error overlay
|
|
1636
|
-
* (`@lunora/vite`) renders the Markdown in the browser, and the standalone
|
|
1637
|
-
* `lunora dev` CLI prints it to the terminal. It lives in `@lunora/codegen`
|
|
1638
|
-
* because codegen owns most of the throw sites, so the hints stay next to the
|
|
1639
|
-
* code that produces the messages.
|
|
1640
|
-
*
|
|
1641
|
-
* Matching is on the **message text**. By the time a diagnostic reaches a
|
|
1642
|
-
* consumer it has often been flattened to a plain `{ message }` (the overlay
|
|
1643
|
-
* pushes codegen errors through `server.hot.send` with `name === "Error"`), so
|
|
1644
|
-
* the class identity is gone and the message is the only stable signal.
|
|
1645
|
-
*
|
|
1646
|
-
* Bodies are Markdown — the overlay renders them; the CLI prints them (lightly
|
|
1647
|
-
* de-marked) as-is.
|
|
1648
|
-
*/
|
|
1649
|
-
/** A resolved hint for a recognized Lunora error. */
|
|
1650
|
-
interface LunoraSolution {
|
|
1651
|
-
/** Markdown body shown under the header. */
|
|
1652
|
-
body: string;
|
|
1653
|
-
/** Short header for the solution. */
|
|
1654
|
-
header: string;
|
|
1655
|
-
/** Stable id (used in DEBUG logs and tests). */
|
|
1656
|
-
id: string;
|
|
3626
|
+
* THIRD-PARTY packages a consuming project must still install, reported by
|
|
3627
|
+
* the CLI. Empty for six of the eight — the transport is vendored and those
|
|
3628
|
+
* six reach the wire with only their standard library.
|
|
3629
|
+
*
|
|
3630
|
+
* A list, and not derivable from the transport, because a target's MODELS can
|
|
3631
|
+
* carry a dependency the transport does not: quicktype's Ruby backend emits
|
|
3632
|
+
* `Dry::Struct` types with no renderer option to avoid them, so a Ruby SDK
|
|
3633
|
+
* needs the gems even though `sdks/ruby` itself is dependency-free.
|
|
3634
|
+
*/
|
|
3635
|
+
requires: ReadonlyArray<string>;
|
|
3636
|
+
/**
|
|
3637
|
+
* Which parts of `sdks/<id>/` are the transport, and where they land under
|
|
3638
|
+
* `--out`. See {@link SdkVendorEntry}.
|
|
3639
|
+
*/
|
|
3640
|
+
vendor: ReadonlyArray<SdkVendorEntry>;
|
|
1657
3641
|
}
|
|
1658
|
-
/**
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
3642
|
+
/** Every language `lunora sdk generate --lang` accepts, keyed by id. */
|
|
3643
|
+
declare const SDK_TARGETS: Readonly<Record<string, SdkTarget>>;
|
|
3644
|
+
/** The accepted `--lang` values, sorted — for help text and error messages. */
|
|
3645
|
+
declare const SDK_LANGUAGES: ReadonlyArray<string>;
|
|
3646
|
+
/** The files a generation run writes, keyed by path relative to the output directory. */
|
|
3647
|
+
type SdkFiles = Record<string, string>;
|
|
3648
|
+
/** What a generation run produced, plus what it had to weaken and why. */
|
|
3649
|
+
interface SdkResult {
|
|
3650
|
+
files: SdkFiles;
|
|
3651
|
+
/**
|
|
3652
|
+
* Model names predicted from the schema that the chosen backend did not
|
|
3653
|
+
* declare, so their call sites fell back to an untyped return. Surfaced
|
|
3654
|
+
* rather than swallowed — silently weaker types are how an SDK looks
|
|
3655
|
+
* finished while returning `Any` everywhere.
|
|
3656
|
+
*/
|
|
3657
|
+
undeclared: ReadonlyArray<string>;
|
|
3658
|
+
/**
|
|
3659
|
+
* Functions whose args or result carry a `v.bigint()` or `v.bytes()`. No
|
|
3660
|
+
* typed model can represent those — the wire needs a tagged value that no
|
|
3661
|
+
* generated field produces — so their parameters stay untyped and the
|
|
3662
|
+
* caller passes wire values directly.
|
|
3663
|
+
*/
|
|
3664
|
+
unrepresentable: ReadonlyArray<string>;
|
|
1662
3665
|
}
|
|
1663
3666
|
/**
|
|
1664
|
-
*
|
|
1665
|
-
*
|
|
1666
|
-
*
|
|
1667
|
-
* The
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
*
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
*/
|
|
1675
|
-
declare const
|
|
3667
|
+
* Generate the SDK for `document` in `target`'s language.
|
|
3668
|
+
*
|
|
3669
|
+
* Async only because the model layer is: quicktype's renderer is promise-based.
|
|
3670
|
+
* The surface itself is pure, so a target's `render` stays synchronous and
|
|
3671
|
+
* unit-testable without touching quicktype.
|
|
3672
|
+
*
|
|
3673
|
+
* Models are rendered BEFORE the surface because the surface's model references
|
|
3674
|
+
* depend on what the backend actually declared — see {@link withDeclaredModels}.
|
|
3675
|
+
*/
|
|
3676
|
+
declare const generateSdk: (document: OpenRpcDocument, target: SdkTarget) => Promise<SdkResult>;
|
|
3677
|
+
/** The matching secret rule's `kind` for a string value, or `undefined` when none matches. */
|
|
3678
|
+
declare const secretKindOf: (value: string) => string | undefined;
|
|
3679
|
+
/** A redacted preview of a secret value — first 4 chars plus its length, never the full value. */
|
|
3680
|
+
declare const redact: (value: string) => string;
|
|
1676
3681
|
declare const VERSION = "0.0.0";
|
|
1677
|
-
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,
|
|
3682
|
+
export { AGENTS_FILENAME, type AgentIR, type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, DEFAULT_TARGET, type DriftChange, type DriftScope, type EmitAppOptions, FLAGS_FILENAME, type FieldSnapshot, type FlagsIR, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, type LintSchemaOptions, MUTATORS_FILENAME, type MaskProcedureIR, type MigrationIR, type MutatorIR, NOTIFY_FILENAME, OPENRPC_VERSION, type OpenApiEmitInput, type OpenRpcDocument, type OpenRpcEmitInput, type OpenRpcMethod, type PlatformDiagnostic, type ProjectIR, QUEUES_FILENAME, type QueryReadIR, type QueueIR, type R2sqlCallIR, type RelationSnapshot, type RlsMetadataIR, type RlsPolicyIR, type RlsProcedureIR, type RlsRoleIR, type RuntimeVerb, SCHEMA_SNAPSHOT_FILENAME, SCHEMA_SNAPSHOT_VERSION, SDK_LANGUAGES, SDK_TARGETS, SHAPES_FILENAME, type SandboxUsage, type SchemaDrift, type SchemaDriftDecision, type SchemaIR, type SchemaSnapshot, SchemaSnapshotParseError, type SdkFiles, type SdkMethod, type SdkNamespace, type SdkRenderInput, type SdkResult, type SdkTarget, type ShapeIR, type StorageRuleIR, type StorageRulesMetadataIR, type TableIR, type TableSnapshot, VERSION, type ValidatorIR, type VectorIndexIR, WORKFLOWS_FILENAME, type WorkflowIR, type WranglerVariableIR, buildOpenApiDocument, buildOpenRpcDocument, buildSchemaSnapshot, createCodegenProject, describeErrorLevelFindings, diagnosticAt, diffSchemaSnapshots, discoverAgents, discoverAuthApiCalls, discoverContainers, discoverCrons, discoverFlags, discoverFunctions, discoverHttpRoutes, discoverInserts, discoverMaskProcedures, discoverMigrations, discoverMutators, discoverNondeterministicCalls, discoverNotifyCalls, discoverNotifyConfig, discoverQueries, discoverQueues, discoverR2sqlCalls, discoverRlsMetadata, discoverRlsProcedures, discoverSandboxUsage, discoverSchema, discoverShapes, discoverStorageRulesMetadata, discoverWorkflows, emitAgents, emitApi, emitApp, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitOpenApi, emitOpenApiModule, emitOpenRpc, emitOpenRpcModule, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers, errorAdvisoryNames, errorPlatformDiagnosticNames, evaluateSchemaDrift, findTsconfig, formatAdvisories, generateSdk, isTypedSchema, lintSchema, parseSchemaSnapshot, platformMatrixIds, readPackageDependencies, readProjectTarget, redact, refreshCodegenProject, resolveCodegenTarget, runCodegen, schemaFromIr, secretKindOf, serializeSchemaSnapshot, toAdvisorContext, validatorIrToJsonSchema };
|