@lunora/server 1.0.0-alpha.4 → 1.0.0-alpha.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE.md +219 -0
  2. package/README.md +50 -0
  3. package/dist/data-model.d.mts +231 -156
  4. package/dist/data-model.d.ts +231 -156
  5. package/dist/data-model.mjs +0 -1
  6. package/dist/drizzle.mjs +1 -1
  7. package/dist/index.d.mts +1304 -831
  8. package/dist/index.d.ts +1304 -831
  9. package/dist/index.mjs +1 -24
  10. package/dist/otel.d.mts +543 -0
  11. package/dist/otel.d.ts +543 -0
  12. package/dist/otel.mjs +1 -0
  13. package/dist/packem_shared/LunoraEnvError-CgpI2Mm_.mjs +3 -0
  14. package/dist/packem_shared/LunoraError-LVhdU0Lo.mjs +1 -0
  15. package/dist/packem_shared/PRESENCE_DEFAULT_TTL_MS-CF_9uiu1.mjs +1 -0
  16. package/dist/packem_shared/allowAll-BnyNbJZT.mjs +1 -0
  17. package/dist/packem_shared/asBucketStorage-1pFfH-Tn.mjs +1 -0
  18. package/dist/packem_shared/bindOrm-Bp9hsM2q.mjs +1 -0
  19. package/dist/packem_shared/buildRlsReadRegistry-WdiqSj87.mjs +1 -0
  20. package/dist/packem_shared/composePluginMiddleware-COr09CXA.mjs +1 -0
  21. package/dist/packem_shared/context-identity-GunnA6La.mjs +1 -0
  22. package/dist/packem_shared/createPolicyDsl-sV1swpkD.mjs +1 -0
  23. package/dist/packem_shared/createSecrets-CgVPiW2C.mjs +1 -0
  24. package/dist/packem_shared/defineAggregateIndex-CYuH6IbJ.mjs +1 -0
  25. package/dist/packem_shared/defineIdentity-B7gfAgxx.mjs +1 -0
  26. package/dist/packem_shared/defineMigration-Bfpwxv2f.mjs +1 -0
  27. package/dist/packem_shared/defineMutator-BgpQ-xUo.mjs +1 -0
  28. package/dist/packem_shared/defineShape-Ds8uNqzX.mjs +1 -0
  29. package/dist/packem_shared/defineStorageRule-BDu01PUn.mjs +1 -0
  30. package/dist/packem_shared/functions-CDC08CWY.mjs +1 -0
  31. package/dist/packem_shared/httpAction-C14NuF3V.mjs +4 -0
  32. package/dist/packem_shared/initLunora-CHh1EmR4.mjs +1 -0
  33. package/dist/packem_shared/mask-C6Bi78qj.mjs +1 -0
  34. package/dist/packem_shared/onConnect-CEtRmUpJ.mjs +1 -0
  35. package/dist/packem_shared/policy-tag-Dprt9JWo.mjs +1 -0
  36. package/dist/packem_shared/protectPublic-BhKewPqm.mjs +1 -0
  37. package/dist/packem_shared/rls-_iVsPvhX.mjs +1 -0
  38. package/dist/packem_shared/run-middleware-BeEEqmdE.mjs +1 -0
  39. package/dist/packem_shared/storageRules-BptPZbi8.mjs +1 -0
  40. package/dist/packem_shared/types.d-C4CMJK8x.d.mts +141 -0
  41. package/dist/packem_shared/types.d-DdYF8E18.d.ts +141 -0
  42. package/dist/rls/testing.d.mts +31 -31
  43. package/dist/rls/testing.d.ts +31 -31
  44. package/dist/rls/testing.mjs +1 -49
  45. package/dist/types.d.mts +1163 -440
  46. package/dist/types.d.ts +1163 -440
  47. package/dist/types.mjs +1 -31
  48. package/package.json +18 -5
  49. package/dist/packem_shared/LunoraEnvError-DjFkpkSP.mjs +0 -187
  50. package/dist/packem_shared/LunoraError-DhggBJZF.mjs +0 -51
  51. package/dist/packem_shared/PRESENCE_DEFAULT_TTL_MS-BgBQsqQ-.mjs +0 -114
  52. package/dist/packem_shared/asBucketStorage-Cnxd9y2q.mjs +0 -11
  53. package/dist/packem_shared/bindOrm-Ce57S3N9.mjs +0 -128
  54. package/dist/packem_shared/composePluginMiddleware-Ck5_TUO8.mjs +0 -100
  55. package/dist/packem_shared/createPolicyDsl-De67zPDS.mjs +0 -29
  56. package/dist/packem_shared/defineAggregateIndex-C2gT1GzM.mjs +0 -252
  57. package/dist/packem_shared/defineMigration-CAJLr6fx.mjs +0 -8
  58. package/dist/packem_shared/defineStorageRule-qu0mpilX.mjs +0 -20
  59. package/dist/packem_shared/httpAction-B7FYUEgr.mjs +0 -340
  60. package/dist/packem_shared/initLunora-CATvPsVt.mjs +0 -86
  61. package/dist/packem_shared/mask-eCUYOwhd.mjs +0 -211
  62. package/dist/packem_shared/onConnect-CIPXKPyw.mjs +0 -13
  63. package/dist/packem_shared/protectPublic-BjFkQ_Or.mjs +0 -15
  64. package/dist/packem_shared/rls-Bi9HiyDC.mjs +0 -567
  65. package/dist/packem_shared/run-middleware-CYQOuoV6.mjs +0 -18
  66. package/dist/packem_shared/storageRules-4a30FSpI.mjs +0 -88
  67. package/dist/packem_shared/types.d-BDY0FYHK.d.ts +0 -135
  68. package/dist/packem_shared/types.d-DmvyEMD6.d.mts +0 -135
package/dist/types.d.mts CHANGED
@@ -6,12 +6,12 @@ type InferArgs<A extends ArgsValidator> = InferValidatorMap<A>;
6
6
  /** Storage backend for a `.global()` table: D1 (default) or a Postgres/MySQL database via Cloudflare Hyperdrive (PlanetScale, Neon, …). */
7
7
  type GlobalBackend = "d1" | "hyperdrive";
8
8
  /**
9
- * Cloudflare Durable Object data-residency jurisdiction declared via
10
- * `defineSchema(...).jurisdiction("…")`. Restricts where every DO the app
11
- * reaches runs and persists data (GDPR, FedRAMP, US data residency). Widening
12
- * union — Cloudflare adds values over time.
13
- * @see https://developers.cloudflare.com/durable-objects/reference/data-location/
14
- */
9
+ * Cloudflare Durable Object data-residency jurisdiction declared via
10
+ * `defineSchema(...).jurisdiction("…")`. Restricts where every DO the app
11
+ * reaches runs and persists data (GDPR, FedRAMP, US data residency). Widening
12
+ * union — Cloudflare adds values over time.
13
+ * @see https://developers.cloudflare.com/durable-objects/reference/data-location/
14
+ */
15
15
  type DurableObjectJurisdiction = "eu" | "fedramp" | "us";
16
16
  /** How a table is routed at runtime. */
17
17
  type ShardMode = {
@@ -23,6 +23,90 @@ type ShardMode = {
23
23
  } | {
24
24
  kind: "root";
25
25
  };
26
+ /** Poll cadence for a sourced table — `"manual"` (pull only on an explicit trigger) or a fixed interval. */
27
+ type ExternalSourceRefresh = "manual" | {
28
+ everyMs: number;
29
+ };
30
+ /**
31
+ * Delete-detection mode for external-source ingest (plan 077 / 136).
32
+ *
33
+ * `"full-pull"` (the default) reads the **whole** tenant membership each tick and
34
+ * diffs it, so it observes upstream deletes for free — but costs a full read per tick
35
+ * (the Phase-0 bench put the ceiling at ~10k rows).
36
+ *
37
+ * `"incremental"` pulls **only rows past a durable watermark** (`cursor`), cheap for
38
+ * large low-churn tables above the full-pull cap. Because an absent row then means
39
+ * "unchanged", not "deleted", incremental requires a delete-visibility path: either a
40
+ * `reconcileEveryMs` periodic full-pull sweep, or a `softDeleteColumn` whose
41
+ * tombstones the pull returns. `defineSchema` throws (and the
42
+ * `external_source_incremental_no_delete_path` advisor lint fails the build) when an
43
+ * incremental source declares neither.
44
+ */
45
+ type ExternalSourceMode = "full-pull" | "incremental";
46
+ /**
47
+ * Incremental-ingest cursor (plan 136): the monotonic watermark column plus the
48
+ * watermark-parameterized pull query. `column` names the field in the pulled rows
49
+ * whose max becomes the next watermark (e.g. `"updated_at"`). `query` is a second
50
+ * SQL that returns only rows changed since the watermark — the watermark binds as
51
+ * the parameter AFTER `tenantBy`'s params (e.g. Postgres
52
+ * `... WHERE tenant_id = $1 AND updated_at >= $2 ORDER BY updated_at`). Prefer `>=`
53
+ * with the idempotent upsert apply so rows sharing the boundary timestamp are never
54
+ * skipped (re-pulling them is a no-op).
55
+ */
56
+ interface ExternalSourceCursor {
57
+ /** The monotonic watermark column in the pulled rows; its max advances the stored watermark. */
58
+ column: string;
59
+ /** The incremental pull SQL. `tenantBy`'s params bind first, then the watermark as the trailing param. */
60
+ query: string;
61
+ }
62
+ /**
63
+ * Config for `.source(...)` (plan 077): declares a table as **materialized from an
64
+ * external Postgres/MySQL behind Cloudflare Hyperdrive**, not written by user
65
+ * mutations. A system-driven poll loop reads the tenant slice and lands it in the
66
+ * DO's SQLite (via the validated CDC writer), after which `defineShape` carries it
67
+ * to clients unchanged. Orthogonal to `shardMode` — a sourced table almost always
68
+ * also `.shardBy()`s, in which case `tenantBy` is the mandatory tenant-isolation
69
+ * boundary (enforced by the `external_source_unscoped` advisor lint).
70
+ */
71
+ interface ExternalSourceDefinition {
72
+ /** The wrangler Hyperdrive binding name the poll loop reads from. */
73
+ binding: string;
74
+ /** Project the materialized rows to these columns (passed to the membership diff). Omit ⇒ the full mapped document. */
75
+ columns?: ReadonlyArray<string>;
76
+ /** **Required for `mode: "incremental"`**: the watermark column + watermark-parameterized pull query (plan 136). Rejected on a `"full-pull"` source. */
77
+ cursor?: ExternalSourceCursor;
78
+ /** Column whose value becomes the Lunora `_id`. Defaults to `"id"`. */
79
+ idColumn?: string;
80
+ /** Transform an external row into the stored document body. Omit ⇒ every selected column except `idColumn` is copied. */
81
+ map?: (row: Record<string, unknown>) => Record<string, unknown>;
82
+ /** Delete-detection mode. `"full-pull"` (the default) diffs the whole membership; `"incremental"` pulls past a `cursor` watermark. */
83
+ mode?: ExternalSourceMode;
84
+ /** The full tenant-membership query, with driver-native placeholders (`$1` / `?`). `tenantBy` binds its params. */
85
+ query: string;
86
+ /**
87
+ * **Incremental delete-visibility (plan 136)**: run a full-pull sweep at most
88
+ * this often (millis) to GC upstream deletes an incremental slice can't see.
89
+ * One of `reconcileEveryMs` / `softDeleteColumn` is required for incremental;
90
+ * rejected on a `"full-pull"` source.
91
+ */
92
+ reconcileEveryMs?: number;
93
+ /** Poll cadence, or `"manual"`. Omit ⇒ the runtime's size-scaled default. */
94
+ refresh?: ExternalSourceRefresh;
95
+ /**
96
+ * **Incremental delete-visibility (plan 136)**: the upstream soft-delete
97
+ * tombstone column (e.g. `"deleted_at"`). When set, the incremental pull must
98
+ * return tombstoned rows and the ingest turns each into a local delete — an
99
+ * alternative to `reconcileEveryMs`. Rejected on a `"full-pull"` source.
100
+ */
101
+ softDeleteColumn?: string;
102
+ /**
103
+ * **Mandatory under `.shardBy()`**: map this DO's shard key → the query's bound
104
+ * params, so a tenant DO can only ever pull its own rows. An unscoped sourced +
105
+ * sharded table replicates the whole multitenant table into every shard — the
106
+ * `external_source_unscoped` advisor lint fails the build when this is absent.
107
+ */
108
+ tenantBy?: (shardKey: string) => ReadonlyArray<unknown>;
109
+ }
26
110
  interface IndexDefinition {
27
111
  fields: ReadonlyArray<string>;
28
112
  name: string;
@@ -33,21 +117,48 @@ interface SearchIndexDefinition {
33
117
  filterFields?: ReadonlyArray<string>;
34
118
  name: string;
35
119
  }
120
+ /**
121
+ * A geospatial index declared via `.geoIndex(name, { field })`. The runtime
122
+ * maintains a geohash companion table over the `v.geoPoint()` column `field` so
123
+ * `withGeoIndex(name, q => q.near(point, radius) | q.within(bbox))` resolves a
124
+ * proximity / bounding-box read as a geohash-prefix range scan plus a Haversine
125
+ * refine/sort on the candidate rows.
126
+ *
127
+ * - `field` — the `v.geoPoint()` column whose lat/lng feed the geohash.
128
+ * - `precision` — geohash character length on the companion (default 9, ~4.8 m cells); higher precision narrows each cell.
129
+ */
130
+ interface GeoIndexDefinition {
131
+ field: string;
132
+ name: string;
133
+ precision?: number;
134
+ }
135
+ /**
136
+ * Declarative table-level TTL declared via `.ttl(field, { after? })`. A DO
137
+ * alarm-driven sweep deletes (or, when the table also `.softDelete()`s,
138
+ * soft-deletes) rows whose expiry timestamp has passed.
139
+ *
140
+ * - `field` — an epoch-millisecond column. Without `after`, its value is the absolute expiry instant; with `after`, `field` is a base timestamp and the row expires `after` milliseconds later (`field + after`).
141
+ * - `after` — optional millisecond offset added to `field` to derive the expiry.
142
+ */
143
+ interface TtlDefinition {
144
+ after?: number;
145
+ field: string;
146
+ }
36
147
  /** Reducer applied by an aggregate index. */
37
148
  type AggregateOp = "avg" | "count" | "max" | "min" | "sum";
38
149
  /**
39
- * Declared aggregate index — the schema-level seam that lets the runtime keep
40
- * O(1) counters/sums in step with row writes (via the trigger runner) and
41
- * route matching reads through them.
42
- *
43
- * - `on` — the table whose rows feed the aggregate.
44
- * - `op` — the reducer. `count` is field-less; the others take `field`.
45
- * - `field` — the column the reducer applies to (required for non-count ops).
46
- * - `by` — group keys. When all `where` keys in a read participate in `by`, the
47
- * reader can answer from the counter table without scanning rows.
48
- * - `where` — optional static predicate baked into the counter (only the rows
49
- * matching it ever land in the counter).
50
- */
150
+ * Declared aggregate index — the schema-level seam that lets the runtime keep
151
+ * O(1) counters/sums in step with row writes (via the trigger runner) and
152
+ * route matching reads through them.
153
+ *
154
+ * - `on` — the table whose rows feed the aggregate.
155
+ * - `op` — the reducer. `count` is field-less; the others take `field`.
156
+ * - `field` — the column the reducer applies to (required for non-count ops).
157
+ * - `by` — group keys. When all `where` keys in a read participate in `by`, the
158
+ * reader can answer from the counter table without scanning rows.
159
+ * - `where` — optional static predicate baked into the counter (only the rows
160
+ * matching it ever land in the counter).
161
+ */
51
162
  interface AggregateIndexDefinition {
52
163
  by?: ReadonlyArray<string>;
53
164
  field?: string;
@@ -57,32 +168,32 @@ interface AggregateIndexDefinition {
57
168
  where?: Record<string, unknown>;
58
169
  }
59
170
  /**
60
- * One ordering key on a `rankIndex.sortBy`: which column to sort by, and the
61
- * direction. The runtime breaks ties on the row's `_id` ASC so the order is
62
- * total and `rank()` always returns a deterministic 1-based position.
63
- */
171
+ * One ordering key on a `rankIndex.sortBy`: which column to sort by, and the
172
+ * direction. The runtime breaks ties on the row's `_id` ASC so the order is
173
+ * total and `rank()` always returns a deterministic 1-based position.
174
+ */
64
175
  interface RankSortKey {
65
176
  direction: "asc" | "desc";
66
177
  field: string;
67
178
  }
68
179
  /**
69
- * Declared rank index — a sorted companion table per `(partition tuple, sortBy)`
70
- * maintained by triggers, so:
71
- *
72
- * - `rank(row)` returns the row's 1-based position within its partition under
73
- * the declared `sortBy` order, plus the partition's total row count, in
74
- * O(log n) lookups against the SQLite btree on the companion table.
75
- * - `rankPage({ where, take, from })` walks the same companion table to return
76
- * rows in the declared order — a sorted-pagination accelerator.
77
- *
78
- * Fields mirror `AggregateIndexDefinition`:
79
- *
80
- * - `on` — the source table whose rows feed the rank.
81
- * - `sortBy` — ordered keys driving the rank. Required.
82
- * - `partitionBy` — columns that scope each rank context (e.g. `["channelId"]`
83
- * to rank within a channel). Omitted ⇒ one global rank across the table.
84
- * - `where` — static predicate baked into the index; only matching rows enter.
85
- */
180
+ * Declared rank index — a sorted companion table per `(partition tuple, sortBy)`
181
+ * maintained by triggers, so:
182
+ *
183
+ * - `rank(row)` returns the row's 1-based position within its partition under
184
+ * the declared `sortBy` order, plus the partition's total row count, in
185
+ * O(log n) lookups against the SQLite btree on the companion table.
186
+ * - `rankPage({ where, take, from })` walks the same companion table to return
187
+ * rows in the declared order — a sorted-pagination accelerator.
188
+ *
189
+ * Fields mirror `AggregateIndexDefinition`:
190
+ *
191
+ * - `on` — the source table whose rows feed the rank.
192
+ * - `sortBy` — ordered keys driving the rank. Required.
193
+ * - `partitionBy` — columns that scope each rank context (e.g. `["channelId"]`
194
+ * to rank within a channel). Omitted ⇒ one global rank across the table.
195
+ * - `where` — static predicate baked into the index; only matching rows enter.
196
+ */
86
197
  interface RankIndexDefinition {
87
198
  name: string;
88
199
  on: string;
@@ -93,16 +204,16 @@ interface RankIndexDefinition {
93
204
  /** FK behavior when a referenced parent row is deleted (mirrors SQL `ON DELETE`). */
94
205
  type OnDeleteAction = "cascade" | "restrict" | "set null";
95
206
  /**
96
- * A declared relation between two tables, recorded by `.relations((r) => …)`.
97
- *
98
- * - `one` (many-to-one): the FK column `field` lives on **this** table and
99
- * points at `table`.`references` (default `_id`). Loads a single doc.
100
- * - `many` (one-to-many): the FK column `field` lives on the **target** table
101
- * and points back at this table's `references` (default `_id`). Loads an array.
102
- *
103
- * `onDelete` is meaningful only on `one`: it is the action applied to the
104
- * holder rows when the referenced parent row is deleted.
105
- */
207
+ * A declared relation between two tables, recorded by `.relations((r) => …)`.
208
+ *
209
+ * - `one` (many-to-one): the FK column `field` lives on **this** table and
210
+ * points at `table`.`references` (default `_id`). Loads a single doc.
211
+ * - `many` (one-to-many): the FK column `field` lives on the **target** table
212
+ * and points back at this table's `references` (default `_id`). Loads an array.
213
+ *
214
+ * `onDelete` is meaningful only on `one`: it is the action applied to the
215
+ * holder rows when the referenced parent row is deleted.
216
+ */
106
217
  interface RelationDefinition {
107
218
  field: string;
108
219
  kind: "many" | "one";
@@ -113,15 +224,15 @@ interface RelationDefinition {
113
224
  /** Distance metric used by a Vectorize index. */
114
225
  type VectorMetric = "cosine" | "dot-product" | "euclidean";
115
226
  /**
116
- * Bring-your-own-embedder: a user-supplied fn turning a source string into a
117
- * numeric vector. The runtime calls it at upsert/query time so the framework
118
- * never couples to a single embedding provider.
119
- */
227
+ * Bring-your-own-embedder: a user-supplied fn turning a source string into a
228
+ * numeric vector. The runtime calls it at upsert/query time so the framework
229
+ * never couples to a single embedding provider.
230
+ */
120
231
  type VectorEmbedder = (input: string) => Promise<ReadonlyArray<number>> | ReadonlyArray<number>;
121
232
  /**
122
- * Vector index declared inline on a table via `.vectorize(field, opts)`
123
- * (DSL Shape A). The source is always a single column on the owning table.
124
- */
233
+ * Vector index declared inline on a table via `.vectorize(field, opts)`
234
+ * (DSL Shape A). The source is always a single column on the owning table.
235
+ */
125
236
  interface TableVectorIndex {
126
237
  dimensions: number;
127
238
  embed: VectorEmbedder;
@@ -132,74 +243,110 @@ interface TableVectorIndex {
132
243
  }
133
244
  interface TableDefinition<Shape extends Record<string, Validator> = Record<string, Validator>> {
134
245
  /**
135
- * Aggregate indexes declared via `.aggregateIndex(name, opts)`. The runtime
136
- * maintains a counter row per `by` group via the trigger seam, so reads
137
- * whose `where` keys all participate in the index's `by` set are answered
138
- * without scanning the underlying table.
139
- */
246
+ * Aggregate indexes declared via `.aggregateIndex(name, opts)`. The runtime
247
+ * maintains a counter row per `by` group via the trigger seam, so reads
248
+ * whose `where` keys all participate in the index's `by` set are answered
249
+ * without scanning the underlying table.
250
+ */
140
251
  aggregateIndexes: ReadonlyArray<AggregateIndexDefinition>;
252
+ /**
253
+ * Set by `.source(...)` (named `externalSource`, not `source`, so the data
254
+ * field doesn't collide with the fluent `.source()` builder method — same
255
+ * convention as `shardBy()`/`shardMode`). When present, the table is
256
+ * materialized from an external Hyperdrive-backed database by a system poll
257
+ * loop rather than user mutations. Implies `isExternallyManaged`.
258
+ */
259
+ externalSource?: ExternalSourceDefinition;
260
+ /**
261
+ * Geospatial indexes declared via `.geoIndex(name, { field })`. The runtime
262
+ * maintains a geohash companion over the named `v.geoPoint()` column so
263
+ * `withGeoIndex(name, q => q.near(point, radius) | q.within(bbox))` resolves
264
+ * a proximity/bounding-box read as a geohash-prefix range scan plus a
265
+ * Haversine refine/sort. Empty unless `.geoIndex()` was called.
266
+ */
267
+ geoIndexes: ReadonlyArray<GeoIndexDefinition>;
141
268
  indexes: ReadonlyArray<IndexDefinition>;
142
269
  /**
143
- * `true` when `.externallyManaged()` was called — the table's rows are
144
- * written outside Lunora's discoverable insert path (an adapter, a
145
- * migration, or framework middleware), e.g. `@lunora/auth`'s better-auth
146
- * tables or `@lunora/ratelimit`'s store. Advisor insert-path lints
147
- * (`table_without_insert`) skip such tables instead of flagging the absent
148
- * `ctx.db.insert(...)`.
149
- */
270
+ * `true` when `.externallyManaged()` was called — the table's rows are
271
+ * written outside Lunora's discoverable insert path (an adapter, a
272
+ * migration, or framework middleware), e.g. `@lunora/auth`'s better-auth
273
+ * tables or `@lunora/ratelimit`'s store. Advisor insert-path lints
274
+ * (`table_without_insert`) skip such tables instead of flagging the absent
275
+ * `ctx.db.insert(...)`.
276
+ */
150
277
  isExternallyManaged?: boolean;
151
278
  /**
152
- * `true` when `.public()` was called — the table opts OUT of secure-by-default
153
- * RLS. Under a schema marked `.rls("required")`, every table is protected (the
154
- * DO/D1 write path denies raw, non-RLS `ctx.db` access) UNLESS it is `isPublic`.
155
- * Has no effect when the schema does not require RLS.
156
- */
279
+ * `true` when `.public()` was called — the table opts OUT of secure-by-default
280
+ * RLS. Under a schema marked `.rls("required")`, every table is protected (the
281
+ * DO/D1 write path denies raw, non-RLS `ctx.db` access) UNLESS it is `isPublic`.
282
+ * Has no effect when the schema does not require RLS.
283
+ */
157
284
  isPublic?: boolean;
158
285
  /**
159
- * Rank indexes declared via `.rankIndex(name, opts)`. The runtime maintains
160
- * a sorted companion table per declared rank with a btree on
161
- * `(partition, sortBy)` so `rank(row)` returns the row's 1-based position
162
- * within its partition in O(log n), and `rankPage()` walks the index for
163
- * sorted pagination.
164
- */
286
+ * Set by `.ownedBy(field)` the column holding the owning user's id (named
287
+ * `ownerField`, a data field, rather than colliding with the fluent
288
+ * `.ownedBy()` builder method same convention as `shardBy()`/`shardMode`).
289
+ *
290
+ * A shape over this table with `owner: true` derives its predicate from this
291
+ * field, so "only the owner may replicate these rows" is declared once on the
292
+ * table instead of being restated in every shape's `where`. Absent ⇒ the table
293
+ * has no single owning column and a shape must spell its predicate out.
294
+ */
295
+ ownerField?: string;
296
+ /**
297
+ * Rank indexes declared via `.rankIndex(name, opts)`. The runtime maintains
298
+ * a sorted companion table per declared rank with a btree on
299
+ * `(partition, sortBy)` so `rank(row)` returns the row's 1-based position
300
+ * within its partition in O(log n), and `rankPage()` walks the index for
301
+ * sorted pagination.
302
+ */
165
303
  rankIndexes: ReadonlyArray<RankIndexDefinition>;
166
304
  /**
167
- * Declared relations keyed by accessor name; empty unless `.relations()`
168
- * was called. Named `relationMap` (not `relations`) so the fluent
169
- * `.relations((r) => …)` builder method doesn't collide with this field.
170
- */
305
+ * Declared relations keyed by accessor name; empty unless `.relations()`
306
+ * was called. Named `relationMap` (not `relations`) so the fluent
307
+ * `.relations((r) => …)` builder method doesn't collide with this field.
308
+ */
171
309
  relationMap: Record<string, RelationDefinition>;
172
310
  searchIndexes: ReadonlyArray<SearchIndexDefinition>;
173
311
  shape: Shape;
174
312
  shardMode: ShardMode;
175
313
  /**
176
- * Set by `.softDelete()` (named `softDeleteMode`, not `softDelete`, so the
177
- * data field doesn't collide with the fluent `.softDelete()` builder method —
178
- * same convention as `shardBy()`/`shardMode`). When present, the table carries
179
- * a nullable timestamp column (`field`, default `deletedAt`):
180
- * `ctx.db.&lt;table>.delete()` flips it instead of physically removing the row,
181
- * and **list reads** (`findMany`/`findFirst`/`query()`/`count`/`aggregate`/
182
- * relation loads) hide rows whose `field` is set unless
183
- * `includeDeleted: true` is passed. By-id `get`/`patch`/`replace` and
184
- * `restore` are unaffected. Absent ⇒ deletes are physical, as before.
185
- */
314
+ * Set by `.softDelete()` (named `softDeleteMode`, not `softDelete`, so the
315
+ * data field doesn't collide with the fluent `.softDelete()` builder method —
316
+ * same convention as `shardBy()`/`shardMode`). When present, the table carries
317
+ * a nullable timestamp column (`field`, default `deletedAt`):
318
+ * `ctx.db.&lt;table>.delete()` flips it instead of physically removing the row,
319
+ * and **list reads** (`findMany`/`findFirst`/`query()`/`count`/`aggregate`/
320
+ * relation loads) hide rows whose `field` is set unless
321
+ * `includeDeleted: true` is passed. By-id `get`/`patch`/`replace` and
322
+ * `restore` are unaffected. Absent ⇒ deletes are physical, as before.
323
+ */
186
324
  softDeleteMode?: {
187
325
  field: string;
188
326
  };
189
327
  /**
190
- * Declared lifecycle triggers keyed by accessor name; empty unless
191
- * `.triggers()` was called. Named `triggerMap` (not `triggers`) so the
192
- * fluent `.triggers((t) => …)` builder method doesn't collide with this
193
- * field — same reasoning as {@link TableDefinition.relationMap}.
194
- */
328
+ * Declared lifecycle triggers keyed by accessor name; empty unless
329
+ * `.triggers()` was called. Named `triggerMap` (not `triggers`) so the
330
+ * fluent `.triggers((t) => …)` builder method doesn't collide with this
331
+ * field — same reasoning as {@link TableDefinition.relationMap}.
332
+ */
195
333
  triggerMap: Record<string, TriggerDefinition>;
334
+ /**
335
+ * Set by `.ttl(field, { after })` — the declarative auto-expiry policy. A DO
336
+ * alarm-driven sweep deletes rows whose expiry timestamp has passed (or
337
+ * soft-deletes them when the table also `.softDelete()`s). Named `ttlPolicy`
338
+ * (a data field) rather than colliding with the fluent `.ttl()` builder
339
+ * method — same convention as `shardBy()`/`shardMode`. Absent ⇒ rows never
340
+ * auto-expire.
341
+ */
342
+ ttlPolicy?: TtlDefinition;
196
343
  vectorIndexes: ReadonlyArray<TableVectorIndex>;
197
344
  }
198
345
  /**
199
- * Standalone vector index declared via `defineVectorIndex(...)` (DSL Shape B).
200
- * Unlike {@link TableVectorIndex}, the source is a `select` function so it can
201
- * derive the embedded text from any computation (e.g. `title + body`).
202
- */
346
+ * Standalone vector index declared via `defineVectorIndex(...)` (DSL Shape B).
347
+ * Unlike {@link TableVectorIndex}, the source is a `select` function so it can
348
+ * derive the embedded text from any computation (e.g. `title + body`).
349
+ */
203
350
  interface VectorIndexDefinition {
204
351
  readonly dimensions: number;
205
352
  readonly embed: VectorEmbedder;
@@ -211,38 +358,78 @@ interface VectorIndexDefinition {
211
358
  }
212
359
  interface Schema<T extends Record<string, TableDefinition> = Record<string, TableDefinition>> {
213
360
  /**
214
- * Secure-by-default RLS mode declared via `.rls("required")`. When
215
- * `"required"`, every table is protected: the DO/D1 write path denies raw
216
- * (non-RLS-wrapped) `ctx.db` access at runtime, so a procedure that forgets
217
- * `.use(rls(...))` fails closed instead of silently exposing the table. A
218
- * table opts out with `.public()` (→ {@link TableDefinition.isPublic}).
219
- * Absent ⇒ legacy opt-in behavior (RLS only where a policy is applied).
220
- */
361
+ * Secure-by-default RLS mode declared via `.rls("required")`. When
362
+ * `"required"`, every table is protected: the DO/D1 write path denies raw
363
+ * (non-RLS-wrapped) `ctx.db` access at runtime, so a procedure that forgets
364
+ * `.use(rls(...))` fails closed instead of silently exposing the table. A
365
+ * table opts out with `.public()` (→ {@link TableDefinition.isPublic}).
366
+ * Absent ⇒ legacy opt-in behavior (RLS only where a policy is applied).
367
+ */
221
368
  readonly rlsMode?: "required";
222
369
  readonly tables: T;
223
370
  readonly vectorIndexes: Record<string, VectorIndexDefinition>;
224
371
  }
225
372
  type FunctionKind = "action" | "mutation" | "query" | "stream";
226
373
  /**
227
- * Call surface a function is exposed on. `public` functions are reachable from
228
- * clients via the generated `api`; `internal` functions are reachable only
229
- * server-to-server (`ctx.runQuery`/`runMutation`/`runAction`) and are rejected
230
- * by the DO's external RPC path. Absence is treated as `public` for
231
- * back-compat with functions registered before visibility existed.
232
- */
374
+ * Call surface a function is exposed on. `public` functions are reachable from
375
+ * clients via the generated `api`; `internal` functions are reachable only
376
+ * server-to-server (`ctx.runQuery`/`runMutation`/`runAction`) and are rejected
377
+ * by the DO's external RPC path. Absence is treated as `public` for
378
+ * back-compat with functions registered before visibility existed.
379
+ */
233
380
  type FunctionVisibility = "internal" | "public";
381
+ /**
382
+ * x402 payment tag attached by the `.x402({ price })` builder modifier. Marks a
383
+ * public procedure as paid: the origin worker answers an unpaid client RPC with
384
+ * HTTP 402, verifies + settles the payment, and only then dispatches to the
385
+ * shard. The runtime reads only `price` from here — the network, recipient, and
386
+ * facilitator live in the worker-level x402 charge config, so `@lunora/runtime`
387
+ * never has to import `@lunora/x402` (and its viem/solana deps).
388
+ */
389
+ interface X402ProcedureConfig {
390
+ /**
391
+ * USD-denominated price: a number of dollars (`0.01`) or a decimal string
392
+ * (`"0.01"`, or the `"$0.01"` shorthand). Resolved to the network
393
+ * stablecoin's base units (USDC has 6 decimals) at challenge time.
394
+ */
395
+ readonly price: number | string;
396
+ }
397
+ /**
398
+ * Opt-in public-surface tag attached by the `.expose({ rest: true })` builder
399
+ * modifier (plan 167). Marks a procedure as deliberately published over the
400
+ * public REST surface: the runtime mints a `/_lunora/rest/&lt;namespace>/&lt;fn>` route
401
+ * that dispatches THROUGH the procedure (so `ctx.auth` / RLS / validators are
402
+ * enforced), and the generated OpenAPI describes it. Everything is default-closed
403
+ * — a procedure without this tag is unreachable over REST.
404
+ */
405
+ interface ExposeConfig {
406
+ /** Publish this procedure over the public REST surface. */
407
+ readonly rest?: boolean;
408
+ }
234
409
  interface RegisteredFunction<A extends ArgsValidator, R, Kind extends FunctionKind> {
235
410
  readonly args: A;
411
+ /**
412
+ * Set by the `.expose({ rest: true })` builder modifier. Marks the procedure
413
+ * as published on the public REST surface (plan 167). Absent on procedures that
414
+ * are reachable only via typed RPC (the default).
415
+ */
416
+ readonly expose?: ExposeConfig;
236
417
  readonly handler: (context: unknown, args: InferArgs<A>) => Promise<R> | R;
237
418
  readonly kind: Kind;
238
419
  /**
239
- * Set on connection-lifecycle hooks (`onConnect` / `onDisconnect`).
240
- * Marks the function for the generated `LUNORA_LIFECYCLE_HOOKS` manifest so the
241
- * DO dispatches it on socket connect/disconnect rather than via a client RPC.
242
- * Absent on ordinary registrations.
243
- */
420
+ * Set on connection-lifecycle hooks (`onConnect` / `onDisconnect`).
421
+ * Marks the function for the generated `LUNORA_LIFECYCLE_HOOKS` manifest so the
422
+ * DO dispatches it on socket connect/disconnect rather than via a client RPC.
423
+ * Absent on ordinary registrations.
424
+ */
244
425
  readonly lifecycle?: LifecycleEventKind;
245
426
  readonly visibility?: FunctionVisibility;
427
+ /**
428
+ * Set by the `.x402({ price })` builder modifier. Marks the procedure as paid
429
+ * so the origin worker gates it behind an x402 402-challenge before dispatch.
430
+ * Absent on unpaid functions.
431
+ */
432
+ readonly x402?: X402ProcedureConfig;
246
433
  }
247
434
  type RegisteredQuery<A extends ArgsValidator, R> = RegisteredFunction<A, R, "query">;
248
435
  type RegisteredMutation<A extends ArgsValidator, R> = RegisteredFunction<A, R, "mutation">;
@@ -250,11 +437,11 @@ type RegisteredAction<A extends ArgsValidator, R> = RegisteredFunction<A, R, "ac
250
437
  /** Which side of the WebSocket lifecycle a hook fires on. */
251
438
  type LifecycleEventKind = "connect" | "disconnect";
252
439
  /**
253
- * The event a connection-lifecycle hook receives as its second argument. It is
254
- * the JSON-serializable payload the DO forwards on socket connect/disconnect;
255
- * the verified caller identity is also reflected on `ctx.auth` (the hook runs
256
- * under the connecting user via `resolveIdentity`).
257
- */
440
+ * The event a connection-lifecycle hook receives as its second argument. It is
441
+ * the JSON-serializable payload the DO forwards on socket connect/disconnect;
442
+ * the verified caller identity is also reflected on `ctx.auth` (the hook runs
443
+ * under the connecting user via `resolveIdentity`).
444
+ */
258
445
  interface LifecycleEvent {
259
446
  /** Stable per-socket id, minted at upgrade and replayed verbatim on disconnect. */
260
447
  readonly connectionId: string;
@@ -266,20 +453,20 @@ interface LifecycleEvent {
266
453
  readonly userId: string | null;
267
454
  }
268
455
  /**
269
- * A registered connection-lifecycle hook — an internal mutation tagged with the
270
- * lifecycle side it fires on. Produced by `onConnect` / `onDisconnect`.
271
- */
456
+ * A registered connection-lifecycle hook — an internal mutation tagged with the
457
+ * lifecycle side it fires on. Produced by `onConnect` / `onDisconnect`.
458
+ */
272
459
  type RegisteredLifecycleHook = RegisteredFunction<Record<string, never>, void, "mutation"> & {
273
460
  readonly lifecycle: LifecycleEventKind;
274
461
  };
275
462
  /**
276
- * A streaming query registration. Unlike {@link RegisteredFunction} the handler
277
- * returns an `AsyncIterable&lt;R>` synchronously (it does NOT `Promise&lt;R>`); the
278
- * runtime drives it frame by frame and forwards each chunk to the caller. The
279
- * third `signal` argument is wired to the caller's cancel signal so the handler
280
- * can stop early — break out of the loop or check `signal.aborted` between
281
- * yields.
282
- */
463
+ * A streaming query registration. Unlike {@link RegisteredFunction} the handler
464
+ * returns an `AsyncIterable&lt;R>` synchronously (it does NOT `Promise&lt;R>`); the
465
+ * runtime drives it frame by frame and forwards each chunk to the caller. The
466
+ * third `signal` argument is wired to the caller's cancel signal so the handler
467
+ * can stop early — break out of the loop or check `signal.aborted` between
468
+ * yields.
469
+ */
283
470
  interface RegisteredStream<A extends ArgsValidator, R> {
284
471
  readonly args: A;
285
472
  readonly handler: (context: unknown, args: InferArgs<A>, signal: AbortSignal) => AsyncIterable<R>;
@@ -289,10 +476,10 @@ interface RegisteredStream<A extends ArgsValidator, R> {
289
476
  /** The system tables `ctx.db.system` can read. */
290
477
  type SystemTableName = "_scheduled_functions" | "_storage";
291
478
  /**
292
- * A pending scheduled invocation as surfaced by the `_scheduled_functions`
293
- * system table. Mirrors {@link ScheduledJob} (the `ctx.scheduler` view); the
294
- * separate name keeps the system-table read surface self-describing.
295
- */
479
+ * A pending scheduled invocation as surfaced by the `_scheduled_functions`
480
+ * system table. Mirrors {@link ScheduledJob} (the `ctx.scheduler` view); the
481
+ * separate name keeps the system-table read surface self-describing.
482
+ */
296
483
  interface ScheduledFunctionDoc {
297
484
  /** Function arguments the job will be dispatched with. */
298
485
  args: Record<string, unknown>;
@@ -322,55 +509,79 @@ interface SystemQuery<T extends SystemTableName> {
322
509
  collect: () => Promise<SystemDoc<T>[]>;
323
510
  }
324
511
  /**
325
- * Read-only reader over Lunora's system tables (`_scheduled_functions`,
326
- * `_storage`), exposed as `ctx.db.system`. Mirrors Convex's `ctx.db.system`.
327
- *
328
- * **Best-effort and eventually consistent.** Unlike `ctx.db.&lt;table>` — which
329
- * reads the shard's transactional SQLite snapshot — the data behind these tables
330
- * lives OUTSIDE the shard (scheduled functions in the `SchedulerDO`, storage
331
- * objects in R2). Every `collect()` / `get()` reaches across to that source.
332
- *
333
- * It is **not part of the mutation transaction snapshot** (no OCC guard, no
334
- * subscription dependency recorded — reading it inside a mutation does not pin
335
- * it), and results are **eventually consistent** with writes a mutation just
336
- * made (e.g. a freshly scheduled job may not appear yet).
337
- *
338
- * Read-only by design: mutate scheduled jobs via `ctx.scheduler`, storage
339
- * objects via `ctx.storage`.
340
- */
512
+ * Read-only reader over Lunora's system tables (`_scheduled_functions`,
513
+ * `_storage`), exposed as `ctx.db.system`. Mirrors Convex's `ctx.db.system`.
514
+ *
515
+ * **Best-effort and eventually consistent.** Unlike `ctx.db.&lt;table>` — which
516
+ * reads the shard's transactional SQLite snapshot — the data behind these tables
517
+ * lives OUTSIDE the shard (scheduled functions in the `SchedulerDO`, storage
518
+ * objects in R2). Every `collect()` / `get()` reaches across to that source.
519
+ *
520
+ * It is **not part of the mutation transaction snapshot** (no OCC guard, no
521
+ * subscription dependency recorded — reading it inside a mutation does not pin
522
+ * it), and results are **eventually consistent** with writes a mutation just
523
+ * made (e.g. a freshly scheduled job may not appear yet).
524
+ *
525
+ * Read-only by design: mutate scheduled jobs via `ctx.scheduler`, storage
526
+ * objects via `ctx.storage`.
527
+ */
341
528
  interface SystemDatabaseReader {
342
529
  /**
343
- * Resolve a single system-table row by id, or `null` when absent.
344
- * (`_scheduled_functions` → job id; `_storage` → object key.)
345
- */
530
+ * Resolve a single system-table row by id, or `null` when absent.
531
+ * (`_scheduled_functions` → job id; `_storage` → object key.)
532
+ */
346
533
  get: <T extends SystemTableName>(table: T, id: string) => Promise<SystemDoc<T> | null>;
347
534
  /**
348
- * Begin a read over a system table; call `.collect()` to resolve the full
349
- * list. No filtering, indexing, or pagination — the backing source is remote
350
- * and the surface stays deliberately minimal.
351
- */
535
+ * Begin a read over a system table; call `.collect()` to resolve the full
536
+ * list. No filtering, indexing, or pagination — the backing source is remote
537
+ * and the surface stays deliberately minimal.
538
+ */
352
539
  query: <T extends SystemTableName>(table: T) => SystemQuery<T>;
353
540
  }
354
541
  /**
355
- * Read-only handle bound to a table. Used by `query`/`mutation`/`action`. The
356
- * actual SQL implementation lives in `@lunora/do`; these are signatures only.
357
- */
542
+ * Read-only handle bound to a table. Used by `query`/`mutation`/`action`. The
543
+ * actual SQL implementation lives in `@lunora/do`; these are signatures only.
544
+ */
358
545
  interface DatabaseReader {
546
+ /**
547
+ * The throwing sibling of {@link DatabaseReader.normalizeId}: brand `id` as an
548
+ * {@link Id} for `tableName`, or throw `BAD_REQUEST` when it is not structurally
549
+ * an id. Pure — it never reads the database, so a valid id for a row that
550
+ * doesn't exist still returns.
551
+ *
552
+ * This is the **parse boundary** for an id that arrived as a plain `string`: a
553
+ * wire payload, a mutator's args, a change plan computed on the client. The
554
+ * alternative is `value as Id&lt;"table">` at every such call site — an assertion,
555
+ * not a check, and one that has to be repeated for every table a helper is
556
+ * generic over:
557
+ *
558
+ * ```ts
559
+ * for (const patch of plan.patches) {
560
+ * await ctx.db.patch(ctx.db.asId("nodes", patch.id), patch.fields);
561
+ * }
562
+ * ```
563
+ *
564
+ * Ids are opaque strings, so the check is exactly `normalizeId`'s: it rejects
565
+ * empty, whitespace-bearing, and NUL-bearing values, not "an id that isn't in
566
+ * this table". Use it to get the brand honestly, and `get()` to learn whether
567
+ * the row exists.
568
+ */
569
+ asId: <T extends string>(tableName: T, id: string) => Id<T>;
359
570
  get: <T extends string>(id: Id<T>) => Promise<Record<string, unknown> | null>;
360
571
  /**
361
- * Validate an untrusted `id` string against the structural shape of an id
362
- * for `tableName`, returning the branded {@link Id} when it is well-formed
363
- * and `null` otherwise. Pure structural validation — it never reads the
364
- * database, so a structurally valid id for a row that doesn't exist still
365
- * returns the branded id (mirrors Convex's `db.normalizeId`).
366
- */
572
+ * Validate an untrusted `id` string against the structural shape of an id
573
+ * for `tableName`, returning the branded {@link Id} when it is well-formed
574
+ * and `null` otherwise. Pure structural validation — it never reads the
575
+ * database, so a structurally valid id for a row that doesn't exist still
576
+ * returns the branded id (mirrors Convex's `db.normalizeId`).
577
+ */
367
578
  normalizeId: <T extends string>(tableName: T, id: string) => Id<T> | null;
368
579
  query: (tableName: string) => TableReader;
369
580
  /**
370
- * Best-effort, read-only reader over Lunora's system tables
371
- * (`_scheduled_functions`, `_storage`). Eventually consistent and **not**
372
- * part of the transaction snapshot — see {@link SystemDatabaseReader}.
373
- */
581
+ * Best-effort, read-only reader over Lunora's system tables
582
+ * (`_scheduled_functions`, `_storage`). Eventually consistent and **not**
583
+ * part of the transaction snapshot — see {@link SystemDatabaseReader}.
584
+ */
374
585
  readonly system: SystemDatabaseReader;
375
586
  }
376
587
  /** Options for {@link TableReader.paginate} — Convex-compatible page request. */
@@ -378,14 +589,14 @@ interface PaginationOptions {
378
589
  /** Opaque cursor from the prior page's `continueCursor`; `null`/omitted starts at the first page. */
379
590
  cursor?: null | string;
380
591
  /**
381
- * Optional inclusive upper bound for reactive pagination. When supplied the
382
- * page covers the fixed half-open range `(cursor, endCursor]` (ignoring
383
- * `numItems`): every row strictly after `cursor` up to and including the
384
- * boundary row `endCursor` encodes. The page's `isDone` is `true` and its
385
- * `continueCursor` echoes `endCursor`, so the next page keeps starting where
386
- * this one ends even as rows are inserted/deleted inside the range. Omit (or
387
- * pass `null`) for the legacy "first `numItems` after `cursor`" behaviour.
388
- */
592
+ * Optional inclusive upper bound for reactive pagination. When supplied the
593
+ * page covers the fixed half-open range `(cursor, endCursor]` (ignoring
594
+ * `numItems`): every row strictly after `cursor` up to and including the
595
+ * boundary row `endCursor` encodes. The page's `isDone` is `true` and its
596
+ * `continueCursor` echoes `endCursor`, so the next page keeps starting where
597
+ * this one ends even as rows are inserted/deleted inside the range. Omit (or
598
+ * pass `null`) for the legacy "first `numItems` after `cursor`" behaviour.
599
+ */
389
600
  endCursor?: null | string;
390
601
  /** Maximum rows to return for this page. */
391
602
  numItems: number;
@@ -398,46 +609,55 @@ interface PaginationResult<T = Record<string, unknown>> {
398
609
  isDone: boolean;
399
610
  page: T[];
400
611
  /**
401
- * Reactive-pagination only: the midpoint cursor of a bounded
402
- * `(cursor, endCursor]` page, used by the client to split an over-grown page
403
- * into two adjacent ranges. Absent on legacy (open-ended) pages.
404
- */
612
+ * Reactive-pagination only: the midpoint cursor of a bounded
613
+ * `(cursor, endCursor]` page, used by the client to split an over-grown page
614
+ * into two adjacent ranges. Absent on legacy (open-ended) pages.
615
+ */
405
616
  splitCursor?: null | string;
406
617
  }
407
618
  /**
408
- * The fluent `ctx.db.query(table)` reader. Generic over the document type
409
- * `Row` so the generated `ctx.db` can bind it to `Doc&lt;table>` (the chain and
410
- * every terminal then resolve typed rows — no `as unknown as Doc&lt;...>` casts).
411
- * Defaults to the untyped `Record&lt;string, unknown>` shape for the base
412
- * (schema-agnostic) `@lunora/server` reader.
413
- */
619
+ * The fluent `ctx.db.query(table)` reader. Generic over the document type
620
+ * `Row` so the generated `ctx.db` can bind it to `Doc&lt;table>` (the chain and
621
+ * every terminal then resolve typed rows — no `as unknown as Doc&lt;...>` casts).
622
+ * Defaults to the untyped `Record&lt;string, unknown>` shape for the base
623
+ * (schema-agnostic) `@lunora/server` reader.
624
+ */
414
625
  interface TableReader<Row = Record<string, unknown>> {
415
626
  collect: () => Promise<Row[]>;
416
627
  filter: (predicate: (document: Row) => boolean) => TableReader<Row>;
417
628
  first: () => Promise<Row | null>;
418
629
  /**
419
- * Set the result order. Orders by the active `.withIndex()` (or by
420
- * `_creationTime` when none is staged), `"asc"` by default; `"desc"`
421
- * reverses it. Composes with `.withIndex()`, `.filter()`, and every
422
- * terminal (`collect`/`first`/`take`/`paginate`/`unique`). Mirrors Convex's
423
- * `.order("asc" | "desc")`.
424
- */
630
+ * Set the result order. Orders by the active `.withIndex()` (or by
631
+ * `_creationTime` when none is staged), `"asc"` by default; `"desc"`
632
+ * reverses it. Composes with `.withIndex()`, `.filter()`, and every
633
+ * terminal (`collect`/`first`/`take`/`paginate`/`unique`). Mirrors Convex's
634
+ * `.order("asc" | "desc")`.
635
+ */
425
636
  order: (direction: "asc" | "desc") => TableReader<Row>;
426
637
  paginate: (options: PaginationOptions) => Promise<PaginationResult<Row>>;
427
638
  take: (limit: number) => Promise<Row[]>;
428
639
  /**
429
- * Return the single matching document. Returns `null` when nothing matches
430
- * and throws when more than one row matches. Mirrors Convex's `.unique()`.
431
- */
640
+ * Return the single matching document. Returns `null` when nothing matches
641
+ * and throws when more than one row matches. Mirrors Convex's `.unique()`.
642
+ */
432
643
  unique: () => Promise<Row | null>;
644
+ /**
645
+ * Restrict the query to a declared `.geoIndex()`. The builder's
646
+ * `.near(point, radiusMeters)` returns rows within `radiusMeters` of `point`,
647
+ * ordered nearest-first; `.within(bbox)` returns rows inside the
648
+ * latitude/longitude bounding box. Both resolve as a geohash-prefix range
649
+ * scan over the index's companion followed by a Haversine refine. Pair with
650
+ * `.take(n)` to cap results (`.paginate()` is not supported on a geo query).
651
+ */
652
+ withGeoIndex: (indexName: string, build: (q: GeoFilterBuilder) => GeoFilterBuilder) => TableReader<Row>;
433
653
  withIndex: (indexName: string, range?: (q: IndexRangeBuilder) => IndexRangeBuilder) => TableReader<Row>;
434
654
  /**
435
- * Restrict the query to a declared `.searchIndex()`. The builder's
436
- * `.search(field, query)` runs a full-text match against the index's
437
- * searchable field; `.eq(field, value)` narrows by a declared filter
438
- * field. Results come back ordered by relevance — pair with `.take(n)`
439
- * (`.paginate()` is not supported on a search query).
440
- */
655
+ * Restrict the query to a declared `.searchIndex()`. The builder's
656
+ * `.search(field, query)` runs a full-text match against the index's
657
+ * searchable field; `.eq(field, value)` narrows by a declared filter
658
+ * field. Results come back ordered by relevance — pair with `.take(n)`
659
+ * (`.paginate()` is not supported on a search query).
660
+ */
441
661
  withSearchIndex: (indexName: string, search: (q: SearchFilterBuilder) => SearchFilterBuilder) => TableReader<Row>;
442
662
  }
443
663
  interface IndexRangeBuilder {
@@ -454,87 +674,201 @@ interface SearchFilterBuilder {
454
674
  /** Full-text match `query` against the index's searchable `field`. Call exactly once. */
455
675
  search: (field: string, query: string) => SearchFilterBuilder;
456
676
  }
677
+ /** A latitude/longitude point (WGS84 decimal degrees) accepted by geo queries. */
678
+ interface GeoPointInput {
679
+ lat: number;
680
+ lng: number;
681
+ }
682
+ /**
683
+ * An axis-aligned latitude/longitude bounding box: `sw` is the south-west
684
+ * (min lat, min lng) corner, `ne` the north-east (max lat, max lng) corner.
685
+ */
686
+ interface GeoBoundingBox {
687
+ ne: GeoPointInput;
688
+ sw: GeoPointInput;
689
+ }
690
+ /**
691
+ * Builder passed to {@link TableReader.withGeoIndex}. Call exactly one of
692
+ * `.near(...)` / `.within(...)` — the two are mutually exclusive proximity vs
693
+ * bounding-box modes.
694
+ */
695
+ interface GeoFilterBuilder {
696
+ /** Rows within `radiusMeters` of `point`, resolved nearest-first. Call exactly once. */
697
+ near: (point: GeoPointInput, radiusMeters: number) => GeoFilterBuilder;
698
+ /** Rows whose point falls inside the bounding `box`. Call exactly once. */
699
+ within: (box: GeoBoundingBox) => GeoFilterBuilder;
700
+ }
457
701
  /**
458
- * Options shared by the batch-write methods (`insertMany`/`deleteMany`/
459
- * `patchMany`) — a per-call payload cap. The default cap (500) rejects an
460
- * oversized call up front so an accidental O(n²) or a payload past the Durable
461
- * Object request limit fails loudly instead of degrading the mutation. Callers
462
- * with larger sets should chunk their own loop or raise `limit`.
463
- */
702
+ * Options shared by the batch-write methods (`insertMany`/`deleteMany`/
703
+ * `patchMany`) — a per-call payload cap. The default cap (500) rejects an
704
+ * oversized call up front so an accidental O(n²) or a payload past the Durable
705
+ * Object request limit fails loudly instead of degrading the mutation. Callers
706
+ * with larger sets should chunk their own loop or raise `limit`.
707
+ */
464
708
  interface BatchWriteOptions {
465
709
  /** Reject the call when the batch size exceeds this value (default 500). */
466
710
  limit?: number;
467
711
  }
712
+ /** Options accepted by {@link DatabaseWriter.insertMany} and the per-table facade. */
713
+ interface InsertManyOptions extends BatchWriteOptions {
714
+ /**
715
+ * When `true`, a UNIQUE-constraint breach for a row resolves to `null`
716
+ * instead of throwing — the rest of the batch is still inserted. Skipped rows
717
+ * keep their input-order slot with `null` in the returned array. Mirrors
718
+ * better-drizzle's `createMany({ skipDuplicates: true })`.
719
+ */
720
+ skipDuplicates?: boolean;
721
+ }
468
722
  interface DatabaseWriter extends DatabaseReader {
469
723
  delete: <T extends string>(id: Id<T>) => Promise<void>;
470
724
  /**
471
- * Delete many rows by id in one call. Each id is deleted through the full
472
- * single-row pipeline (triggers + per-row RLS). The returned `deleted` is the
473
- * number of ids **requested**, not the rows actually removed — an unknown or
474
- * duplicated id is a silent no-op.
475
- *
476
- * **Atomic within a mutation:** the DO wraps a mutation's dispatch in a
477
- * BEGIN/COMMIT span, so a mid-batch failure (a later RLS denial or handler
478
- * error) rolls back the whole mutation. (In an action there is no transaction
479
- * span, so the prior deletes persist; the in-memory test harness mirrors the span.)
480
- */
725
+ * Delete EVERY row in `tableName`, chunking internally until the table is
726
+ * empty the erasure primitive.
727
+ *
728
+ * Unlike `deleteWhere(tableName, {})` there is **no batch cap**: a
729
+ * `BATCH_LIMIT_EXCEEDED` at row 501 of an account deletion is a bug, not a
730
+ * safety rail. Rows still go through the single-row delete pipeline, so
731
+ * triggers, cascades, companions, CDC, and live subscriptions stay correct.
732
+ *
733
+ * On a `.softDelete()` table the default flips the marker column; pass
734
+ * `{ hard: true }` to remove the rows physically (what GDPR erasure means).
735
+ */
736
+ deleteAll: (tableName: string, options?: {
737
+ chunkSize?: number;
738
+ hard?: boolean;
739
+ }) => Promise<{
740
+ deleted: number;
741
+ }>;
742
+ /**
743
+ * Delete many rows by id in one call. Each id is deleted through the full
744
+ * single-row pipeline (triggers + per-row RLS). The returned `deleted` is the
745
+ * number of ids **requested**, not the rows actually removed — an unknown or
746
+ * duplicated id is a silent no-op.
747
+ *
748
+ * **Atomic within a mutation:** the DO wraps a mutation's dispatch in a
749
+ * BEGIN/COMMIT span, so a mid-batch failure (a later RLS denial or handler
750
+ * error) rolls back the whole mutation. (In an action there is no transaction
751
+ * span, so the prior deletes persist; the in-memory test harness mirrors the span.)
752
+ */
481
753
  deleteMany: <T extends string>(ids: ReadonlyArray<Id<T>>, options?: BatchWriteOptions) => Promise<{
482
754
  deleted: number;
483
755
  }>;
484
756
  /**
485
- * Insert a document, returning its server id.
486
- *
487
- * Pass `options.clientId` (a UUID) to key the row yourself for an
488
- * optimistic client that needs the persisted row to match the key it
489
- * already rendered. It's validated for shape and still subject to the
490
- * primary-key uniqueness constraint; omit it and the server mints the id.
491
- */
757
+ * Delete every row matching `where` in one call. Matching rows are resolved
758
+ * first, then each row is deleted through the single-row delete pipeline
759
+ * (triggers, companion sync, CDC, broadcast) so reactive subscriptions and
760
+ * search/aggregate companions stay correct.
761
+ *
762
+ * **Atomic within a mutation:** the DO wraps a mutation's dispatch in a
763
+ * BEGIN/COMMIT span, so a mid-batch failure rolls back the whole mutation.
764
+ */
765
+ deleteWhere: (tableName: string, where: Record<string, unknown>, options?: BatchWriteOptions) => Promise<{
766
+ deleted: number;
767
+ }>;
768
+ /**
769
+ * Insert a document, returning its server id.
770
+ *
771
+ * Pass `options.clientId` (a UUID) to key the row yourself — for an
772
+ * optimistic client that needs the persisted row to match the key it
773
+ * already rendered. It's validated for shape and still subject to the
774
+ * primary-key uniqueness constraint; omit it and the server mints the id.
775
+ */
492
776
  insert: <T extends string>(tableName: T, document: Record<string, unknown>, options?: {
493
777
  clientId?: string;
494
778
  }) => Promise<Id<T>>;
495
779
  /**
496
- * Insert many documents into one table in a single call, returning the
497
- * minted ids in input order. Equivalent to a per-row `insert()` loop — each
498
- * row gets defaults, validators, triggers, and a per-row RLS check — but the
499
- * caller pays one round-trip instead of N.
500
- *
501
- * **Atomic within a mutation:** the DO wraps a mutation's dispatch in a
502
- * BEGIN/COMMIT span, so a mid-batch failure (an invalid or RLS-denied row)
503
- * rolls back the whole mutation. (In an action there is no transaction span,
504
- * so the prior inserts persist; the in-memory test harness mirrors the span.)
505
- */
506
- insertMany: <T extends string>(tableName: T, documents: ReadonlyArray<Record<string, unknown>>, options?: BatchWriteOptions) => Promise<Id<T>[]>;
507
- /**
508
- * **Trusted** bulk insert: one multi-row `INSERT` that **skips per-row
509
- * `.check()` validators and before/after triggers** for throughput on data you
510
- * control (seed, migration, admin import). Defaults, ids, and every companion
511
- * (search/aggregate/rank/CDC + live subscriptions) are still applied, so reads
512
- * stay correct.
513
- *
514
- * It is **"unsafe" only in that it bypasses the validation/trigger pipeline** —
515
- * RLS is **not** bypassed: secure-by-default and the table's insert policy still
516
- * apply (the framework ships no RLS-bypassing writer). Pass `allowExplicitId` to
517
- * preserve a supplied `_id` (import). Use only for data you trust; prefer
518
- * `insertMany` for anything user-supplied.
519
- */
780
+ * Insert many documents into one table in a single call, returning the
781
+ * minted ids in input order. Equivalent to a per-row `insert()` loop — each
782
+ * row gets defaults, validators, triggers, and a per-row RLS check — but the
783
+ * caller pays one round-trip instead of N.
784
+ *
785
+ * Pass `{ skipDuplicates: true }` to turn UNIQUE-constraint breaches into
786
+ * `null` results for that row instead of failing the whole batch; the rest of
787
+ * the batch is still inserted and order is preserved.
788
+ *
789
+ * **Atomic within a mutation:** the DO wraps a mutation's dispatch in a
790
+ * BEGIN/COMMIT span, so a mid-batch failure (an invalid or RLS-denied row)
791
+ * rolls back the whole mutation. (In an action there is no transaction span,
792
+ * so the prior inserts persist; the in-memory test harness mirrors the span.)
793
+ */
794
+ insertMany: {
795
+ <T extends string>(tableName: T, documents: ReadonlyArray<Record<string, unknown>>, options: BatchWriteOptions & {
796
+ skipDuplicates: true;
797
+ }): Promise<(Id<T> | null)[]>;
798
+ <T extends string>(tableName: T, documents: ReadonlyArray<Record<string, unknown>>, options?: InsertManyOptions): Promise<Id<T>[]>;
799
+ };
800
+ /**
801
+ * **Trusted** bulk insert: one multi-row `INSERT` that **skips per-row
802
+ * `.check()` validators and before/after triggers** for throughput on data you
803
+ * control (seed, migration, admin import). Defaults, ids, and every companion
804
+ * (search/aggregate/rank/CDC + live subscriptions) are still applied, so reads
805
+ * stay correct.
806
+ *
807
+ * It is **"unsafe" only in that it bypasses the validation/trigger pipeline** —
808
+ * RLS is **not** bypassed: secure-by-default and the table's insert policy still
809
+ * apply (the framework ships no RLS-bypassing writer). Pass `allowExplicitId` to
810
+ * preserve a supplied `_id` (import). Use only for data you trust; prefer
811
+ * `insertMany` for anything user-supplied.
812
+ */
520
813
  insertManyUnsafe: <T extends string>(tableName: T, documents: ReadonlyArray<Record<string, unknown>>, options?: BatchWriteOptions & {
521
814
  allowExplicitId?: boolean;
522
815
  }) => Promise<Id<T>[]>;
523
816
  patch: <T extends string>(id: Id<T>, patch: Record<string, unknown>) => Promise<void>;
524
817
  /**
525
- * Patch many rows by id in one call. Each `{ id, patch }` is applied like a
526
- * single `patch()` (per-row triggers + RLS).
527
- *
528
- * **Atomic within a mutation:** the DO wraps a mutation's dispatch in a
529
- * BEGIN/COMMIT span, so a mid-batch failure rolls back the whole mutation.
530
- * (In an action there is no transaction span, so the prior patches persist;
531
- * the in-memory test harness mirrors the span.)
532
- */
818
+ * Patch many rows by id in one call. Each `{ id, patch }` is applied like a
819
+ * single `patch()` (per-row triggers + RLS). Returns the number of rows
820
+ * actually patched.
821
+ *
822
+ * **Atomic within a mutation:** the DO wraps a mutation's dispatch in a
823
+ * BEGIN/COMMIT span, so a mid-batch failure rolls back the whole mutation.
824
+ * (In an action there is no transaction span, so the prior patches persist;
825
+ * the in-memory test harness mirrors the span.)
826
+ */
533
827
  patchMany: <T extends string>(patches: ReadonlyArray<{
534
828
  id: Id<T>;
535
829
  patch: Record<string, unknown>;
536
- }>, options?: BatchWriteOptions) => Promise<void>;
830
+ }>, options?: BatchWriteOptions) => Promise<{
831
+ patched: number;
832
+ }>;
833
+ /**
834
+ * Patch every row matching `where` with the same `patch` in one call. The
835
+ * matching rows are resolved first, then each row is updated through the
836
+ * single-row patch pipeline (OCC, triggers, companion sync, CDC, broadcast)
837
+ * so reactive subscriptions and search/aggregate companions stay correct.
838
+ *
839
+ * **Atomic within a mutation:** the DO wraps a mutation's dispatch in a
840
+ * BEGIN/COMMIT span, so a mid-batch failure rolls back the whole mutation.
841
+ */
842
+ patchWhere: (tableName: string, args: {
843
+ patch: Record<string, unknown>;
844
+ where: Record<string, unknown>;
845
+ }, options?: BatchWriteOptions) => Promise<{
846
+ patched: number;
847
+ }>;
537
848
  replace: <T extends string>(id: Id<T>, document: Record<string, unknown>) => Promise<void>;
849
+ /**
850
+ * Erase every shard-local table — the account-deletion / tenant-teardown
851
+ * primitive. Sweeps the schema's non-`.global()` tables with
852
+ * {@link DatabaseWriter.deleteAll}`({ hard: true })` and returns the per-table
853
+ * counts.
854
+ *
855
+ * `.global()` tables are skipped by design: their rows live in D1 and are shared
856
+ * across shards, so "wipe this shard" must not reach them. Restrict the sweep
857
+ * with `options.tables`, or spare one with `options.exclude` (e.g. an audit log
858
+ * that must outlive the data).
859
+ *
860
+ * ```ts
861
+ * export const deleteAccount = internalMutation({ handler: async ({ ctx }) => ctx.db.wipeShard() });
862
+ * ```
863
+ */
864
+ wipeShard: (options?: {
865
+ chunkSize?: number;
866
+ exclude?: ReadonlyArray<string>;
867
+ tables?: ReadonlyArray<string>;
868
+ }) => Promise<{
869
+ deleted: number;
870
+ tables: Record<string, number>;
871
+ }>;
538
872
  }
539
873
  /** Authenticated identity surfaced into every context. */
540
874
  interface AuthState {
@@ -542,11 +876,11 @@ interface AuthState {
542
876
  readonly userId: string | null;
543
877
  }
544
878
  /**
545
- * A pending scheduled invocation as surfaced by {@link Scheduler.list} /
546
- * {@link Scheduler.get}. A clean public mirror of `@lunora/scheduler`'s internal
547
- * `ScheduleRecord` — re-declared here so the public ctx surface carries no
548
- * dependency on the scheduler package's internal types.
549
- */
879
+ * A pending scheduled invocation as surfaced by {@link Scheduler.list} /
880
+ * {@link Scheduler.get}. A clean public mirror of `@lunora/scheduler`'s internal
881
+ * `ScheduleRecord` — re-declared here so the public ctx surface carries no
882
+ * dependency on the scheduler package's internal types.
883
+ */
550
884
  interface ScheduledJob {
551
885
  args: Record<string, unknown>;
552
886
  /** Number of dispatch attempts already made (absent until the first retry). */
@@ -560,6 +894,21 @@ interface ScheduledJob {
560
894
  /** Routing hint forwarded so dispatch lands on the right shard. */
561
895
  shardKey?: string;
562
896
  }
897
+ /**
898
+ * A schedulable durable-workflow reference — the generated `workflows.&lt;name>` /
899
+ * `agents.&lt;name>` object, which carries its `WORKFLOW_*`/`AGENT_*` binding and
900
+ * stable name. Structural mirror of `@lunora/scheduler`'s `WorkflowReference` so
901
+ * `ctx.scheduler` can target a workflow/agent without a dependency on
902
+ * `@lunora/scheduler` / `@lunora/workflow`. A scheduled workflow target starts a
903
+ * fresh instance on fire (the args become its `params`).
904
+ */
905
+ interface SchedulableWorkflowReference {
906
+ /** The `WORKFLOW_*`/`AGENT_*` binding name (present on a generated ref). */
907
+ readonly binding?: string;
908
+ readonly isLunoraWorkflow: true;
909
+ /** The workflow/agent export/stable name (present on a generated ref). */
910
+ readonly name?: string;
911
+ }
563
912
  interface Scheduler {
564
913
  /** Cancel a pending job by id. `cancelled` is `false` when no such job exists. */
565
914
  cancel: (id: string) => Promise<{
@@ -569,16 +918,23 @@ interface Scheduler {
569
918
  get: (id: string) => Promise<ScheduledJob | null>;
570
919
  /** List all pending scheduled jobs. */
571
920
  list: () => Promise<ScheduledJob[]>;
572
- runAfter: (delayMs: number, functionPath: string, args?: Record<string, unknown>) => Promise<string>;
573
- runAt: (timestampMs: number, functionPath: string, args?: Record<string, unknown>) => Promise<string>;
921
+ /**
922
+ * Schedule a one-shot run `delayMs` from now. `target` is a function path
923
+ * (`"ns:fn"`) dispatched as a one-shot, or a generated `workflows.&lt;name>` /
924
+ * `agents.&lt;name>` reference which starts a fresh durable instance on fire
925
+ * (the args become its `params`).
926
+ */
927
+ runAfter: (delayMs: number, target: SchedulableWorkflowReference | string, args?: Record<string, unknown>) => Promise<string>;
928
+ /** Like {@link Scheduler.runAfter} but fires at an absolute epoch-ms timestamp. */
929
+ runAt: (timestampMs: number, target: SchedulableWorkflowReference | string, args?: Record<string, unknown>) => Promise<string>;
574
930
  }
575
931
  /**
576
- * A workflow instance's lifecycle status. Clean public mirror of
577
- * `@lunora/workflow`'s `WorkflowInstanceStatus` (itself a mirror of Cloudflare's
578
- * `WorkflowInstanceStatus`) — re-declared here so the ctx surface carries no
579
- * dependency on the workflow package, exactly as {@link Scheduler} avoids a
580
- * dependency on `@lunora/scheduler`.
581
- */
932
+ * A workflow instance's lifecycle status. Clean public mirror of
933
+ * `@lunora/workflow`'s `WorkflowInstanceStatus` (itself a mirror of Cloudflare's
934
+ * `WorkflowInstanceStatus`) — re-declared here so the ctx surface carries no
935
+ * dependency on the workflow package, exactly as {@link Scheduler} avoids a
936
+ * dependency on `@lunora/scheduler`.
937
+ */
582
938
  type WorkflowInstanceStatus = "complete" | "errored" | "paused" | "queued" | "running" | "terminated" | "unknown" | "waiting" | "waitingForPause";
583
939
  /** Result of {@link WorkflowInstance.status}. Mirrors `@lunora/workflow`'s `WorkflowStatusResult`. */
584
940
  interface WorkflowStatusResult {
@@ -615,9 +971,9 @@ interface WorkflowInstance {
615
971
  terminate: () => Promise<void>;
616
972
  }
617
973
  /**
618
- * A typed handle to one declared workflow, addressable from `ctx.workflows`.
619
- * Mirrors `@lunora/workflow`'s `WorkflowHandle`.
620
- */
974
+ * A typed handle to one declared workflow, addressable from `ctx.workflows`.
975
+ * Mirrors `@lunora/workflow`'s `WorkflowHandle`.
976
+ */
621
977
  interface WorkflowHandle<Params = Record<string, unknown>> {
622
978
  /** Start a new instance (optionally with an id + params). */
623
979
  create: (options?: WorkflowCreateOptions<Params>) => Promise<WorkflowInstance>;
@@ -627,25 +983,65 @@ interface WorkflowHandle<Params = Record<string, unknown>> {
627
983
  get: (id: string) => Promise<WorkflowInstance>;
628
984
  }
629
985
  /**
630
- * The `ctx.workflows` surface on {@link MutationCtx} / {@link ActionCtx}. Each
631
- * workflow declared in `lunora/workflows.ts` is reachable by its export name;
632
- * codegen narrows the `get(name)` overloads to the known workflow names + their
633
- * inferred param types. Mirrors `@lunora/workflow`'s `Workflows`.
634
- */
986
+ * The `ctx.workflows` surface on {@link MutationCtx} / {@link ActionCtx}. Each
987
+ * workflow declared in `lunora/workflows.ts` is reachable by its export name;
988
+ * codegen narrows the `get(name)` overloads to the known workflow names + their
989
+ * inferred param types. Mirrors `@lunora/workflow`'s `Workflows`.
990
+ */
635
991
  interface Workflows {
636
992
  /** Resolve the handle for a declared workflow by export name. */
637
993
  get: <Params = Record<string, unknown>>(name: string) => WorkflowHandle<Params>;
638
994
  }
995
+ /**
996
+ * Programmatic cache purge surface exposed on {@link ActionCtx}. Actions run
997
+ * in the Worker (not the DO), so they can reach the Worker's `ctx.cache.purge`.
998
+ * Queries and mutations do not expose this — they run inside the Durable Object.
999
+ */
1000
+ interface CachePurge {
1001
+ /**
1002
+ * Purge cached responses matching the given tags, or everything when
1003
+ * `purgeEverything` is true. Only available in action handlers.
1004
+ */
1005
+ purge: (options: {
1006
+ purgeEverything?: boolean;
1007
+ tags?: string[];
1008
+ }) => Promise<unknown>;
1009
+ }
1010
+ /**
1011
+ * Structural projection of workers-types' `SecretsStoreSecret` binding — the
1012
+ * per-secret `secrets_store_secrets[]` binding whose `.get()` resolves the
1013
+ * secret value (or throws if it does not exist). Mirrored structurally so the
1014
+ * runtime resolves it without a workerd type dependency.
1015
+ */
1016
+ interface SecretsStoreSecretLike {
1017
+ get: () => Promise<string>;
1018
+ }
1019
+ /**
1020
+ * `ctx.secrets` — read account-level secrets bound via Cloudflare Secrets Store.
1021
+ * A core built-in (always present on every context, like `ctx.log`): a binding
1022
+ * named in wrangler's `secrets_store_secrets[]` is read by its binding name.
1023
+ *
1024
+ * ```ts
1025
+ * const apiKey = await ctx.secrets.get("STRIPE_KEY");
1026
+ * ```
1027
+ *
1028
+ * The lookup is async (the platform fetches and decrypts on first read);
1029
+ * reading an undeclared name throws a directed error naming the bound secrets.
1030
+ */
1031
+ interface Secrets {
1032
+ /** Resolve a Secrets Store secret by its wrangler binding name. */
1033
+ get: (name: string) => Promise<string>;
1034
+ }
639
1035
  /** Lifecycle phase relative to the SQL write. */
640
1036
  type TriggerTiming = "after" | "before";
641
1037
  /** The CRUD operation a trigger reacts to. `patch` and `replace` both map to `update`. */
642
1038
  type TriggerOp = "delete" | "insert" | "update";
643
1039
  /**
644
- * A row as observed by a trigger handler: the table's `Shape` (with the same
645
- * optionality rules as {@link InferArgs}) plus the system columns every stored
646
- * doc carries.
647
- */
648
- type TriggerRow<Shape extends Record<string, Validator>> = { [K in keyof Shape as undefined extends Infer<Shape[K]> ? K : never]?: Infer<Shape[K]> } & { [K in keyof Shape as undefined extends Infer<Shape[K]> ? never : K]: Infer<Shape[K]> } & {
1040
+ * A row as observed by a trigger handler: the table's `Shape` (with the same
1041
+ * optionality rules as {@link InferArgs}) plus the system columns every stored
1042
+ * doc carries.
1043
+ */
1044
+ type TriggerRow<Shape extends Record<string, Validator>> = { [K in keyof Shape as undefined extends Infer<Shape[K]> ? K : never]?: Infer<Shape[K]>; } & { [K in keyof Shape as undefined extends Infer<Shape[K]> ? never : K]: Infer<Shape[K]>; } & {
649
1045
  readonly _creationTime: number;
650
1046
  readonly _id: string;
651
1047
  };
@@ -657,11 +1053,11 @@ interface TriggerInsertEvent<Shape extends Record<string, Validator> = Record<st
657
1053
  readonly table: string;
658
1054
  }
659
1055
  /**
660
- * What an `update` trigger observes: the merged row plus the pre-write row.
661
- * `previous` is typed as always present (the row must exist to be updated); the
662
- * runtime supplies it best-effort and only omits it in the unreachable
663
- * row-vanished-mid-write case.
664
- */
1056
+ * What an `update` trigger observes: the merged row plus the pre-write row.
1057
+ * `previous` is typed as always present (the row must exist to be updated); the
1058
+ * runtime supplies it best-effort and only omits it in the unreachable
1059
+ * row-vanished-mid-write case.
1060
+ */
665
1061
  interface TriggerUpdateEvent<Shape extends Record<string, Validator> = Record<string, Validator>> {
666
1062
  readonly doc: TriggerRow<Shape>;
667
1063
  readonly id: string;
@@ -670,10 +1066,10 @@ interface TriggerUpdateEvent<Shape extends Record<string, Validator> = Record<st
670
1066
  readonly table: string;
671
1067
  }
672
1068
  /**
673
- * What a `delete` trigger observes: the row about to be (or just) removed.
674
- * `previous` is typed as always present; the runtime supplies it best-effort
675
- * and only omits it in the unreachable row-vanished-mid-write case.
676
- */
1069
+ * What a `delete` trigger observes: the row about to be (or just) removed.
1070
+ * `previous` is typed as always present; the runtime supplies it best-effort
1071
+ * and only omits it in the unreachable row-vanished-mid-write case.
1072
+ */
677
1073
  interface TriggerDeleteEvent<Shape extends Record<string, Validator> = Record<string, Validator>> {
678
1074
  readonly id: string;
679
1075
  readonly op: "delete";
@@ -697,10 +1093,10 @@ interface TriggerQueryArgs {
697
1093
  with?: Record<string, unknown>;
698
1094
  }
699
1095
  /**
700
- * Args accepted by {@link TriggerDatabase.aggregate} — structural mirror of
701
- * `@lunora/do`'s `AggregateOptions`, kept local so trigger handlers in
702
- * `@lunora/server` don't take a hard dep on the DO runtime.
703
- */
1096
+ * Args accepted by {@link TriggerDatabase.aggregate} — structural mirror of
1097
+ * `@lunora/do`'s `AggregateOptions`, kept local so trigger handlers in
1098
+ * `@lunora/server` don't take a hard dep on the DO runtime.
1099
+ */
704
1100
  interface TriggerAggregateOptions {
705
1101
  baseWhere?: Record<string, unknown>;
706
1102
  field?: string;
@@ -745,17 +1141,17 @@ interface TriggerRankPageOptions {
745
1141
  where?: Record<string, unknown>;
746
1142
  }
747
1143
  /**
748
- * Portable, table/id-addressed ORM writer handed to trigger handlers via
749
- * `ctx.db`. Mirrors `@lunora/do`'s runtime `DatabaseWriterLike` surface — it is
750
- * **not** the generated per-table `ctx.db.&lt;table>` facade (which can't be typed
751
- * from inside `defineTable`, where the full schema isn't known).
752
- *
753
- * `aggregate`/`groupBy`/`count`/`rank`/`rankPage` route through the same
754
- * trigger-maintained counter and rank tables the user-facing reader uses, so
755
- * a handler's `ctx.db.&lt;table>.aggregate(...)` observes the just-staged write
756
- * within the same DO transaction (the counter step happens before the trigger
757
- * fires).
758
- */
1144
+ * Portable, table/id-addressed ORM writer handed to trigger handlers via
1145
+ * `ctx.db`. Mirrors `@lunora/do`'s runtime `DatabaseWriterLike` surface — it is
1146
+ * **not** the generated per-table `ctx.db.&lt;table>` facade (which can't be typed
1147
+ * from inside `defineTable`, where the full schema isn't known).
1148
+ *
1149
+ * `aggregate`/`groupBy`/`count`/`rank`/`rankPage` route through the same
1150
+ * trigger-maintained counter and rank tables the user-facing reader uses, so
1151
+ * a handler's `ctx.db.&lt;table>.aggregate(...)` observes the just-staged write
1152
+ * within the same DO transaction (the counter step happens before the trigger
1153
+ * fires).
1154
+ */
759
1155
  interface TriggerDatabase {
760
1156
  aggregate: (tableName: string, options: TriggerAggregateOptions) => Promise<null | number>;
761
1157
  count: (tableName: string, where?: Record<string, unknown>) => Promise<number>;
@@ -771,10 +1167,10 @@ interface TriggerDatabase {
771
1167
  replace: (id: string, document: Record<string, unknown>) => Promise<void>;
772
1168
  }
773
1169
  /**
774
- * Handle injected into every trigger handler. `db` is the portable ORM writer;
775
- * `scheduler` enqueues async / cross-shard follow-up work (cross-shard work is
776
- * **not** transactional with the firing write).
777
- */
1170
+ * Handle injected into every trigger handler. `db` is the portable ORM writer;
1171
+ * `scheduler` enqueues async / cross-shard follow-up work (cross-shard work is
1172
+ * **not** transactional with the firing write).
1173
+ */
778
1174
  interface TriggerCtx {
779
1175
  readonly db: TriggerDatabase;
780
1176
  readonly scheduler: Scheduler;
@@ -782,20 +1178,20 @@ interface TriggerCtx {
782
1178
  /** A user-declared trigger handler. Throwing from a `before*` handler aborts the write. */
783
1179
  type TriggerHandler<Event> = (context: TriggerCtx, event: Event) => Promise<void> | void;
784
1180
  /**
785
- * A single declared trigger, as stored in {@link TableDefinition.triggerMap}.
786
- * The handler's event type is erased to the {@link TriggerEvent} union here; the
787
- * per-op {@link TriggerBuilder} methods recover the precise event type for
788
- * authors.
789
- */
1181
+ * A single declared trigger, as stored in {@link TableDefinition.triggerMap}.
1182
+ * The handler's event type is erased to the {@link TriggerEvent} union here; the
1183
+ * per-op {@link TriggerBuilder} methods recover the precise event type for
1184
+ * authors.
1185
+ */
790
1186
  interface TriggerDefinition {
791
1187
  readonly handler: TriggerHandler<TriggerEvent>;
792
1188
  readonly op: TriggerOp;
793
1189
  readonly timing: TriggerTiming;
794
1190
  }
795
1191
  /**
796
- * The `t` argument passed to `.triggers((t) => …)`. Each method binds a handler
797
- * to one `timing`+`op` pair, typing the event against the table's `Shape`.
798
- */
1192
+ * The `t` argument passed to `.triggers((t) => …)`. Each method binds a handler
1193
+ * to one `timing`+`op` pair, typing the event against the table's `Shape`.
1194
+ */
799
1195
  interface TriggerBuilder<Shape extends Record<string, Validator> = Record<string, Validator>> {
800
1196
  afterDelete: (handler: TriggerHandler<TriggerDeleteEvent<Shape>>) => TriggerDefinition;
801
1197
  afterInsert: (handler: TriggerHandler<TriggerInsertEvent<Shape>>) => TriggerDefinition;
@@ -805,11 +1201,11 @@ interface TriggerBuilder<Shape extends Record<string, Validator> = Record<string
805
1201
  beforeUpdate: (handler: TriggerHandler<TriggerUpdateEvent<Shape>>) => TriggerDefinition;
806
1202
  }
807
1203
  /**
808
- * Per-file metadata returned by {@link ReadOnlyStorage.getMetadata}. A clean
809
- * public mirror of `@lunora/storage`'s `ObjectMetadata` — re-declared here so
810
- * the ctx surface carries no dependency on the storage package's types. Matches
811
- * the columns Convex surfaces for `ctx.storage.getMetadata` / `_storage`.
812
- */
1204
+ * Per-file metadata returned by {@link ReadOnlyStorage.getMetadata}. A clean
1205
+ * public mirror of `@lunora/storage`'s `ObjectMetadata` — re-declared here so
1206
+ * the ctx surface carries no dependency on the storage package's types. Matches
1207
+ * the columns Convex surfaces for `ctx.storage.getMetadata` / `_storage`.
1208
+ */
813
1209
  interface StorageMetadata {
814
1210
  /** The object's `Content-Type`, when recorded. */
815
1211
  contentType?: string;
@@ -825,31 +1221,31 @@ interface StorageMetadata {
825
1221
  uploaded?: number;
826
1222
  }
827
1223
  /**
828
- * Read-only projection of `Storage` exposed on `QueryCtx` / `MutationCtx`.
829
- *
830
- * Queries are pure reads, and mutations run inside a transactional scope —
831
- * neither is allowed to perform side-effectful R2 writes (`upload`) or
832
- * deletes (`delete`). They can, however, **read** existing objects and
833
- * resolve signed URLs (the URL signing itself is HMAC-only — no R2 round
834
- * trip), so the read-only surface keeps `download` and `getSignedUrl`. The
835
- * full {@link Storage} surface stays on `ActionCtx`.
836
- */
1224
+ * Read-only projection of `Storage` exposed on `QueryCtx` / `MutationCtx`.
1225
+ *
1226
+ * Queries are pure reads, and mutations run inside a transactional scope —
1227
+ * neither is allowed to perform side-effectful R2 writes (`upload`) or
1228
+ * deletes (`delete`). They can, however, **read** existing objects and
1229
+ * resolve signed URLs (the URL signing itself is HMAC-only — no R2 round
1230
+ * trip), so the read-only surface keeps `download` and `getSignedUrl`. The
1231
+ * full {@link Storage} surface stays on `ActionCtx`.
1232
+ */
837
1233
  interface ReadOnlyStorage<Buckets extends string = string> {
838
1234
  /**
839
- * Select a named bucket (declared via `v.storage("name")`). The returned
840
- * accessor's operations target that bucket — `ctx.storage.bucket("avatars")
841
- * .download(key)`. The bare `ctx.storage` targets the default bucket.
842
- */
1235
+ * Select a named bucket (declared via `v.storage("name")`). The returned
1236
+ * accessor's operations target that bucket — `ctx.storage.bucket("avatars")
1237
+ * .download(key)`. The bare `ctx.storage` targets the default bucket.
1238
+ */
843
1239
  bucket: (name: Buckets) => ReadOnlyStorage<Buckets>;
844
1240
  /** The bucket this accessor's operations target (the default for the bare `ctx.storage`). */
845
1241
  readonly bucketName: string;
846
1242
  /** Fetch the body of an existing object. Returns `null` when absent. */
847
1243
  download: (key: string) => Promise<ReadableStream | null>;
848
1244
  /**
849
- * Read a file's metadata (size, content-type, sha256, upload time, custom
850
- * metadata) without fetching its body. Returns `null` when the object is
851
- * absent. Mirrors Convex's `ctx.storage.getMetadata`.
852
- */
1245
+ * Read a file's metadata (size, content-type, sha256, upload time, custom
1246
+ * metadata) without fetching its body. Returns `null` when the object is
1247
+ * absent. Mirrors Convex's `ctx.storage.getMetadata`.
1248
+ */
853
1249
  getMetadata: (key: string) => Promise<StorageMetadata | null>;
854
1250
  /** Resolve a short-lived signed URL for an existing object. */
855
1251
  getSignedUrl: (key: string, options?: {
@@ -863,20 +1259,20 @@ interface Storage<Buckets extends string = string> extends ReadOnlyStorage<Bucke
863
1259
  bucket: (name: Buckets) => Storage<Buckets>;
864
1260
  delete: (key: string) => Promise<void>;
865
1261
  /**
866
- * Mint a short-lived signed `PUT` URL a client can upload directly to,
867
- * optionally pinning the `Content-Type` the uploader must send. Mirrors
868
- * Convex's `storage.generateUploadUrl`.
869
- */
1262
+ * Mint a short-lived signed `PUT` URL a client can upload directly to,
1263
+ * optionally pinning the `Content-Type` the uploader must send. Mirrors
1264
+ * Convex's `storage.generateUploadUrl`.
1265
+ */
870
1266
  generateUploadUrl: (key: string, options?: {
871
1267
  contentType?: string;
872
1268
  expiresInSeconds?: number;
873
1269
  }) => Promise<string>;
874
1270
  /**
875
- * Upload `body` to `key` from the server, returning the stored object's key
876
- * and etag. Mirrors Convex's `storage.store`. Accepts the same guard fields
877
- * as `@lunora/storage`'s `UploadOptions` so `maxSize` /
878
- * `allowedContentTypes` enforcement isn't lost behind the Convex-style alias.
879
- */
1271
+ * Upload `body` to `key` from the server, returning the stored object's key
1272
+ * and etag. Mirrors Convex's `storage.store`. Accepts the same guard fields
1273
+ * as `@lunora/storage`'s `UploadOptions` so `maxSize` /
1274
+ * `allowedContentTypes` enforcement isn't lost behind the Convex-style alias.
1275
+ */
880
1276
  store: (key: string, body: ReadableStream | ArrayBuffer | Blob, options?: {
881
1277
  allowedContentTypes?: ReadonlyArray<string>;
882
1278
  contentType?: string;
@@ -920,154 +1316,481 @@ interface VectorRecord {
920
1316
  values: ReadonlyArray<number>;
921
1317
  }
922
1318
  /**
923
- * Read-only vector surface exposed on {@link QueryCtx}. Mirrors the read half
924
- * of `@lunora/vectors`' `LunoraVectors` so the live adapter is assignable.
925
- */
1319
+ * Read-only vector surface exposed on {@link QueryCtx}. Mirrors the read half
1320
+ * of `@lunora/bindings/vectors`' `LunoraVectors` so the live adapter is assignable.
1321
+ */
926
1322
  interface VectorSearchReader {
927
1323
  getByIds: (indexName: string, ids: ReadonlyArray<string>) => Promise<ReadonlyArray<VectorRecord>>;
928
1324
  query: (indexName: string, input: VectorQueryInput) => Promise<VectorMatches>;
929
1325
  }
930
1326
  /**
931
- * Mutating vector surface on {@link MutationCtx} / {@link ActionCtx}. `upsert`
932
- * is queued post-commit by default; `upsertNow` forces a synchronous write.
933
- * `db.delete` on a vectorized table auto-propagates the matching `deleteByIds`.
934
- */
1327
+ * Mutating vector surface on {@link MutationCtx} / {@link ActionCtx}. `upsert`
1328
+ * is queued post-commit by default; `upsertNow` forces a synchronous write.
1329
+ * `db.delete` on a vectorized table auto-propagates the matching `deleteByIds`.
1330
+ */
935
1331
  interface VectorSearch extends VectorSearchReader {
936
1332
  deleteByIds: (indexName: string, ids: ReadonlyArray<string>) => Promise<void>;
937
1333
  upsert: (indexName: string, input: VectorUpsertInput) => Promise<void>;
938
1334
  upsertNow: (indexName: string, input: VectorUpsertInput) => Promise<void>;
939
1335
  }
940
1336
  /**
941
- * Structured logger on every function `ctx`. Each call emits one attributed log
942
- * line tagged with the function path on the server that flows to an
943
- * `ObservabilitySink`'s `onLog` (where you route it in production) and, in
944
- * development, to the dev server terminal via the CLI / Vite plugin formatter.
945
- * Mirrors the `console` method names so it's a drop-in for `console.log` inside a
946
- * handler, but with attribution and a routable transport.
947
- *
948
- * Accepts any number of values per call, exactly like `console`; objects are
949
- * rendered into the human-readable message. The raw, un-rendered arguments are
950
- * preserved ONLY on the in-process `onLog` sink (which you opt into and control);
951
- * the rendered message — not the structured args — is what reaches the dev
952
- * terminal and the platform's Workers Logs.
953
- *
954
- * Attribution follows the dispatched function: a log emitted inside an internal
955
- * function invoked via `ctx.runQuery`/`runMutation`/`runAction` is attributed to
956
- * the outer request entrypoint, since the composed call reuses its context.
957
- */
1337
+ * Structured, filterable key/value fields attached to a log line the second
1338
+ * argument of a `ctx.log.&lt;level>(message, fields)` call, or the fields bound by
1339
+ * `ctx.log.with(fields)`. They travel to an `ObservabilitySink`'s `onLog` and,
1340
+ * for a network sink, become OTLP log-record attributes a log pipeline (or the
1341
+ * Cloud log viewer) can filter and index on. Primitive values pass through;
1342
+ * objects/arrays are JSON-encoded at the sink boundary.
1343
+ */
1344
+ type LogFields = Record<string, unknown>;
1345
+ /**
1346
+ * One `ctx.log` severity method. Two call forms:
1347
+ *
1348
+ * - **Structured** `ctx.log.info("order placed", { orderId, total })`: a message string plus a `fields` object. The fields are indexed as attributes.
1349
+ * - **Console-style** — `ctx.log.info("state", value, other)`: any number of values, joined into the display message exactly like `console.log`.
1350
+ *
1351
+ * The structured form is matched when the second argument is a plain object;
1352
+ * otherwise the call is treated as console-style, so existing `console`-shaped
1353
+ * calls keep working unchanged.
1354
+ */
1355
+ interface LunoraLogMethod {
1356
+ (message: string, fields?: LogFields): void;
1357
+ (...args: unknown[]): void;
1358
+ }
1359
+ /**
1360
+ * Structured logger on every function `ctx`. Each call emits one attributed log
1361
+ * line — tagged with the function path on the server — that flows to an
1362
+ * `ObservabilitySink`'s `onLog` (where you route it in production) and, in
1363
+ * development, to the dev server terminal via the CLI / Vite plugin formatter.
1364
+ * Mirrors the `console` method names so it's a drop-in for `console.log` inside a
1365
+ * handler, but with attribution, structured fields, and a routable transport.
1366
+ *
1367
+ * Six severities spanning the OpenTelemetry ramp: `trace`, `debug`, `info` (and
1368
+ * its `log` alias), `warn`, `error`, `fatal`.
1369
+ *
1370
+ * Two ways to attach structured {@link LogFields}: pass them per call
1371
+ * (`ctx.log.info(message, fields)`) or bind them once with {@link with} for a
1372
+ * child logger that stamps every line. The rendered `message` and the structured
1373
+ * `fields` reach the dev terminal and the platform's Workers Logs; the raw,
1374
+ * un-rendered console-style arguments are preserved ONLY on the in-process
1375
+ * `onLog` sink (which you opt into and control).
1376
+ *
1377
+ * Attribution follows the dispatched function: a log emitted inside an internal
1378
+ * function invoked via `ctx.runQuery`/`runMutation`/`runAction` is attributed to
1379
+ * the outer request entrypoint, since the composed call reuses its context.
1380
+ */
958
1381
  interface LunoraLogger {
959
- readonly debug: (...args: unknown[]) => void;
960
- readonly error: (...args: unknown[]) => void;
961
- readonly info: (...args: unknown[]) => void;
962
- readonly log: (...args: unknown[]) => void;
963
- readonly warn: (...args: unknown[]) => void;
1382
+ readonly debug: LunoraLogMethod;
1383
+ readonly error: LunoraLogMethod;
1384
+ /**
1385
+ * Emit a **structured event** instead of a log line OpenTelemetry's Events
1386
+ * API, on the wire as `LogRecord.eventName` (plus an `event.name` attribute
1387
+ * for collectors predating that field).
1388
+ *
1389
+ * ```ts
1390
+ * ctx.log.event("checkout.completed", { plan: user.plan, total, currency });
1391
+ * ```
1392
+ *
1393
+ * The difference from `ctx.log.info("checkout completed", { … })` is what a
1394
+ * backend can do with it. A log line's payload is its message: prose, written
1395
+ * for a human, free to be reworded next sprint — so "how many checkouts
1396
+ * completed, by plan, this hour" degrades into a substring search over
1397
+ * English. An event's payload is its `fields` under a **stable name**, which a
1398
+ * collector can index, group, and alert on directly.
1399
+ *
1400
+ * Rule of thumb: `log.*` for narration you'd read while debugging, `event` for
1401
+ * anything you'd ever put on a dashboard. And for facts about the request as a
1402
+ * whole, prefer `ctx.span` — one wide event beats a dozen
1403
+ * events, however well named.
1404
+ */
1405
+ readonly event: (name: string, fields?: LogFields) => void;
1406
+ readonly fatal: LunoraLogMethod;
1407
+ readonly info: LunoraLogMethod;
1408
+ readonly log: LunoraLogMethod;
1409
+ readonly trace: LunoraLogMethod;
1410
+ readonly warn: LunoraLogMethod;
1411
+ /**
1412
+ * Return a child logger that stamps `fields` onto every line it emits,
1413
+ * merged under any per-call fields (per-call wins on a key clash). Chainable
1414
+ * — `ctx.log.with({ requestId }).with({ step })` accumulates both. Use it to
1415
+ * bind request-scoped context once instead of repeating it per call.
1416
+ */
1417
+ readonly with: (fields: LogFields) => LunoraLogger;
1418
+ }
1419
+ /**
1420
+ * Handle the enclosing `ctx.trace` span hands its body, so the body can attach
1421
+ * attributes only known *after* it resolves (an AI call's token usage / dollar
1422
+ * cost, a downstream status, a computed count). Declared structurally here to
1423
+ * mirror `shared/span-event.ts`'s `SpanHandle` and `@lunora/do`'s implementation;
1424
+ * a cross-package assignability guard in `@lunora/testing` fails the build if the
1425
+ * three drift apart. Start attributes are snapshotted before the body runs;
1426
+ * handle writes are merged over them at record time, post-hoc winning on a clash.
1427
+ */
1428
+ interface SpanHandle {
1429
+ /**
1430
+ * Record a timestamped event on the enclosing span — a retry, a cache miss, a
1431
+ * state transition. Prefer this over an extra `ctx.log` line for anything that
1432
+ * only makes sense *relative to this span*: it rides the span's own export, so
1433
+ * it costs no additional record and can never be separated from its context.
1434
+ */
1435
+ addEvent: (name: string, attributes?: LogFields) => void;
1436
+ /**
1437
+ * Link this span to one in another trace — how a queue consumer points back at
1438
+ * the request that enqueued its message without collapsing every producer into
1439
+ * one giant trace.
1440
+ */
1441
+ addLink: (link: SpanLink) => void;
1442
+ /**
1443
+ * Record a **handled** exception as the OTel-conventional `exception` span
1444
+ * event (`exception.type` / `exception.message` / `exception.stacktrace`),
1445
+ * without marking the span failed.
1446
+ *
1447
+ * For an error you swallowed — a retried request, a fallback that worked. An
1448
+ * error that escapes the span body is recorded automatically and *does* set
1449
+ * the error status, so don't call this for one you're re-throwing.
1450
+ */
1451
+ recordException: (error: unknown) => void;
1452
+ /** Set one attribute on the enclosing span (merged at record time; post-hoc wins on key clash). */
1453
+ setAttribute: (key: string, value: LogFields[string]) => void;
1454
+ /** Merge attributes onto the enclosing span (post-hoc wins on key clash). */
1455
+ setAttributes: (fields: LogFields) => void;
1456
+ /**
1457
+ * The W3C ids of the span this handle refers to (32-hex trace, 16-hex span).
1458
+ *
1459
+ * On `ctx.span` these are the DISPATCH's ids — the trace the whole request
1460
+ * belongs to. Use it to echo a trace id back to a caller so a user can quote
1461
+ * it in a bug report, to build a `traceparent` for a hand-rolled outbound
1462
+ * call, or to parent a third-party library's spans onto this request.
1463
+ */
1464
+ spanContext: () => {
1465
+ spanId: string;
1466
+ traceId: string;
1467
+ };
1468
+ }
1469
+ /**
1470
+ * A causal reference to a span in another trace (OTel `Span.links`). Ids are
1471
+ * lowercase hex — 32 chars for `traceId`, 16 for `spanId`.
1472
+ */
1473
+ interface SpanLink {
1474
+ /** Attributes describing the relationship, e.g. `{ "link.kind": "enqueued_by" }`. */
1475
+ attributes?: LogFields;
1476
+ spanId: string;
1477
+ traceId: string;
1478
+ }
1479
+ /** OTel `SpanKind`. Drives a collector's service map — see {@link SpanOptions.kind}. */
1480
+ type SpanKind = "client" | "consumer" | "internal" | "producer" | "server";
1481
+ /** Options accepted by `ctx.trace(name, fn, options)` beyond a plain attribute bag. */
1482
+ interface SpanOptions {
1483
+ /** Start attributes, snapshotted before the body runs. */
1484
+ attributes?: LogFields;
1485
+ /**
1486
+ * OTel `SpanKind`, default `"internal"`. Set `"client"` for a call OUT to
1487
+ * another service and `"producer"`/`"consumer"` for queue hops: a collector
1488
+ * builds its service map from this, so leaving everything `"internal"` yields
1489
+ * a trace with no topology.
1490
+ */
1491
+ kind?: SpanKind;
1492
+ /** Links to spans in other traces, known at span start. */
1493
+ links?: SpanLink[];
1494
+ }
1495
+ /**
1496
+ * Span factory on every function `ctx`. Wraps a sub-operation so it becomes its
1497
+ * own **span** nested under the dispatch's RPC span, giving a trace real shape:
1498
+ * without it a slow request is one opaque bar, with it you see which part was
1499
+ * slow.
1500
+ *
1501
+ * ```ts
1502
+ * const charge = await ctx.trace("stripe.charge", () => stripe.charges.create(…), { orderId });
1503
+ * ```
1504
+ *
1505
+ * **Nesting is explicit.** The body receives a tracer bound to its own span;
1506
+ * calling *that* is what makes a child:
1507
+ *
1508
+ * ```ts
1509
+ * await ctx.trace("fulfil", async (trace) => {
1510
+ * // Children of "fulfil" — including under Promise.all, where an ambient
1511
+ * // "currently open span" would mis-record these as nested inside each other.
1512
+ * await Promise.all([trace("reserve.stock", …), trace("email.receipt", …)]);
1513
+ * });
1514
+ * ```
1515
+ *
1516
+ * Calling `ctx.trace` again inside a body (rather than the passed tracer) is not
1517
+ * an error — that span is simply parented to the dispatch instead of to the
1518
+ * enclosing span, which is flatter but never wrong.
1519
+ *
1520
+ * Spans share the dispatch's trace id with its `ctx.log` lines and any container
1521
+ * the handler calls (the same `traceparent` is propagated), so one trace spans
1522
+ * worker, shard, and container.
1523
+ *
1524
+ * The span is recorded when the body settles, and the body's value is returned
1525
+ * unchanged. A throw is recorded as an error span and then **re-thrown** — this
1526
+ * is instrumentation, never flow control. Recording is best-effort: a failing
1527
+ * sink can't turn a working handler into a broken one.
1528
+ *
1529
+ * **Post-hoc attributes.** The body also receives a {@link SpanHandle} as its
1530
+ * second argument. The `attributes` passed here are stamped at span start (and
1531
+ * snapshotted, so a later mutation can't rewrite them); anything the body sets
1532
+ * through the handle — `span.setAttribute(k, v)` / `span.setAttributes({…})` — is
1533
+ * merged over that snapshot when the span is recorded, so a value known only once
1534
+ * the body has resolved (an AI call's token usage / dollar cost, a computed
1535
+ * count) still lands on the span. Post-hoc wins on a key clash. The handle is a
1536
+ * trailing parameter, so every existing `(trace) => …` body keeps working
1537
+ * unchanged.
1538
+ * @param name Span name, e.g. `"stripe.charge"`. Prefer a low-cardinality name
1539
+ * and put the varying part in `attributes` — a name built from an id makes every
1540
+ * span its own group in a collector.
1541
+ * @param fn The body to time, receiving a tracer bound to this span for any
1542
+ * nested spans and the enclosing span's {@link SpanHandle} for post-hoc
1543
+ * attributes. May be sync or async; the result is awaited.
1544
+ * @param attributes Either a plain attribute bag to stamp on the span at start
1545
+ * (normalized like a log line's `fields`), or a {@link SpanOptions} object when
1546
+ * you need `kind` or `links`. It is read as options only when *every* key is one
1547
+ * of `attributes`/`kind`/`links`; `{ attributes: { kind: "premium" } }` is the
1548
+ * explicit form if your own attributes happen to be named that.
1549
+ */
1550
+ type LunoraTracer = <T>(name: string, function_: (trace: LunoraTracer, span: SpanHandle) => Promise<T> | T, attributes?: LogFields | SpanOptions) => Promise<T>;
1551
+ /**
1552
+ * `ctx.span` — a handle onto **this request's own span**, and with it the
1553
+ * wide-event API.
1554
+ *
1555
+ * ```ts
1556
+ * export const checkout = mutation({ handler: async (ctx, args) => {
1557
+ * ctx.span.setAttributes({ "user.plan": user.plan, "cart.items": cart.length });
1558
+ * const payment = await charge(cart);
1559
+ * ctx.span.setAttributes({ "payment.provider": payment.provider, "payment.total": payment.total });
1560
+ * if (payment.retried) ctx.span.addEvent("payment.retried", { attempts: payment.attempts });
1561
+ * return payment;
1562
+ * }});
1563
+ * ```
1564
+ *
1565
+ * **Why this instead of more log lines.** The usual way to make a handler
1566
+ * observable is to sprinkle `ctx.log.info` through it, which costs one record per
1567
+ * call, scatters one request's facts across a dozen rows, and forces every
1568
+ * question to be answered by correlating them back together. The wide-event
1569
+ * pattern inverts that: accumulate the facts as you learn them, and emit **one**
1570
+ * richly-attributed record per unit of work. Cost is flat — one span per request
1571
+ * no matter how much you attach — and every question ("p99 checkout latency for
1572
+ * pro-plan users with >10 items") becomes a single filter over one table instead
1573
+ * of a join across log lines.
1574
+ *
1575
+ * **This is plain OpenTelemetry, not a Lunora convention.** The attributes land
1576
+ * on the span the dispatch already emits, and are additionally exported as an
1577
+ * OTel Event record named `lunora.dispatch`, correlated by `trace_id`/`span_id`.
1578
+ * Any OTLP backend groups and aggregates them with no special configuration.
1579
+ *
1580
+ * **`span` vs `trace`.** `ctx.trace(name, fn)` creates a NEW child span to time a
1581
+ * sub-operation; `ctx.span` attaches to the one that already exists for the
1582
+ * request. Use `trace` for "how long did this part take", `span` for "what was
1583
+ * true about this request". Inside a `ctx.trace` body, the handle passed as the
1584
+ * body's second argument is that child span's equivalent of this.
1585
+ *
1586
+ * Attributes are normalized exactly like `ctx.log` fields, and recording is
1587
+ * best-effort — a telemetry failure never breaks the handler.
1588
+ */
1589
+ type LunoraWideEvent = SpanHandle;
1590
+ /**
1591
+ * Application metrics on every function `ctx` — the third signal alongside
1592
+ * `ctx.log` and `ctx.trace`. Each call records one measurement that flows to an
1593
+ * `ObservabilitySink`'s `onMetric`, and from `otlpSink` to a collector's
1594
+ * `/v1/metrics`.
1595
+ *
1596
+ * ```ts
1597
+ * ctx.metrics.count("orders.placed", 1, { plan: user.plan });
1598
+ * ctx.metrics.record("checkout.latency_ms", Date.now() - started);
1599
+ * ctx.metrics.gauge("cart.items", cart.items.length);
1600
+ * ```
1601
+ *
1602
+ * Pick the instrument by the question you want to answer: `count` for "how many"
1603
+ * (summed over time), `gauge` for "how many right now" (replaces the last
1604
+ * reading), `record` for "what's the distribution" (percentiles, not just a
1605
+ * mean).
1606
+ *
1607
+ * `attributes` are the metric's dimensions. Keep them **low-cardinality** — an
1608
+ * attribute valued by user id or order id creates a distinct time series per id,
1609
+ * which is how a metrics backend gets expensive. Put identifiers on a log line or
1610
+ * a span instead.
1611
+ *
1612
+ * No pre-aggregation happens: one call is one exported measurement, with counter
1613
+ * deltas for the collector to sum. In a hot loop, sum locally and record once
1614
+ * rather than calling per iteration.
1615
+ */
1616
+ interface LunoraMetrics {
1617
+ /**
1618
+ * Add to a monotonic counter (default `1`) — requests served, retries,
1619
+ * bytes sent. The collector sums successive deltas.
1620
+ */
1621
+ readonly count: (name: string, value?: number, attributes?: LogFields) => void;
1622
+ /**
1623
+ * Report a point-in-time reading that replaces the previous one — queue
1624
+ * depth, cache size, connections open.
1625
+ */
1626
+ readonly gauge: (name: string, value: number, attributes?: LogFields) => void;
1627
+ /**
1628
+ * Observe one sample of a distribution — latency, payload size. Use this,
1629
+ * not a counter, when percentiles matter.
1630
+ */
1631
+ readonly record: (name: string, value: number, attributes?: LogFields) => void;
964
1632
  }
965
1633
  interface QueryCtx {
966
1634
  readonly auth: AuthState;
967
1635
  readonly db: DatabaseReader;
968
1636
  /**
969
- * The caller's IP for this request Cloudflare's trusted `CF-Connecting-IP`,
970
- * forwarded server-side (never read from a client header). `undefined` when
971
- * unknown: a live-subscription re-run, a server-initiated dispatch, or
972
- * non-Cloudflare hosting. A convenient rate-limit key for anonymous traffic.
973
- */
1637
+ * The validated, typed environment. Populated only when the project declares
1638
+ * a `defineEnv(...)` contract in `lunora/env.ts`; codegen then narrows this to
1639
+ * the validated `InferEnv` shape so `ctx.env.STRIPE_KEY` is parsed and
1640
+ * coercion-aware. Absent (optional) without a contract declare
1641
+ * `lunora/env.ts` to populate and type it.
1642
+ */
1643
+ readonly env?: Record<string, unknown>;
1644
+ /**
1645
+ * The caller's IP for this request — Cloudflare's trusted `CF-Connecting-IP`,
1646
+ * forwarded server-side (never read from a client header). `undefined` when
1647
+ * unknown: a live-subscription re-run, a server-initiated dispatch, or
1648
+ * non-Cloudflare hosting. A convenient rate-limit key for anonymous traffic.
1649
+ */
974
1650
  readonly ip?: string;
975
1651
  /** Structured, function-attributed logger; see {@link LunoraLogger}. */
976
1652
  readonly log: LunoraLogger;
1653
+ /** Application counters, gauges, and histograms; see {@link LunoraMetrics}. */
1654
+ readonly metrics: LunoraMetrics;
977
1655
  /**
978
- * Wall-clock time (epoch ms) the function began, captured once so the whole
979
- * handler sees a single stable value. Query/mutation handlers must be
980
- * deterministic — they may be re-run on OCC retry / subscription re-eval — so
981
- * read time through `ctx.now` instead of `Date.now()` (the latter is flagged
982
- * by the `nondeterministic_query_mutation` advisor). Actions may use `Date.now()`.
983
- */
1656
+ * Wall-clock time (epoch ms) the function began, captured once so the whole
1657
+ * handler sees a single stable value. Query/mutation handlers must be
1658
+ * deterministic — they may be re-run on OCC retry / subscription re-eval — so
1659
+ * read time through `ctx.now` instead of `Date.now()` (the latter is flagged
1660
+ * by the `nondeterministic_query_mutation` advisor). Actions may use `Date.now()`.
1661
+ */
984
1662
  readonly now: number;
985
1663
  /**
986
- * Compose a read-only subquery in-process, reusing this query's read
987
- * context (same transaction, same `db`). Executes the referenced query's
988
- * handler directly — no fresh DO RPC round-trip — so it observes the exact
989
- * same snapshot. A query may only call other queries; there is no
990
- * `runMutation` on a `QueryCtx` (writes are not allowed from a query).
991
- * Mirrors Convex's `ctx.runQuery`.
992
- */
1664
+ * Compose a read-only subquery in-process, reusing this query's read
1665
+ * context (same transaction, same `db`). Executes the referenced query's
1666
+ * handler directly — no fresh DO RPC round-trip — so it observes the exact
1667
+ * same snapshot. A query may only call other queries; there is no
1668
+ * `runMutation` on a `QueryCtx` (writes are not allowed from a query).
1669
+ * Mirrors Convex's `ctx.runQuery`.
1670
+ */
993
1671
  readonly runQuery: <A extends ArgsValidator, R>(reference: RegisteredQuery<A, R>, args: InferArgs<A>) => Promise<R>;
1672
+ /** Read account-level secrets from Cloudflare Secrets Store; see {@link Secrets}. */
1673
+ readonly secrets: Secrets;
1674
+ /** Attach facts to THIS request's span — the wide event; see {@link LunoraWideEvent}. */
1675
+ readonly span: LunoraWideEvent;
994
1676
  readonly storage: ReadOnlyStorage;
1677
+ /** Wrap a sub-operation in its own nested span; see {@link LunoraTracer}. */
1678
+ readonly trace: LunoraTracer;
995
1679
  readonly vectors: VectorSearchReader;
996
1680
  }
997
1681
  interface MutationCtx {
998
1682
  readonly auth: AuthState;
999
1683
  readonly db: DatabaseWriter;
1000
1684
  /**
1001
- * The caller's IP for this request Cloudflare's trusted `CF-Connecting-IP`,
1002
- * forwarded server-side (never read from a client header). `undefined` when
1003
- * unknown: a live-subscription re-run, a server-initiated dispatch, or
1004
- * non-Cloudflare hosting. A convenient rate-limit key for anonymous traffic.
1005
- */
1685
+ * The validated, typed environment. Populated only when the project declares
1686
+ * a `defineEnv(...)` contract in `lunora/env.ts`; codegen then narrows this to
1687
+ * the validated `InferEnv` shape so `ctx.env.STRIPE_KEY` is parsed and
1688
+ * coercion-aware. Absent (optional) without a contract declare
1689
+ * `lunora/env.ts` to populate and type it.
1690
+ */
1691
+ readonly env?: Record<string, unknown>;
1692
+ /**
1693
+ * The caller's IP for this request — Cloudflare's trusted `CF-Connecting-IP`,
1694
+ * forwarded server-side (never read from a client header). `undefined` when
1695
+ * unknown: a live-subscription re-run, a server-initiated dispatch, or
1696
+ * non-Cloudflare hosting. A convenient rate-limit key for anonymous traffic.
1697
+ */
1006
1698
  readonly ip?: string;
1007
1699
  /** Structured, function-attributed logger; see {@link LunoraLogger}. */
1008
1700
  readonly log: LunoraLogger;
1701
+ /** Application counters, gauges, and histograms; see {@link LunoraMetrics}. */
1702
+ readonly metrics: LunoraMetrics;
1009
1703
  /**
1010
- * Wall-clock time (epoch ms) the function began, captured once so the whole
1011
- * handler sees a single stable value. Mutation handlers must be deterministic
1012
- * — they may be re-run on OCC retry — so read time through `ctx.now` instead
1013
- * of `Date.now()` (the latter is flagged by the `nondeterministic_query_mutation`
1014
- * advisor). Actions may use `Date.now()`.
1015
- */
1704
+ * Wall-clock time (epoch ms) the function began, captured once so the whole
1705
+ * handler sees a single stable value. Mutation handlers must be deterministic
1706
+ * — they may be re-run on OCC retry — so read time through `ctx.now` instead
1707
+ * of `Date.now()` (the latter is flagged by the `nondeterministic_query_mutation`
1708
+ * advisor). Actions may use `Date.now()`.
1709
+ */
1016
1710
  readonly now: number;
1017
1711
  /**
1018
- * Compose a submutation in-process, reusing this mutation's `db` writer.
1019
- * Executes the referenced mutation's handler directly — no fresh DO RPC —
1020
- * so its writes apply through the same shard invocation as the enclosing
1021
- * mutation. Note: writes are not wrapped in a SQL transaction, so a partial
1022
- * failure does not roll back earlier writes (the same as a top-level
1023
- * mutation). Mirrors Convex's `ctx.runMutation`.
1024
- */
1712
+ * Compose a submutation in-process, reusing this mutation's `db` writer.
1713
+ * Executes the referenced mutation's handler directly — no fresh DO RPC —
1714
+ * so its writes apply through the same shard invocation as the enclosing
1715
+ * mutation. Note: writes are not wrapped in a SQL transaction, so a partial
1716
+ * failure does not roll back earlier writes (the same as a top-level
1717
+ * mutation). Mirrors Convex's `ctx.runMutation`.
1718
+ */
1025
1719
  readonly runMutation: <A extends ArgsValidator, R>(reference: RegisteredMutation<A, R>, args: InferArgs<A>) => Promise<R>;
1026
1720
  /**
1027
- * Compose a read-only subquery in-process, reusing this mutation's `db`.
1028
- * Executes the referenced query's handler directly — no fresh DO RPC — so
1029
- * it observes this mutation's in-flight writes. Mirrors Convex's
1030
- * `ctx.runQuery`.
1031
- */
1721
+ * Compose a read-only subquery in-process, reusing this mutation's `db`.
1722
+ * Executes the referenced query's handler directly — no fresh DO RPC — so
1723
+ * it observes this mutation's in-flight writes. Mirrors Convex's
1724
+ * `ctx.runQuery`.
1725
+ */
1032
1726
  readonly runQuery: <A extends ArgsValidator, R>(reference: RegisteredQuery<A, R>, args: InferArgs<A>) => Promise<R>;
1033
1727
  readonly scheduler: Scheduler;
1728
+ /** Read account-level secrets from Cloudflare Secrets Store; see {@link Secrets}. */
1729
+ readonly secrets: Secrets;
1730
+ /** Attach facts to THIS request's span — the wide event; see {@link LunoraWideEvent}. */
1731
+ readonly span: LunoraWideEvent;
1034
1732
  readonly storage: ReadOnlyStorage;
1733
+ /** Wrap a sub-operation in its own nested span; see {@link LunoraTracer}. */
1734
+ readonly trace: LunoraTracer;
1035
1735
  readonly vectors: VectorSearch;
1036
1736
  /** Start / resume / inspect durable workflows; see {@link Workflows}. */
1037
1737
  readonly workflows: Workflows;
1038
1738
  }
1039
1739
  interface ActionCtx {
1040
1740
  readonly auth: AuthState;
1741
+ /**
1742
+ * Programmatic Workers Cache purge; see {@link CachePurge}.
1743
+ * **Action-only** — actions run in the Worker, which has a `cache` binding.
1744
+ * Queries and mutations run inside the Durable Object and do not expose this.
1745
+ * Optional at runtime because Workers Cache is only present when enabled.
1746
+ */
1747
+ readonly cache?: CachePurge;
1041
1748
  readonly db: DatabaseWriter;
1749
+ /**
1750
+ * The validated, typed environment. Populated only when the project declares
1751
+ * a `defineEnv(...)` contract in `lunora/env.ts`; codegen then narrows this to
1752
+ * the validated `InferEnv` shape so `ctx.env.STRIPE_KEY` is parsed and
1753
+ * coercion-aware. Absent (optional) without a contract — declare
1754
+ * `lunora/env.ts` to populate and type it.
1755
+ */
1756
+ readonly env?: Record<string, unknown>;
1042
1757
  readonly fetch: typeof globalThis.fetch;
1043
1758
  /**
1044
- * The caller's IP for this request — Cloudflare's trusted `CF-Connecting-IP`,
1045
- * forwarded server-side (never read from a client header). `undefined` when
1046
- * unknown: a live-subscription re-run, a server-initiated dispatch, or
1047
- * non-Cloudflare hosting. A convenient rate-limit key for anonymous traffic.
1048
- */
1759
+ * The caller's IP for this request — Cloudflare's trusted `CF-Connecting-IP`,
1760
+ * forwarded server-side (never read from a client header). `undefined` when
1761
+ * unknown: a live-subscription re-run, a server-initiated dispatch, or
1762
+ * non-Cloudflare hosting. A convenient rate-limit key for anonymous traffic.
1763
+ */
1049
1764
  readonly ip?: string;
1050
1765
  /** Structured, function-attributed logger; see {@link LunoraLogger}. */
1051
1766
  readonly log: LunoraLogger;
1767
+ /** Application counters, gauges, and histograms; see {@link LunoraMetrics}. */
1768
+ readonly metrics: LunoraMetrics;
1052
1769
  /**
1053
- * Wall-clock time (epoch ms) the action began, captured once for convenience
1054
- * and parity with query/mutation `ctx.now`. Actions run exactly once, so they
1055
- * may also use ambient `Date.now()` freely.
1056
- */
1770
+ * Wall-clock time (epoch ms) the action began, captured once for convenience
1771
+ * and parity with query/mutation `ctx.now`. Actions run exactly once, so they
1772
+ * may also use ambient `Date.now()` freely.
1773
+ */
1057
1774
  readonly now: number;
1058
1775
  readonly runAction: <A extends ArgsValidator, R>(reference: RegisteredAction<A, R>, args: InferArgs<A>) => Promise<R>;
1059
1776
  readonly runMutation: <A extends ArgsValidator, R>(reference: RegisteredMutation<A, R>, args: InferArgs<A>) => Promise<R>;
1060
1777
  readonly runQuery: <A extends ArgsValidator, R>(reference: RegisteredQuery<A, R>, args: InferArgs<A>) => Promise<R>;
1061
1778
  readonly scheduler: Scheduler;
1779
+ /** Read account-level secrets from Cloudflare Secrets Store; see {@link Secrets}. */
1780
+ readonly secrets: Secrets;
1781
+ /** Attach facts to THIS request's span — the wide event; see {@link LunoraWideEvent}. */
1782
+ readonly span: LunoraWideEvent;
1062
1783
  readonly storage: Storage;
1784
+ /** Wrap a sub-operation in its own nested span; see {@link LunoraTracer}. */
1785
+ readonly trace: LunoraTracer;
1063
1786
  readonly vectors: VectorSearch;
1064
1787
  /** Start / resume / inspect durable workflows; see {@link Workflows}. */
1065
1788
  readonly workflows: Workflows;
1066
1789
  }
1067
1790
  /**
1068
- * Stand-in returned by codegen so projects can `import { api } from "./_generated/api"`.
1069
- * The runtime value is opaque; the types are filled in by generated declarations.
1070
- */
1791
+ * Stand-in returned by codegen so projects can `import { api } from "./_generated/api"`.
1792
+ * The runtime value is opaque; the types are filled in by generated declarations.
1793
+ */
1071
1794
  type AnyApi = Record<string, Record<string, RegisteredFunction<ArgsValidator, unknown, FunctionKind>>>;
1072
1795
  declare const anyApi: AnyApi;
1073
- export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type DatabaseReader, type DatabaseWriter, type DurableObjectJurisdiction, type FunctionKind, type FunctionVisibility, type GlobalBackend, type IndexDefinition, type IndexRangeBuilder, type InferArgs, type LifecycleEvent, type LifecycleEventKind, type LunoraLogger, type MutationCtx, type OnDeleteAction, type PaginationOptions, type PaginationResult, type QueryCtx, type RankIndexDefinition, type RankSortKey, type ReadOnlyStorage, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationDefinition, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type Schema, type SearchFilterBuilder, type SearchIndexDefinition, type ShardMode, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableDefinition, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerBuilder, type TriggerCtx, type TriggerDatabase, type TriggerDefinition, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type VectorEmbedder, type VectorIndexDefinition, type VectorMatch, type VectorMatches, type VectorMetric, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi };
1796
+ export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type CachePurge, type DatabaseReader, type DatabaseWriter, type DurableObjectJurisdiction, type ExposeConfig, type ExternalSourceCursor, type ExternalSourceDefinition, type ExternalSourceMode, type ExternalSourceRefresh, type FunctionKind, type FunctionVisibility, type GeoBoundingBox, type GeoFilterBuilder, type GeoIndexDefinition, type GeoPointInput, type GlobalBackend, type IndexDefinition, type IndexRangeBuilder, type InferArgs, type LifecycleEvent, type LifecycleEventKind, type LogFields, type LunoraLogMethod, type LunoraLogger, type LunoraMetrics, type LunoraTracer, type LunoraWideEvent, type MutationCtx, type OnDeleteAction, type PaginationOptions, type PaginationResult, type QueryCtx, type RankIndexDefinition, type RankSortKey, type ReadOnlyStorage, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationDefinition, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type Schema, type SearchFilterBuilder, type SearchIndexDefinition, type Secrets, type SecretsStoreSecretLike, type ShardMode, type SpanHandle, type SpanKind, type SpanLink, type SpanOptions, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableDefinition, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerBuilder, type TriggerCtx, type TriggerDatabase, type TriggerDefinition, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type TtlDefinition, type VectorEmbedder, type VectorIndexDefinition, type VectorMatch, type VectorMatches, type VectorMetric, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, type X402ProcedureConfig, anyApi };