@lunora/runtime 1.0.0-alpha.3 → 1.0.0-alpha.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,31 +1,33 @@
1
1
  import { RankDirection, RankPageRow, DatabaseWriterLike } from '@lunora/do';
2
2
  export type { RankDirection as RankPageDirection, RankPageRowKey as RankPageKey, RankPageRow, ShardRankPageResult } from '@lunora/do';
3
3
  import { WorkflowsRestClient } from '@lunora/workflow';
4
- /**
5
- * Turn-key incremental-sync source helpers for warehouse connectors
6
- * (Fivetran custom functions, Airbyte incremental sources).
7
- *
8
- * The runtime's admin `/_lunora/admin/connector/sync` endpoint returns a
9
- * {@link ConnectorSyncPage}: a flat list of change records since an opaque
10
- * cursor, a `nextCursor` to resume from, and a `hasMore` flag. These helpers
11
- * reshape that page into the response envelopes the two ecosystems expect, so a
12
- * connector wrapper stays a few lines.
13
- *
14
- * {@link toFivetranResponse} produces the `{ state, insert, update, delete,
15
- * hasMore, schema }` object a Fivetran connector function returns from its
16
- * handler. {@link toAirbyteMessages} produces an ordered array of Airbyte
17
- * protocol messages (a `RECORD` per row, a trailing `STATE` carrying the cursor),
18
- * the line-delimited stream an Airbyte incremental source emits.
19
- *
20
- * Both consume the SAME page, so a single endpoint feeds either ecosystem.
21
- */
22
- /**
23
- * One change record in a {@link ConnectorSyncPage}. Mirrors a row of the CDC log
24
- * the shard / D1 change feed produces: an `op` (insert / update / delete), the
25
- * owning `table`, and the document. `op` is normalised to the three warehouse
26
- * verbs; an unknown / absent op is treated as `"upsert"` (insert-or-update),
27
- * which is the safe default for change feeds that don't distinguish the two.
28
- */
4
+ import { LunoraError as LunoraError$1, ErrorBody } from '@lunora/errors';
5
+ import { R2SqlClient } from '@lunora/bindings/r2sql';
6
+ /**
7
+ * Turn-key incremental-sync source helpers for warehouse connectors
8
+ * (Fivetran custom functions, Airbyte incremental sources).
9
+ *
10
+ * The runtime's admin `/_lunora/admin/connector/sync` endpoint returns a
11
+ * {@link ConnectorSyncPage}: a flat list of change records since an opaque
12
+ * cursor, a `nextCursor` to resume from, and a `hasMore` flag. These helpers
13
+ * reshape that page into the response envelopes the two ecosystems expect, so a
14
+ * connector wrapper stays a few lines.
15
+ *
16
+ * {@link toFivetranResponse} produces the `{ state, insert, update, delete,
17
+ * hasMore, schema }` object a Fivetran connector function returns from its
18
+ * handler. {@link toAirbyteMessages} produces an ordered array of Airbyte
19
+ * protocol messages (a `RECORD` per row, a trailing `STATE` carrying the cursor),
20
+ * the line-delimited stream an Airbyte incremental source emits.
21
+ *
22
+ * Both consume the SAME page, so a single endpoint feeds either ecosystem.
23
+ */
24
+ /**
25
+ * One change record in a {@link ConnectorSyncPage}. Mirrors a row of the CDC log
26
+ * the shard / D1 change feed produces: an `op` (insert / update / delete), the
27
+ * owning `table`, and the document. `op` is normalised to the three warehouse
28
+ * verbs; an unknown / absent op is treated as `"upsert"` (insert-or-update),
29
+ * which is the safe default for change feeds that don't distinguish the two.
30
+ */
29
31
  interface ConnectorChange {
30
32
  /** The full document. For a delete, may carry only the primary key. */
31
33
  doc: Record<string, unknown>;
@@ -35,11 +37,11 @@ interface ConnectorChange {
35
37
  table: string;
36
38
  }
37
39
  /**
38
- * A page of changes the connector endpoint returns. `nextCursor` is an opaque
39
- * token the consumer stores and re-posts verbatim to resume; never parse it.
40
- * `hasMore` is `true` while the source has further pages past this one — keep
41
- * paging until it is `false` (caught up).
42
- */
40
+ * A page of changes the connector endpoint returns. `nextCursor` is an opaque
41
+ * token the consumer stores and re-posts verbatim to resume; never parse it.
42
+ * `hasMore` is `true` while the source has further pages past this one — keep
43
+ * paging until it is `false` (caught up).
44
+ */
43
45
  interface ConnectorSyncPage {
44
46
  changes: ReadonlyArray<ConnectorChange>;
45
47
  hasMore: boolean;
@@ -47,15 +49,15 @@ interface ConnectorSyncPage {
47
49
  nextCursor: string;
48
50
  }
49
51
  /**
50
- * Fivetran connector-function response envelope. A Fivetran custom function
51
- * returns this object: `state` is persisted by Fivetran and handed back on the
52
- * next sync (map it straight to {@link ConnectorSyncPage.nextCursor}), the
53
- * `insert` / `update` / `delete` maps bucket records per table, `hasMore` drives
54
- * Fivetran's "call me again immediately" loop, and `schema` declares each table's
55
- * primary key.
56
- *
57
- * See https://fivetran.com/docs/connectors/functions#responseformat.
58
- */
52
+ * Fivetran connector-function response envelope. A Fivetran custom function
53
+ * returns this object: `state` is persisted by Fivetran and handed back on the
54
+ * next sync (map it straight to {@link ConnectorSyncPage.nextCursor}), the
55
+ * `insert` / `update` / `delete` maps bucket records per table, `hasMore` drives
56
+ * Fivetran's "call me again immediately" loop, and `schema` declares each table's
57
+ * primary key.
58
+ *
59
+ * See https://fivetran.com/docs/connectors/functions#responseformat.
60
+ */
59
61
  interface FivetranResponse {
60
62
  delete: Record<string, Record<string, unknown>[]>;
61
63
  hasMore: boolean;
@@ -85,38 +87,76 @@ type AirbyteMessage = {
85
87
  type: "STATE";
86
88
  };
87
89
  /**
88
- * Format a {@link ConnectorSyncPage} as a Fivetran connector-function response.
89
- *
90
- * Inserts and upserts both land in `insert` (Fivetran upserts on primary key, so
91
- * an insert and an update of an existing row are wire-identical); explicit
92
- * updates land in `update`; deletes in `delete`. `state.cursor` carries the
93
- * opaque resume token Fivetran will echo back on the next invocation.
94
- * @param page the page returned by the connector sync endpoint.
95
- * @param primaryKey the primary-key column per table (default `"_id"`); pass a
96
- * map to override per table, used to fill the `schema` block.
97
- */
90
+ * Format a {@link ConnectorSyncPage} as a Fivetran connector-function response.
91
+ *
92
+ * Inserts and upserts both land in `insert` (Fivetran upserts on primary key, so
93
+ * an insert and an update of an existing row are wire-identical); explicit
94
+ * updates land in `update`; deletes in `delete`. `state.cursor` carries the
95
+ * opaque resume token Fivetran will echo back on the next invocation.
96
+ * @param page the page returned by the connector sync endpoint.
97
+ * @param primaryKey the primary-key column per table (default `"_id"`); pass a
98
+ * map to override per table, used to fill the `schema` block.
99
+ */
98
100
  declare const toFivetranResponse: (page: ConnectorSyncPage, primaryKey?: Record<string, string> | string) => FivetranResponse;
99
101
  /**
100
- * Format a {@link ConnectorSyncPage} as an ordered array of Airbyte protocol
101
- * messages: one `RECORD` per change (stream = table name), followed by a single
102
- * trailing `STATE` message carrying the opaque cursor. An Airbyte source serializes
103
- * these as line-delimited JSON to stdout.
104
- *
105
- * Airbyte's protocol has no native delete verb in `RECORD`; a delete is emitted
106
- * as a `RECORD` with a `_lunora_deleted: true` marker on the row so a downstream
107
- * normalization / dbt step can tombstone it. Callers needing true CDC deletes
108
- * should run Airbyte's CDC-deletion handling on that marker.
109
- * @param page the page returned by the connector sync endpoint.
110
- * @param emittedAt epoch-ms stamped on each `RECORD` (default `Date.now()`).
111
- */
102
+ * Format a {@link ConnectorSyncPage} as an ordered array of Airbyte protocol
103
+ * messages: one `RECORD` per change (stream = table name), followed by a single
104
+ * trailing `STATE` message carrying the opaque cursor. An Airbyte source serializes
105
+ * these as line-delimited JSON to stdout.
106
+ *
107
+ * Airbyte's protocol has no native delete verb in `RECORD`; a delete is emitted
108
+ * as a `RECORD` with a `_lunora_deleted: true` marker on the row so a downstream
109
+ * normalization / dbt step can tombstone it. Callers needing true CDC deletes
110
+ * should run Airbyte's CDC-deletion handling on that marker.
111
+ * @param page the page returned by the connector sync endpoint.
112
+ * @param emittedAt epoch-ms stamped on each `RECORD` (default `Date.now()`).
113
+ */
112
114
  declare const toAirbyteMessages: (page: ConnectorSyncPage, emittedAt?: number) => AirbyteMessage[];
115
+ /**
116
+ * The subset of the Cloudflare `ExecutionContext` the Lunora worker entry and
117
+ * the framework mount seams rely on — `waitUntil` for fire-and-forget work that
118
+ * must outlive the response, and `passThroughOnException` for the top-level
119
+ * error posture.
120
+ *
121
+ * It is deliberately **not** a package. `@lunora/runtime` is the leaf server
122
+ * runtime and `@lunora/nuxt` is a framework integration that intentionally does
123
+ * not depend on `@lunora/runtime`'s worker types, yet both need this exact
124
+ * shape: the runtime to build/forward the worker `fetch`, Nuxt to forward an
125
+ * inbound request to the user's composed worker. Each imports this file by
126
+ * relative path and the bundler (packem/rollup) inlines it: no runtime
127
+ * dependency edge is created, the helper is duplicated only in emitted output,
128
+ * never in source. One source of truth, zero deps. See AGENTS.md → "Top-level
129
+ * `shared/` — bundler-inlined source".
130
+ *
131
+ * Both methods are **optional**: a real Cloudflare `ExecutionContext` always
132
+ * supplies them, but a host that mounts Lunora as a sub-handler (Nitro/H3, a
133
+ * non-Cloudflare preview, a unit test) may hand over a partial context or none
134
+ * at all. Callers therefore invoke them defensively (`ctx.waitUntil?.(…)`) or
135
+ * fall back to {@link NOOP_EXECUTION_CONTEXT}.
136
+ */
137
+ interface ExecutionContextLike {
138
+ cache?: {
139
+ purge: (options: {
140
+ purgeEverything?: boolean;
141
+ tags?: string[];
142
+ }) => Promise<unknown>;
143
+ };
144
+ passThroughOnException?: () => void;
145
+ waitUntil?: (promise: Promise<unknown>) => void;
146
+ }
147
+ /**
148
+ * No-op `ExecutionContext` used when the host runtime didn't supply one (a
149
+ * non-Cloudflare preview, or a unit test), so the worker's `fetch` always
150
+ * receives a valid third argument.
151
+ */
152
+ declare const NOOP_EXECUTION_CONTEXT: ExecutionContextLike;
113
153
  /** A timestamp as better-auth stores it: epoch-ms, an ISO string, or absent. */
114
154
  type AuthTimestamp = null | number | string;
115
155
  /**
116
- * One authenticated user, as the auth browser surfaces it. Mirrors better-auth's
117
- * `user` row plus the `admin()` plugin columns (`role`/`banned`/…); the index
118
- * signature additionally carries any app-defined `user.additionalFields`.
119
- */
156
+ * One authenticated user, as the auth browser surfaces it. Mirrors better-auth's
157
+ * `user` row plus the `admin()` plugin columns (`role`/`banned`/…); the index
158
+ * signature additionally carries any app-defined `user.additionalFields`.
159
+ */
120
160
  interface AuthUser {
121
161
  [key: string]: unknown;
122
162
  banExpires?: AuthTimestamp;
@@ -153,10 +193,10 @@ interface AuthImpersonation {
153
193
  user: AuthUser;
154
194
  }
155
195
  /**
156
- * Which admin surfaces the configured auth plane supports, derived from the
157
- * enabled better-auth plugins. The studio renders only the panels whose
158
- * capability is `true`.
159
- */
196
+ * Which admin surfaces the configured auth plane supports, derived from the
197
+ * enabled better-auth plugins. The studio renders only the panels whose
198
+ * capability is `true`.
199
+ */
160
200
  interface AuthCapabilities {
161
201
  accounts: boolean;
162
202
  admin: boolean;
@@ -164,6 +204,43 @@ interface AuthCapabilities {
164
204
  passkey: boolean;
165
205
  twoFactor: boolean;
166
206
  }
207
+ /** One user-settable extra field for the create-user form, derived from the merged `user` table. */
208
+ interface AuthUserFieldSpec {
209
+ name: string;
210
+ plugin?: string;
211
+ required: boolean;
212
+ type: "boolean" | "date" | "number" | "string";
213
+ unique: boolean;
214
+ }
215
+ /**
216
+ * Rich, read-only description of the deployment's auth configuration — enabled
217
+ * plugins, sign-in methods, user-settable fields, organization sub-features, and
218
+ * session / rate-limit policy — for the studio's config panel and dynamic
219
+ * create-user form. Never carries a secret.
220
+ */
221
+ interface AuthConfigInfo {
222
+ capabilities: AuthCapabilities;
223
+ emailAndPassword: boolean;
224
+ organization: {
225
+ enabled: boolean;
226
+ roles: boolean;
227
+ teams: boolean;
228
+ };
229
+ plugins: string[];
230
+ rateLimit: {
231
+ enabled: boolean;
232
+ max?: number;
233
+ window?: number;
234
+ };
235
+ session: {
236
+ cookieCache?: boolean;
237
+ expiresIn?: number;
238
+ freshAge?: number;
239
+ updateAge?: number;
240
+ };
241
+ socialProviders: string[];
242
+ userFields: AuthUserFieldSpec[];
243
+ }
167
244
  /** Filtering / paging options forwarded to {@link AuthAdmin.listUsers} from the users endpoint's query string. */
168
245
  interface ListAuthUsersOptions {
169
246
  filterField?: string;
@@ -176,18 +253,27 @@ interface ListAuthUsersOptions {
176
253
  sortDirection?: "asc" | "desc";
177
254
  }
178
255
  /**
179
- * The auth user-management plane backing the studio's auth dashboard. The host
180
- * wires this to better-auth (typically via `@lunora/auth`'s `createAuthAdmin`);
181
- * the runtime stays free of a hard dependency on `@lunora/auth`. The read
182
- * methods back the GET browse endpoints; the optional mutations back the
183
- * admin-gated POST endpoints — a host that only needs read-only browsing can
184
- * omit them (the POST routes then respond `AUTH_OP_NOT_SUPPORTED`). Omit the
185
- * whole option and every `/auth/*` endpoint responds `AUTH_NOT_CONFIGURED`.
186
- *
187
- * Every method here runs behind the worker's `LUNORA_ADMIN_TOKEN` gate — the
188
- * implementation is a trusted server-side operator, not an end-user API.
189
- */
256
+ * The auth user-management plane backing the studio's auth dashboard. The host
257
+ * wires this to better-auth (typically via `@lunora/auth`'s `createAuthAdmin`);
258
+ * the runtime stays free of a hard dependency on `@lunora/auth`. The read
259
+ * methods back the GET browse endpoints; the optional mutations back the
260
+ * admin-gated POST endpoints — a host that only needs read-only browsing can
261
+ * omit them (the POST routes then respond `AUTH_OP_NOT_SUPPORTED`). Omit the
262
+ * whole option and every `/auth/*` endpoint responds `AUTH_NOT_CONFIGURED`.
263
+ *
264
+ * Every method here runs behind the worker's `LUNORA_ADMIN_TOKEN` gate — the
265
+ * implementation is a trusted server-side operator, not an end-user API.
266
+ */
190
267
  interface AuthAdmin {
268
+ addMember?: (input: {
269
+ organizationId: string;
270
+ role?: string;
271
+ userId: string;
272
+ }) => Promise<Record<string, unknown>>;
273
+ addTeamMember?: (input: {
274
+ teamId: string;
275
+ userId: string;
276
+ }) => Promise<Record<string, unknown>>;
191
277
  banUser?: (input: {
192
278
  expiresInSeconds?: number;
193
279
  reason?: string;
@@ -197,6 +283,23 @@ interface AuthAdmin {
197
283
  invitationId: string;
198
284
  }) => Promise<void>;
199
285
  capabilities?: () => Promise<AuthCapabilities>;
286
+ config?: () => Promise<AuthConfigInfo>;
287
+ createOrganization?: (input: {
288
+ logo?: string;
289
+ metadata?: Record<string, unknown>;
290
+ name: string;
291
+ ownerId?: string;
292
+ slug?: string;
293
+ }) => Promise<Record<string, unknown>>;
294
+ createOrgRole?: (input: {
295
+ organizationId: string;
296
+ permission: Record<string, string[]>;
297
+ role: string;
298
+ }) => Promise<Record<string, unknown>>;
299
+ createTeam?: (input: {
300
+ name: string;
301
+ organizationId: string;
302
+ }) => Promise<Record<string, unknown>>;
200
303
  createUser?: (input: {
201
304
  data?: Record<string, unknown>;
202
305
  email: string;
@@ -204,6 +307,12 @@ interface AuthAdmin {
204
307
  password?: string;
205
308
  role?: string | string[];
206
309
  }) => Promise<AuthUser>;
310
+ deleteOrganization?: (input: {
311
+ organizationId: string;
312
+ }) => Promise<void>;
313
+ deleteOrgRole?: (input: {
314
+ roleId: string;
315
+ }) => Promise<void>;
207
316
  deletePasskey?: (input: {
208
317
  passkeyId: string;
209
318
  }) => Promise<void>;
@@ -213,6 +322,12 @@ interface AuthAdmin {
213
322
  impersonateUser?: (input: {
214
323
  userId: string;
215
324
  }) => Promise<AuthImpersonation>;
325
+ inviteMember?: (input: {
326
+ email: string;
327
+ inviterId?: string;
328
+ organizationId: string;
329
+ role?: string;
330
+ }) => Promise<Record<string, unknown>>;
216
331
  listAccounts?: (input: {
217
332
  userId: string;
218
333
  }) => Promise<Record<string, unknown>[]>;
@@ -230,6 +345,11 @@ interface AuthAdmin {
230
345
  limit?: number;
231
346
  offset?: number;
232
347
  }) => Promise<AuthPage<Record<string, unknown>>>;
348
+ listOrgRoles?: (options: {
349
+ limit?: number;
350
+ offset?: number;
351
+ organizationId: string;
352
+ }) => Promise<AuthPage<Record<string, unknown>>>;
233
353
  listPasskeys?: (input: {
234
354
  userId: string;
235
355
  }) => Promise<Record<string, unknown>[]>;
@@ -238,10 +358,26 @@ interface AuthAdmin {
238
358
  offset?: number;
239
359
  userId?: string;
240
360
  }) => Promise<AuthPage<AuthSession>>;
361
+ listTeamMembers?: (options: {
362
+ limit?: number;
363
+ offset?: number;
364
+ teamId: string;
365
+ }) => Promise<AuthPage<Record<string, unknown>>>;
366
+ listTeams?: (options: {
367
+ limit?: number;
368
+ offset?: number;
369
+ organizationId: string;
370
+ }) => Promise<AuthPage<Record<string, unknown>>>;
241
371
  listUsers: (options: ListAuthUsersOptions) => Promise<AuthPage<AuthUser>>;
242
372
  removeMember?: (input: {
243
373
  memberId: string;
244
374
  }) => Promise<void>;
375
+ removeTeam?: (input: {
376
+ teamId: string;
377
+ }) => Promise<void>;
378
+ removeTeamMember?: (input: {
379
+ teamMemberId: string;
380
+ }) => Promise<void>;
245
381
  removeUser?: (input: {
246
382
  userId: string;
247
383
  }) => Promise<void>;
@@ -266,25 +402,36 @@ interface AuthAdmin {
266
402
  accountId: string;
267
403
  userId: string;
268
404
  }) => Promise<void>;
405
+ updateMemberRole?: (input: {
406
+ memberId: string;
407
+ role: string | string[];
408
+ }) => Promise<Record<string, unknown>>;
409
+ updateOrganization?: (input: {
410
+ logo?: string;
411
+ metadata?: Record<string, unknown>;
412
+ name?: string;
413
+ organizationId: string;
414
+ slug?: string;
415
+ }) => Promise<Record<string, unknown>>;
416
+ updateOrgRole?: (input: {
417
+ permission: Record<string, string[]>;
418
+ roleId: string;
419
+ }) => Promise<Record<string, unknown>>;
420
+ updateTeam?: (input: {
421
+ name: string;
422
+ teamId: string;
423
+ }) => Promise<Record<string, unknown>>;
269
424
  updateUser?: (input: {
270
425
  data: Record<string, unknown>;
271
426
  userId: string;
272
427
  }) => Promise<AuthUser>;
273
428
  }
274
429
  /**
275
- * Read-only subset of {@link AuthAdmin}, kept as an alias for the former
276
- * `authIntrospector` option (which the worker still honours as a browse-only
277
- * fallback). Prefer wiring `authAdmin` with `@lunora/auth`'s `createAuthAdmin`
278
- * so the mutation endpoints light up too.
279
- */
280
- type AuthIntrospector = Pick<AuthAdmin, "listSessions" | "listUsers">;
281
- /** Closure-scoped worker helpers the auth routes borrow (so this module stays out of the worker's god-closure). */
282
- /**
283
- * A compact, transport-safe description of one function argument — the runtime
284
- * read of a `v.*` validator's reflection tags (`kind` + `_meta`). The runtime
285
- * deliberately avoids a hard dependency on `@lunora/values`, so this reads the
286
- * validator structurally rather than importing its types.
287
- */
430
+ * A compact, transport-safe description of one function argument the runtime
431
+ * read of a `v.*` validator's reflection tags (`kind` + `_meta`). The runtime
432
+ * deliberately avoids a hard dependency on `@lunora/values`, so this reads the
433
+ * validator structurally rather than importing its types.
434
+ */
288
435
  interface FunctionArgumentDescriptor {
289
436
  /** Element validator kind for an `array` arg (one level), e.g. `string`. */
290
437
  element?: string;
@@ -298,49 +445,364 @@ interface FunctionArgumentDescriptor {
298
445
  table?: string;
299
446
  }
300
447
  /**
301
- * Describe one named argument from its validator. Unwraps a single `v.optional`
302
- * layer (marking the arg optional and reporting the inner kind), and surfaces
303
- * the two most useful per-kind details: an `id` arg's target table and an
304
- * `array` arg's element kind. Nested object/union shapes report their top-level
305
- * kind only enough for a signature view without a deep recursive walk.
306
- */
307
- /**
308
- * Observability hooks for the Lunora runtime.
309
- *
310
- * A user-supplied {@link ObservabilitySink} receives one event per dispatched
311
- * RPC (single-shard forward or fan-out). The runtime is otherwise oblivious
312
- * to where the telemetry goes adapters that forward to Cloudflare Analytics
313
- * Engine, OTLP-over-HTTP, Sentry, or stdout all implement the same shape.
314
- *
315
- * Failure model: the sink callback is wrapped in a try/catch so a faulty
316
- * adapter never breaks user-facing RPC dispatch. Errors thrown from inside
317
- * the sink are swallowed (they would otherwise replace a useful user-visible
318
- * error with a telemetry-pipeline failure).
319
- */
320
- /**
321
- * Per-RPC dispatch event. Single-shard calls set `shardKey`; cross-shard
322
- * fan-outs set `fanOut` with the table being aggregated, shard count, and
323
- * per-shard failure count.
324
- */
448
+ * Identity resolved from the inbound request by `WorkerOptions.resolveIdentity`.
449
+ *
450
+ * The `userId` field is special it becomes `ctx.auth.userId` inside the
451
+ * Durable Object. Any other keys (`email`, `name`, custom roles, etc.) are
452
+ * forwarded verbatim as `ctx.auth.getIdentity()`'s return value.
453
+ *
454
+ * Return `null` to signal that the request is anonymous; the runtime will
455
+ * skip both `x-lunora-userid` and `x-lunora-identity` headers, and
456
+ * `ctx.auth.userId` will be `undefined` on the shard side.
457
+ */
458
+ interface ResolvedIdentity {
459
+ /** Arbitrary additional claims. Must be JSON-serialisable. */
460
+ [key: string]: unknown;
461
+ /**
462
+ * JWT-standard expiry in epoch SECONDS. When present (and `expiresAtMs` is
463
+ * absent), the runtime forwards it as the socket's credential expiry — the
464
+ * DO drops the socket once it lapses. Used only on the WebSocket path.
465
+ */
466
+ exp?: number;
467
+ /**
468
+ * Credential expiry in epoch MILLISECONDS. Preferred over `exp` when
469
+ * both are present. Forwarded as the socket's expiry on the WebSocket path
470
+ * so the DO drops the socket once it lapses; omit for non-expiring sessions.
471
+ */
472
+ expiresAtMs?: number;
473
+ /** Stable user identifier (e.g. `"user_2k3..."` or `"u_42"`). */
474
+ userId: string;
475
+ }
476
+ /**
477
+ * A verifier that turns an inbound request into a {@link ResolvedIdentity} (or
478
+ * `null` for anonymous). Structurally identical to `WorkerOptions.resolveIdentity`,
479
+ * so `.auth()`'s better-auth session resolver, a signed-preview-link verifier, a
480
+ * per-tenant bearer check, an upstream-JWT reader, … are all just `IdentityResolver`s
481
+ * — the identity layer is generic over every scheme, not coupled to any one.
482
+ */
483
+ type IdentityResolver = (request: Request, env: unknown) => Promise<ResolvedIdentity | null> | ResolvedIdentity | null;
484
+ /** Error policy for {@link composeIdentityResolvers} when a participant resolver throws. */
485
+ type ComposeIdentityResolversErrorMode = "fail-closed" | "skip";
486
+ /** Options for {@link composeIdentityResolvers}. */
487
+ interface ComposeIdentityResolversOptions {
488
+ /**
489
+ * What to do when a resolver throws. `"fail-closed"` (default, safe)
490
+ * re-throws so a broken verifier fails the request rather than silently
491
+ * falling through to a weaker one; `"skip"` swallows the error and tries the
492
+ * next resolver (use only when a resolver's failure genuinely means "not my
493
+ * scheme").
494
+ */
495
+ readonly onError?: ComposeIdentityResolversErrorMode;
496
+ }
497
+ /**
498
+ * Compose several {@link IdentityResolver}s into one, first-match-wins: each is
499
+ * tried in order and the first that returns a non-null identity short-circuits.
500
+ * Generic over every scheme — the better-auth session resolver (obtained via the
501
+ * builder's `derived.resolveIdentity` escape hatch) is just one entry in the list,
502
+ * so composition never means losing it.
503
+ *
504
+ * A resolver that throws is handled per {@link ComposeIdentityResolversOptions.onError}
505
+ * (default `"fail-closed"`: the error propagates).
506
+ */
507
+ declare const composeIdentityResolvers: (resolvers: ReadonlyArray<IdentityResolver>, options?: ComposeIdentityResolversOptions) => IdentityResolver;
508
+ /**
509
+ * A thin, generic helper over {@link composeIdentityResolvers} for the per-route case:
510
+ * pick a resolver by `new URL(request.url).pathname`. Keys are matched by longest
511
+ * path prefix; `"*"` is the fallback. Still fully generic — a route→resolver map,
512
+ * with no portal / preview / tenant concepts baked in (those live in the app's
513
+ * own resolvers).
514
+ * @example
515
+ * routeIdentityResolvers({ "/admin": adminResolver, "/partner": partnerResolver, "*": sessionResolver })
516
+ */
517
+ declare const routeIdentityResolvers: (routes: Record<string, IdentityResolver>) => IdentityResolver;
518
+ /** The result of validating a candidate identity against an {@link IdentityContractLike}. */
519
+ type IdentityValidation = {
520
+ ok: true;
521
+ } | {
522
+ error: string;
523
+ ok: false;
524
+ };
525
+ /**
526
+ * Structural view of `@lunora/server`'s `IdentityContract` (from `defineIdentity`).
527
+ * Kept structural so `@lunora/runtime` stays free of an `@lunora/server` dependency.
528
+ * Keep the `onInvalid` union and `validate`/`IdentityValidation` shapes in sync with
529
+ * `@lunora/server`'s `IdentityContract` — they are projected by hand, not imported.
530
+ * The generated worker entry passes the app's `defineIdentity(...)` result here;
531
+ * the worker validates every resolver's returned claims against it at the trust
532
+ * boundary before they become `ctx.auth`.
533
+ */
534
+ interface IdentityContractLike {
535
+ /** Reject policy applied when validation fails: downgrade to anonymous, or reject the request (401). */
536
+ readonly onInvalid: "anonymous" | "reject";
537
+ /** Validate resolver-returned claims against the declared contract. */
538
+ validate: (identity: Record<string, unknown>) => IdentityValidation;
539
+ }
540
+ /** One KV namespace as the studio's KV browser surfaces it. */
541
+ interface KvNamespaceSummary {
542
+ /** The wrangler/env binding name, e.g. `"MY_KV"`. */
543
+ binding: string;
544
+ }
545
+ /** One key entry as the KV admin browser surfaces it. */
546
+ interface KvKeyEntry {
547
+ /** Absolute expiration (Unix seconds), when set. */
548
+ expiration?: number;
549
+ /** Per-key metadata set at write time, or absent when none. */
550
+ metadata?: unknown;
551
+ /** The key name. */
552
+ name: string;
553
+ }
554
+ /** A paginated page of KV keys as the admin browser returns it. */
555
+ interface KvKeyListResult {
556
+ /** Opaque cursor for the next page; absent when the listing is complete. */
557
+ cursor?: string;
558
+ /** The keys on this page. */
559
+ keys: KvKeyEntry[];
560
+ /** True when this is the final page. */
561
+ listComplete: boolean;
562
+ }
563
+ /** A KV value together with its stored metadata. */
564
+ interface KvValueResult {
565
+ /** Per-key metadata, or `null` when none. */
566
+ metadata: unknown;
567
+ /** The stored value as a string, or `null` when the key is absent. */
568
+ value: null | string;
569
+ }
570
+ /**
571
+ * The introspector the worker wires for the studio's KV browser. Build it from
572
+ * the env's bound KV namespaces. Omit it and the `/_lunora/admin/kv/*`
573
+ * endpoints respond `KV_NOT_CONFIGURED`.
574
+ */
575
+ interface KvIntrospector {
576
+ /** Delete a key from a namespace. No-op when the key is absent. */
577
+ deleteKey: (options: {
578
+ key: string;
579
+ namespace: string;
580
+ }) => Promise<void>;
581
+ /** Read a value (as text) and its metadata from a namespace key. */
582
+ getValue: (options: {
583
+ key: string;
584
+ namespace: string;
585
+ }) => Promise<KvValueResult>;
586
+ /** List keys in a namespace, optionally filtered by prefix and paginated. */
587
+ listKeys: (options: {
588
+ cursor?: string;
589
+ limit?: number;
590
+ namespace: string;
591
+ prefix?: string;
592
+ }) => Promise<KvKeyListResult>;
593
+ /** List the registered KV namespaces (binding names). */
594
+ listNamespaces: () => Promise<KvNamespaceSummary[]>;
595
+ /** Write a value (as text) with optional absolute expiration / relative TTL and metadata. */
596
+ putValue: (options: {
597
+ expiration?: number;
598
+ expirationTtl?: number;
599
+ key: string;
600
+ metadata?: unknown;
601
+ namespace: string;
602
+ value: string;
603
+ }) => Promise<void>;
604
+ }
605
+ /**
606
+ * Shared, bundler-inlined helpers for the structured `fields` a
607
+ * `ctx.log.<level>(message, fields)` / `ctx.log.with(fields)` call carries.
608
+ *
609
+ * Inlined (like {@link file://./otlp.ts}) so `@lunora/do`, `@lunora/runtime`,
610
+ * `@lunora/config`, and `@lunora/studio` — which sit on different tiers with no
611
+ * acceptable runtime dependency edge between them — share ONE implementation of
612
+ * field rendering/normalization instead of the byte-identical copies they would
613
+ * otherwise hand-mirror. Keep this genuinely zero-dependency (only built-ins) so
614
+ * inlining into each `dist` stays sound.
615
+ */
616
+ /** Structured, filterable key/value fields attached to a `ctx.log` line. */
617
+ type LogFields = Record<string, unknown>;
618
+ /**
619
+ * Severity of a `ctx.log.*` call. The five console method names (`log` is the
620
+ * default level, distinct from `info`) plus `trace`/`fatal`, so the logger spans
621
+ * the full OpenTelemetry severity ramp (`trace`→`fatal`).
622
+ */
623
+ type ContextLogLevel = "debug" | "error" | "fatal" | "info" | "log" | "trace" | "warn";
624
+ /**
625
+ * Per-event context handed to a sink alongside the event: lets a sink register
626
+ * background work (a telemetry POST, a durable pipeline send) with the request's
627
+ * `waitUntil` so it survives isolate teardown after the response returns. Absent
628
+ * `waitUntil` (no request context) means the sink falls back to fire-and-forget.
629
+ */
630
+ interface LogSinkContext {
631
+ /** Keep a background promise alive past the response (the request's `waitUntil`). */
632
+ waitUntil?: (promise: Promise<unknown>) => void;
633
+ }
634
+ /**
635
+ * One application log line emitted from a function handler via `ctx.log`.
636
+ * Produced per `ctx.log.*` call (unlike a per-dispatch RPC summary).
637
+ */
638
+ interface LogEvent {
639
+ /** Raw arguments passed to the `ctx.log.*` call, in order. */
640
+ args: unknown[];
641
+ /**
642
+ * Structured fields the caller attached (`ctx.log.info(message, fields)` or a
643
+ * bound `ctx.log.with(fields)` child), already normalized to a fresh bag of
644
+ * JSON-safe primitives (see `shared/log-fields.ts`). Absent for a plain
645
+ * console-style call.
646
+ */
647
+ fields?: LogFields;
648
+ /** Function path that emitted the line, e.g. `"messages:list"`. */
649
+ functionPath: string;
650
+ /** Severity the line was logged at. */
651
+ level: ContextLogLevel;
652
+ /** Display string — the message, or the console-style args rendered and space-joined. */
653
+ message: string;
654
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
655
+ shardKey?: string;
656
+ /** Span id of the RPC this line was emitted under (trace correlation), or absent. */
657
+ spanId?: string;
658
+ /** Trace id this line belongs to (from the inbound `traceparent`), or absent. */
659
+ traceId?: string;
660
+ /** Wall-clock millis when the line was emitted. */
661
+ ts: number;
662
+ /** Acting userId, or absent when anonymous. */
663
+ userId?: string;
664
+ }
665
+ /**
666
+ * What kind of instrument produced a measurement, which decides how a collector
667
+ * aggregates it:
668
+ *
669
+ * - `counter` — a monotonic delta to add up (requests, retries, bytes sent).
670
+ * - `gauge` — a point-in-time reading that replaces the last one (queue depth,
671
+ * cache size).
672
+ * - `histogram` — a value whose *distribution* matters (latency, payload size),
673
+ * giving percentiles rather than just a mean.
674
+ */
675
+ type MetricKind = "counter" | "gauge" | "histogram";
676
+ /**
677
+ * One measurement recorded from a function handler.
678
+ *
679
+ * Each `ctx.metrics.*` call produces exactly one of these — the runtime does no
680
+ * pre-aggregation, so counters carry **delta** temporality and a collector sums
681
+ * them. That keeps the sink model identical to logs and spans (one event, one
682
+ * export) at the cost of chattiness in a hot loop, where the handler should sum
683
+ * locally and record once.
684
+ */
685
+ interface MetricEvent {
686
+ /**
687
+ * Structured attributes the caller attached, normalized to a fresh bag of
688
+ * JSON-safe primitives (see `shared/log-fields.ts`). These are the metric's
689
+ * dimensions — keep them low-cardinality; an id-valued attribute creates a
690
+ * distinct time series per id.
691
+ *
692
+ * Caller-controlled, so they MAY contain user input and they DO egress to
693
+ * whatever destination the sink ships to — the same caveat as a log line's
694
+ * `fields` and a span's `error.message`. Scrub upstream if that matters.
695
+ */
696
+ attributes?: LogFields;
697
+ /** Function path that recorded the measurement, e.g. `"orders:checkout"`. */
698
+ functionPath: string;
699
+ /** Instrument kind; see {@link MetricKind}. */
700
+ kind: MetricKind;
701
+ /** Instrument name, e.g. `"orders.placed"`. */
702
+ name: string;
703
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
704
+ shardKey?: string;
705
+ /** Wall-clock millis when the measurement was recorded. */
706
+ ts: number;
707
+ /**
708
+ * The measured value: the increment for a `counter`, the current reading for
709
+ * a `gauge`, the observed sample for a `histogram`.
710
+ */
711
+ value: number;
712
+ }
713
+ /**
714
+ * One span produced by a `ctx.trace(name, fn)` call, or the synthetic root span
715
+ * the shard records for the dispatch itself so a waterfall has a bar to hang
716
+ * its children under.
717
+ *
718
+ * Ids are the same lowercase-hex form the OTLP encoders and `traceparent` use
719
+ * (32-hex trace, 16-hex span), so a `SpanEvent` composes into an OTLP span with
720
+ * no reformatting.
721
+ */
722
+ interface SpanEvent {
723
+ /**
724
+ * Structured attributes the caller attached, already normalized to a fresh
725
+ * bag of JSON-safe primitives (see `shared/log-fields.ts`) exactly like a log
726
+ * line's `fields`. Absent when the caller passed none.
727
+ */
728
+ attributes?: LogFields;
729
+ /** Wall-clock duration of the span body, in milliseconds. */
730
+ durationMs: number;
731
+ /**
732
+ * Populated when the span body threw. `type` is the error's constructor name
733
+ * (or its `LunoraError` code); `message` is the human-readable string and may
734
+ * include user input, so sinks shipping to third parties should scrub it.
735
+ */
736
+ error?: {
737
+ message: string;
738
+ type: string;
739
+ };
740
+ /**
741
+ * Function path the span was created under, e.g. `"messages:list"`. A span
742
+ * created inside a function invoked via `ctx.runQuery`/`runMutation`/
743
+ * `runAction` carries the OUTER entrypoint's path, since the composed call
744
+ * reuses its context — the same attribution rule `ctx.log` follows.
745
+ */
746
+ functionPath: string;
747
+ /** Caller-supplied span name, e.g. `"stripe.charge"`. */
748
+ name: string;
749
+ /** True when the span body returned without throwing. */
750
+ ok: boolean;
751
+ /**
752
+ * Span id of the enclosing span — the parent `ctx.trace` when nested, else
753
+ * the dispatch's own RPC span (from the inbound `traceparent`). A span with
754
+ * no inbound trace context is parented to a locally-minted root, so this is
755
+ * always set for a `ctx.trace` span; only the synthetic `dispatch` span below
756
+ * carries `""`, meaning "nothing above me in this trace".
757
+ */
758
+ parentSpanId: string;
759
+ /**
760
+ * True for the synthetic span representing the **dispatch itself**, which the
761
+ * shard records so a waterfall has a bar for the request to hang its
762
+ * `ctx.trace` spans under.
763
+ *
764
+ * Named for what it is rather than "root": it is not the root of the
765
+ * collector-side trace — the worker's own RPC span sits above it — and it is
766
+ * never exported to a sink, because the runtime already emits that dispatch
767
+ * via `onRpc` and a collector would otherwise show it twice. Locally it *is*
768
+ * the outermost span, which is why the fold prefers it as a trace's anchor.
769
+ */
770
+ dispatch?: boolean;
771
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
772
+ shardKey?: string;
773
+ /** This span's own id (16-hex). */
774
+ spanId: string;
775
+ /** Wall-clock millis when the span started. */
776
+ startTs: number;
777
+ /** Trace this span belongs to (32-hex) — shared with the dispatch's logs. */
778
+ traceId: string;
779
+ /** Acting userId, or absent when anonymous. */
780
+ userId?: string;
781
+ }
782
+ /**
783
+ * Per-RPC dispatch event. Single-shard calls set `shardKey`; cross-shard
784
+ * fan-outs set `fanOut` with the table being aggregated, shard count, and
785
+ * per-shard failure count.
786
+ */
325
787
  interface ObservabilityEvent {
326
788
  /** Wall-clock duration of the dispatch, in milliseconds. */
327
789
  durationMs: number;
328
790
  /**
329
- * Populated on `ok === false`. `code`/`status` mirror the LunoraError
330
- * taxonomy; `message` is the human-readable string (may include user
331
- * input — sinks that ship to third parties should scrub it).
332
- */
791
+ * Populated on `ok === false`. `code`/`status` mirror the LunoraError
792
+ * taxonomy; `message` is the human-readable string (may include user
793
+ * input — sinks that ship to third parties should scrub it).
794
+ */
333
795
  error?: {
334
796
  code: string;
335
797
  message: string;
336
798
  status: number;
337
799
  };
338
800
  /**
339
- * Populated for fan-out dispatches.
340
- * `shards` is the total fan-out cardinality; `failed` counts shards that
341
- * timed out or returned an error (the same `errors[]` the response body
342
- * carries to the caller).
343
- */
801
+ * Populated for fan-out dispatches.
802
+ * `shards` is the total fan-out cardinality; `failed` counts shards that
803
+ * timed out or returned an error (the same `errors[]` the response body
804
+ * carries to the caller).
805
+ */
344
806
  fanOut?: {
345
807
  failed: number;
346
808
  shards: number;
@@ -352,135 +814,152 @@ interface ObservabilityEvent {
352
814
  ok: boolean;
353
815
  /** Shard key for single-shard calls; absent for fan-outs. */
354
816
  shardKey?: string;
355
- }
356
- /** Severity of a {@link LogEvent}, mirroring the usual console levels. */
357
- type LogLevel = "debug" | "error" | "info" | "log" | "warn";
358
- /**
359
- * One application log line emitted from a function handler via `ctx.log`.
360
- *
361
- * Unlike {@link ObservabilityEvent} (one summary per dispatch), a `LogEvent`
362
- * is produced for each `ctx.log.*` call, carrying the human-readable `message`
363
- * (the args joined for display) plus the structured `args` array for sinks that
364
- * want the raw values. `functionPath` attributes the line to the handler that
365
- * emitted it; `shardKey`/`userId` mirror the dispatch context.
366
- *
367
- * This is how `ctx.log` reaches a destination in production: wire a sink's
368
- * {@link ObservabilitySink.onLog} and route it wherever you ship logs. In dev
369
- * the runtime also emits these to `console` so the CLI / Vite plugin can format
370
- * them in the terminal.
371
- */
372
- interface LogEvent {
373
- /** Raw arguments passed to the `ctx.log.*` call, in order. */
374
- args: unknown[];
375
- /** Function path that emitted the line, e.g. `"messages:list"`. */
376
- functionPath: string;
377
- /** Severity the line was logged at. */
378
- level: LogLevel;
379
- /** Display string the args rendered and space-joined. */
380
- message: string;
381
- /** Shard key for single-shard calls; absent for the unnamed root DO. */
382
- shardKey?: string;
383
- /** Wall-clock millis when the line was emitted. */
384
- ts: number;
385
- /** Acting userId, or absent when anonymous. */
386
- userId?: string;
387
- }
388
- /**
389
- * Per-event context handed to a sink alongside the event. Lets a sink register
390
- * background work (e.g. a telemetry POST) with the request's `ctx.waitUntil` so
391
- * it survives isolate teardown after the response returns. Absent (`undefined`
392
- * `waitUntil`) on paths with no request context (e.g. the in-process
393
- * `serverQuery` fast-path), where the sink falls back to fire-and-forget.
394
- */
395
- interface ObservabilitySinkContext {
396
- /** Keep a background promise alive past the response (the request's `ctx.waitUntil`). */
397
- waitUntil?: (promise: Promise<unknown>) => void;
398
- }
399
- /**
400
- * The hook contract. Methods are optional so a sink can opt into only the
401
- * events it cares about; the runtime no-ops the others.
402
- */
817
+ /**
818
+ * W3C trace context for this dispatch, generated once at dispatch entry (32-
819
+ * and 16-hex). A sink (e.g. `otlpSink`) reuses these for the dispatch's span
820
+ * instead of minting fresh ids, and the runtime propagates them to the shard
821
+ * as a `traceparent` so a container the handler calls can stitch its spans
822
+ * under the same trace. Absent on paths that don't originate a trace (a sink
823
+ * falls back to random ids).
824
+ */
825
+ spanId?: string;
826
+ traceId?: string;
827
+ }
828
+ /**
829
+ * The `ctx.log` observability contract lives in `shared/` (inlined into each
830
+ * `dist`) so the DO that builds the events and the runtime sink that consumes
831
+ * them agree by construction rather than by hand-mirrored duplication. Re-exported
832
+ * here under the runtime's historical names.
833
+ *
834
+ * `LogLevel` is the canonical `ctx.log` severity union (the five console tiers
835
+ * plus `trace`/`fatal`); `ObservabilitySinkContext` is the shared per-event sink
836
+ * context (a `waitUntil` to keep a background send alive past the response).
837
+ */
838
+ type LogLevel = ContextLogLevel;
839
+ type ObservabilitySinkContext = LogSinkContext;
840
+ /**
841
+ * The hook contract. Methods are optional so a sink can opt into only the
842
+ * events it cares about; the runtime no-ops the others.
843
+ */
403
844
  interface ObservabilitySink {
404
845
  /** Invoked once per `ctx.log.*` call from a function handler. */
405
846
  onLog?: (event: LogEvent, context?: ObservabilitySinkContext) => void;
847
+ /**
848
+ * Invoked once per `ctx.metrics.*` measurement. No pre-aggregation happens
849
+ * upstream, so counter values are deltas for the destination to sum.
850
+ */
851
+ onMetric?: (event: MetricEvent, context?: ObservabilitySinkContext) => void;
406
852
  /** Invoked once per dispatched RPC (single-shard or fan-out). */
407
853
  onRpc?: (event: ObservabilityEvent, context?: ObservabilitySinkContext) => void;
854
+ /**
855
+ * Invoked once per `ctx.trace(name, fn)` span, when the span body settles.
856
+ * Distinct from `onRpc`: that is the one SERVER span per dispatch, this is the
857
+ * INTERNAL spans a handler creates beneath it.
858
+ */
859
+ onSpan?: (event: SpanEvent, context?: ObservabilitySinkContext) => void;
408
860
  }
409
861
  /**
410
- * Invoke `sink.onRpc` with the given event, swallowing any error the sink
411
- * throws. Use at the dispatch boundary; the runtime should never see a
412
- * sink-originating throw bubble up past this point. `context.waitUntil`, when
413
- * supplied, lets a network sink keep its send alive past the response.
414
- */
862
+ * Invoke `sink.onRpc` with the given event, swallowing any error the sink
863
+ * throws. Use at the dispatch boundary; the runtime should never see a
864
+ * sink-originating throw bubble up past this point. `context.waitUntil`, when
865
+ * supplied, lets a network sink keep its send alive past the response.
866
+ */
415
867
  declare const emitRpcEvent: (sink: ObservabilitySink | undefined, event: ObservabilityEvent, context?: ObservabilitySinkContext) => void;
416
868
  /**
417
- * Invoke `sink.onLog` with the given log event, swallowing any error the sink
418
- * throws. The same failure model as {@link emitRpcEvent}: a buggy log sink must
419
- * never break the handler that emitted the line.
420
- */
869
+ * Invoke `sink.onLog` with the given log event, swallowing any error the sink
870
+ * throws. The same failure model as {@link emitRpcEvent}: a buggy log sink must
871
+ * never break the handler that emitted the line.
872
+ */
421
873
  declare const emitLogEvent: (sink: ObservabilitySink | undefined, event: LogEvent, context?: ObservabilitySinkContext) => void;
422
874
  /**
423
- * Structural projection of the bits of `DurableObjectNamespace` the runtime
424
- * needs. Real workers-types defines a much wider surface; this lets us pass
425
- * unit-test doubles without coupling to `@cloudflare/workers-types`.
426
- */
875
+ * Cloudflare Durable Object jurisdictions restrict where a DO runs and persists
876
+ * data, for data-residency / compliance regimes (GDPR, FedRAMP, US data
877
+ * residency). The set is open — Cloudflare adds values over time — so this is a
878
+ * widening union rather than a closed enum.
879
+ * @see https://developers.cloudflare.com/durable-objects/reference/data-location/
880
+ */
881
+ type DurableObjectJurisdiction = "eu" | "fedramp" | "us";
882
+ /**
883
+ * Structural projection of the bits of `DurableObjectNamespace` the runtime
884
+ * needs. Real workers-types defines a much wider surface; this lets us pass
885
+ * unit-test doubles without coupling to `@cloudflare/workers-types`.
886
+ */
427
887
  interface ShardNamespaceLike {
428
888
  get: (id: unknown) => {
429
889
  fetch: (request: Request) => Promise<Response>;
430
890
  };
431
891
  /**
432
- * `getByName` is the friendlier API but isn't on every workers-types
433
- * release yet. We prefer it when available and fall back to
434
- * `idFromName` + `get` for compatibility.
435
- */
892
+ * `getByName` is the friendlier API but isn't on every workers-types
893
+ * release yet. We prefer it when available and fall back to
894
+ * `idFromName` + `get` for compatibility.
895
+ */
436
896
  getByName?: (name: string) => {
437
897
  fetch: (request: Request) => Promise<Response>;
438
898
  };
439
899
  idFromName: (name: string) => unknown;
900
+ /**
901
+ * Derive a jurisdiction-restricted subnamespace. Every ID and stub created
902
+ * from the returned namespace is pinned to `jurisdiction`. Optional because
903
+ * older workers-types releases (and unit-test doubles) may not expose it;
904
+ * {@link applyJurisdiction} fails closed when a jurisdiction is requested
905
+ * but this method is absent.
906
+ */
907
+ jurisdiction?: (jurisdiction: DurableObjectJurisdiction) => ShardNamespaceLike;
440
908
  }
441
909
  interface ResolvedShard {
442
910
  fetch: (request: Request) => Promise<Response>;
443
911
  }
912
+ /**
913
+ * Return a jurisdiction-restricted view of `namespace`, or `namespace`
914
+ * unchanged when no jurisdiction is configured.
915
+ *
916
+ * Fail-closed: if a jurisdiction is requested but the binding does not expose
917
+ * `.jurisdiction()` (an older workers-types, or a misconfigured test double),
918
+ * this throws rather than silently routing to the un-pinned global namespace —
919
+ * silently dropping a residency constraint would let data land outside the
920
+ * compliance boundary the caller asked for.
921
+ */
922
+ declare const applyJurisdiction: (namespace: ShardNamespaceLike, jurisdiction?: DurableObjectJurisdiction) => ShardNamespaceLike;
444
923
  /** Look up a shard stub by name, preferring `getByName` when present. */
445
924
  declare const resolveShard: (namespace: ShardNamespaceLike, shardKey: string) => ResolvedShard;
446
925
  /**
447
- * Source of "which shard keys exist for a given table right now". Returning
448
- * an empty array is valid — the coordinator will respond with the merge
449
- * strategy's identity (empty array for `concat`, `0` for `sum`, etc.).
450
- */
926
+ * Source of "which shard keys exist for a given table right now". Returning
927
+ * an empty array is valid — the coordinator will respond with the merge
928
+ * strategy's identity (empty array for `concat`, `0` for `sum`, etc.).
929
+ */
451
930
  interface ShardRegistry {
452
931
  listShardKeys: (table: string) => Promise<ReadonlyArray<string>> | ReadonlyArray<string>;
453
932
  }
454
933
  /**
455
- * Static-map implementation. Useful for tests and for small deployments
456
- * where shard keys are known up front (e.g. a fixed set of channel IDs).
457
- */
934
+ * Static-map implementation. Useful for tests and for small deployments
935
+ * where shard keys are known up front (e.g. a fixed set of channel IDs).
936
+ */
458
937
  declare const createStaticShardRegistry: (table_to_keys: Readonly<Record<string, ReadonlyArray<string>>>) => ShardRegistry;
459
938
  /**
460
- * Wire-serializable merge strategy. `topK.by` is a field name on the row
461
- * (the runtime looks it up with a string key), not a closure.
462
- *
463
- * Aggregate-friendly variants for cross-shard `count` / `aggregate` /
464
- * `groupBy` fan-outs:
465
- *
466
- * - `sum` — `count(*)`, `aggregate({ op: "sum" })` (sums numeric per-shard payloads).
467
- * - `max` — `aggregate({ op: "max" })`.
468
- * - `min` — `aggregate({ op: "min" })`.
469
- * - `groupBy` — per-shard `GroupByEntry[]` payloads, reduced into one
470
- * entry per distinct key tuple. `op` controls how values combine across
471
- * shards: `sum` (default — works for `COUNT(*)` and `SUM`), `max`, `min`.
472
- *
473
- * `avg` is intentionally absent in v1 — a correct cross-shard average
474
- * requires shipping `(sum, count)` per shard, not the post-shard mean.
475
- * Use two separate fan-outs (`sum` + `count`) and divide in the caller.
476
- *
477
- * `rank` — cross-shard `rank()` over a partition that spans shards (e.g. a
478
- * global leaderboard `.shardBy("userId")` with `rankIndex(partitionBy: [])`).
479
- * Each shard's `__lunora_admin__:rankBefore` returns `{before, total}` (its
480
- * local rows strictly-before the explicit key, plus its local partition
481
- * total); the merge sums them into `{position: Σbefore + 1, total: Σtotal}` —
482
- * the 1-based global position and global partition size.
483
- */
939
+ * Wire-serializable merge strategy. `topK.by` is a field name on the row
940
+ * (the runtime looks it up with a string key), not a closure.
941
+ *
942
+ * Aggregate-friendly variants for cross-shard `count` / `aggregate` /
943
+ * `groupBy` fan-outs:
944
+ *
945
+ * - `sum` — `count(*)`, `aggregate({ op: "sum" })` (sums numeric per-shard payloads).
946
+ * - `max` — `aggregate({ op: "max" })`.
947
+ * - `min` — `aggregate({ op: "min" })`.
948
+ * - `groupBy` — per-shard `GroupByEntry[]` payloads, reduced into one
949
+ * entry per distinct key tuple. `op` controls how values combine across
950
+ * shards: `sum` (default — works for `COUNT(*)` and `SUM`), `max`, `min`.
951
+ *
952
+ * `avg` is intentionally absent in v1 — a correct cross-shard average
953
+ * requires shipping `(sum, count)` per shard, not the post-shard mean.
954
+ * Use two separate fan-outs (`sum` + `count`) and divide in the caller.
955
+ *
956
+ * `rank` — cross-shard `rank()` over a partition that spans shards (e.g. a
957
+ * global leaderboard `.shardBy("userId")` with `rankIndex(partitionBy: [])`).
958
+ * Each shard's `__lunora_admin__:rankBefore` returns `{before, total}` (its
959
+ * local rows strictly-before the explicit key, plus its local partition
960
+ * total); the merge sums them into `{position: Σbefore + 1, total: Σtotal}` —
961
+ * the 1-based global position and global partition size.
962
+ */
484
963
  type MergeStrategy = {
485
964
  kind: "concat";
486
965
  } | {
@@ -503,17 +982,17 @@ type MergeStrategy = {
503
982
  op?: "max" | "min" | "sum";
504
983
  };
505
984
  /**
506
- * Convenience: build the right wire-serializable {@link MergeStrategy} for a
507
- * given aggregate read. The reader doesn't know which op the caller chose, so
508
- * a fan-out wrapper passes the user's op + by-keys through this to derive the
509
- * merge.
510
- *
511
- * - `count` → `sum`.
512
- * - `aggregate({ op })` → `sum`/`max`/`min` (or throws for `avg`).
513
- * - `groupBy({ by, agg })` → `groupBy({ op })` (defaults to `sum` since
514
- * `groupBy`'s default reducer is `count`).
515
- * @returns the derived {@link MergeStrategy}.
516
- */
985
+ * Convenience: build the right wire-serializable {@link MergeStrategy} for a
986
+ * given aggregate read. The reader doesn't know which op the caller chose, so
987
+ * a fan-out wrapper passes the user's op + by-keys through this to derive the
988
+ * merge.
989
+ *
990
+ * - `count` → `sum`.
991
+ * - `aggregate({ op })` → `sum`/`max`/`min` (or throws for `avg`).
992
+ * - `groupBy({ by, agg })` → `groupBy({ op })` (defaults to `sum` since
993
+ * `groupBy`'s default reducer is `count`).
994
+ * @returns the derived {@link MergeStrategy}.
995
+ */
517
996
  declare const mergeStrategyForAggregate: (input: {
518
997
  agg?: {
519
998
  op?: "avg" | "count" | "max" | "min" | "sum";
@@ -531,11 +1010,11 @@ interface FanOutSpec {
531
1010
  table: string;
532
1011
  }
533
1012
  /**
534
- * Per-shard failure surfaced in the aggregate response's `errors` field. We
535
- * never throw out of `fanOut` — slow/failed shards are *data*, not an
536
- * exception, so callers can decide whether to retry or surface a partial
537
- * UI.
538
- */
1013
+ * Per-shard failure surfaced in the aggregate response's `errors` field. We
1014
+ * never throw out of `fanOut` — slow/failed shards are *data*, not an
1015
+ * exception, so callers can decide whether to retry or surface a partial
1016
+ * UI.
1017
+ */
539
1018
  interface ShardError {
540
1019
  /** Human-readable; tests assert on `.includes("timeout")` and similar. */
541
1020
  message: string;
@@ -554,15 +1033,15 @@ interface FanOutResult<T = unknown> {
554
1033
  }
555
1034
  interface QueryCoordinatorOptions {
556
1035
  /**
557
- * Maximum number of shard RPCs to issue in parallel. Defaults to 16 —
558
- * keeps the 30-second Worker CPU budget healthy when fanning out to
559
- * dozens of shards and avoids stampeding the DO namespace.
560
- */
1036
+ * Maximum number of shard RPCs to issue in parallel. Defaults to 16 —
1037
+ * keeps the 30-second Worker CPU budget healthy when fanning out to
1038
+ * dozens of shards and avoids stampeding the DO namespace.
1039
+ */
561
1040
  maxConcurrency?: number;
562
1041
  /**
563
- * Hard per-shard timeout in milliseconds. Defaults to 5000; a slow
564
- * shard surfaces in `errors[]` rather than stalling the aggregate.
565
- */
1042
+ * Hard per-shard timeout in milliseconds. Defaults to 5000; a slow
1043
+ * shard surfaces in `errors[]` rather than stalling the aggregate.
1044
+ */
566
1045
  perShardTimeoutMs?: number;
567
1046
  /** Required — drives which shards to fan out to. */
568
1047
  registry: ShardRegistry;
@@ -575,16 +1054,16 @@ interface FanOutRequest {
575
1054
  headers?: Record<string, string>;
576
1055
  }
577
1056
  /**
578
- * Cross-shard migration request. Unlike {@link FanOutRequest} there is no merge
579
- * strategy — per-shard payloads are `MigrationRunResult`-shaped objects, not
580
- * rows, so {@link QueryCoordinator.orchestrateMigration} rolls them up with the
581
- * fixed semantics documented on {@link MigrationFanOutResult}.
582
- *
583
- * `functionPath` is the admin RPC to invoke on each shard
584
- * (`__lunora_admin__:runMigration` or `:migrationStatus`); `headers` must carry
585
- * the `Authorization` bearer header the shard's admin gate requires (the
586
- * configured admin token), or every shard comes back as a 403 error.
587
- */
1057
+ * Cross-shard migration request. Unlike {@link FanOutRequest} there is no merge
1058
+ * strategy — per-shard payloads are `MigrationRunResult`-shaped objects, not
1059
+ * rows, so {@link QueryCoordinator.orchestrateMigration} rolls them up with the
1060
+ * fixed semantics documented on {@link MigrationFanOutResult}.
1061
+ *
1062
+ * `functionPath` is the admin RPC to invoke on each shard
1063
+ * (`__lunora_admin__:runMigration` or `:migrationStatus`); `headers` must carry
1064
+ * the `Authorization` bearer header the shard's admin gate requires (the
1065
+ * configured admin token), or every shard comes back as a 403 error.
1066
+ */
588
1067
  interface MigrationFanOutRequest {
589
1068
  args?: Record<string, unknown>;
590
1069
  functionPath: string;
@@ -614,23 +1093,23 @@ interface MigrationFanOutResult {
614
1093
  /** Per-shard outcomes, in registry order. */
615
1094
  shards: ReadonlyArray<ShardMigrationOutcome>;
616
1095
  /**
617
- * Rolled-up status. `"failed"` if any shard's runner reported failure;
618
- * `"in_progress"` if any shard is incomplete or unreachable (the run stays
619
- * resumable); `"completed"` only when every shard finished cleanly.
620
- */
1096
+ * Rolled-up status. `"failed"` if any shard's runner reported failure;
1097
+ * `"in_progress"` if any shard is incomplete or unreachable (the run stays
1098
+ * resumable); `"completed"` only when every shard finished cleanly.
1099
+ */
621
1100
  status: "completed" | "failed" | "in_progress";
622
1101
  }
623
1102
  /**
624
- * Cross-shard rank request. Like {@link MigrationFanOutRequest} there is no
625
- * caller-supplied merge — per-shard payloads are `{before, total}` objects, so
626
- * {@link QueryCoordinator.orchestrateRank} rolls them up with the fixed
627
- * `{position: Σbefore + 1, total: Σtotal}` semantics {@link mergeRank} defines.
628
- *
629
- * The key tuple (`partitionKey`/`sortValues`/`rowId`) is built off the row doc
630
- * via `@lunora/do`'s `rankKeyFromDoc(index, doc)` and forwarded verbatim to
631
- * each shard's `__lunora_admin__:rankBefore` admin RPC; `headers` must carry
632
- * the admin bearer the shard's admin gate requires.
633
- */
1103
+ * Cross-shard rank request. Like {@link MigrationFanOutRequest} there is no
1104
+ * caller-supplied merge — per-shard payloads are `{before, total}` objects, so
1105
+ * {@link QueryCoordinator.orchestrateRank} rolls them up with the fixed
1106
+ * `{position: Σbefore + 1, total: Σtotal}` semantics {@link mergeRank} defines.
1107
+ *
1108
+ * The key tuple (`partitionKey`/`sortValues`/`rowId`) is built off the row doc
1109
+ * via `@lunora/do`'s `rankKeyFromDoc(index, doc)` and forwarded verbatim to
1110
+ * each shard's `__lunora_admin__:rankBefore` admin RPC; `headers` must carry
1111
+ * the admin bearer the shard's admin gate requires.
1112
+ */
634
1113
  interface RankFanOutRequest {
635
1114
  headers?: Record<string, string>;
636
1115
  /** Rank index name on `table`. */
@@ -671,18 +1150,18 @@ interface ShardRankOutcome {
671
1150
  shardKey: string;
672
1151
  }
673
1152
  /**
674
- * Cross-shard ranked-pagination request. Like {@link RankFanOutRequest} there's
675
- * no caller-supplied merge — the merge is the fixed k-way merge by the rank-key
676
- * tuple. `take` is the global page size; `cursor` is the opaque composite cursor
677
- * from the prior page's `continueCursor` (absent → first page). `partitionKey`,
678
- * when set, pins a single partition (`encodePartitionKey(index.partitionBy, where)`),
679
- * forwarded so each shard scopes its local slice to that partition.
680
- *
681
- * `directions` is the per-sort-key direction list (`index.sortBy[i].direction`)
682
- * the coordinator's comparator needs to break ties the same way each shard's
683
- * `ORDER BY` does. `partitionKey` and the `__id__` tiebreak are always ascending
684
- * (matching the shard companion's btree), so only the sort columns vary.
685
- */
1153
+ * Cross-shard ranked-pagination request. Like {@link RankFanOutRequest} there's
1154
+ * no caller-supplied merge — the merge is the fixed k-way merge by the rank-key
1155
+ * tuple. `take` is the global page size; `cursor` is the opaque composite cursor
1156
+ * from the prior page's `continueCursor` (absent → first page). `partitionKey`,
1157
+ * when set, pins a single partition (`encodePartitionKey(index.partitionBy, where)`),
1158
+ * forwarded so each shard scopes its local slice to that partition.
1159
+ *
1160
+ * `directions` is the per-sort-key direction list (`index.sortBy[i].direction`)
1161
+ * the coordinator's comparator needs to break ties the same way each shard's
1162
+ * `ORDER BY` does. `partitionKey` and the `__id__` tiebreak are always ascending
1163
+ * (matching the shard companion's btree), so only the sort columns vary.
1164
+ */
686
1165
  interface RankPageFanOutRequest {
687
1166
  /** Opaque composite cursor from the prior page's `continueCursor`. */
688
1167
  cursor?: null | string;
@@ -729,78 +1208,78 @@ interface RankPageFanOutResult {
729
1208
  interface QueryCoordinator {
730
1209
  fanOut: <T = unknown>(namespace: ShardNamespaceLike, request: FanOutRequest) => Promise<FanOutResult<T>>;
731
1210
  /**
732
- * Fan the `__lunora_admin__:applyCdc` admin RPC out by forwarding each
733
- * pre-bucketed per-shard batch of CDC changes, rolling up the applied/failed
734
- * counts. The replay half of point-in-time recovery.
735
- */
1211
+ * Fan the `__lunora_admin__:applyCdc` admin RPC out by forwarding each
1212
+ * pre-bucketed per-shard batch of CDC changes, rolling up the applied/failed
1213
+ * counts. The replay half of point-in-time recovery.
1214
+ */
736
1215
  orchestrateApplyCdc: (namespace: ShardNamespaceLike, request: ApplyCdcFanOutRequest) => Promise<ApplyCdcFanOutResult>;
737
1216
  /**
738
- * Fan the `__lunora_admin__:cdcSync` admin RPC out to every live shard,
739
- * each resumed from its own cursor in `request.cursors` (shardKey → seq).
740
- * Returns the per-shard change pages plus their new cursors so the caller
741
- * can checkpoint each shard independently — the streaming-export feed.
742
- */
1217
+ * Fan the `__lunora_admin__:cdcSync` admin RPC out to every live shard,
1218
+ * each resumed from its own cursor in `request.cursors` (shardKey → seq).
1219
+ * Returns the per-shard change pages plus their new cursors so the caller
1220
+ * can checkpoint each shard independently — the streaming-export feed.
1221
+ */
743
1222
  orchestrateCdcSync: (namespace: ShardNamespaceLike, request: CdcSyncFanOutRequest) => Promise<CdcSyncFanOutResult>;
744
1223
  /**
745
- * Fan an export admin RPC out to every live shard, returning the
746
- * per-shard `{rows}` payloads alongside any per-shard errors. Each shard
747
- * returns a JSON envelope (not a streaming body) so this method is the
748
- * collector — the worker assembles the NDJSON stream.
749
- */
1224
+ * Fan an export admin RPC out to every live shard, returning the
1225
+ * per-shard `{rows}` payloads alongside any per-shard errors. Each shard
1226
+ * returns a JSON envelope (not a streaming body) so this method is the
1227
+ * collector — the worker assembles the NDJSON stream.
1228
+ */
750
1229
  orchestrateExport: (namespace: ShardNamespaceLike, request: ExportFanOutRequest) => Promise<ExportFanOutResult>;
751
1230
  /**
752
- * Fan an import admin RPC out by routing each row to its owning shard. The
753
- * shard registry resolves which shards exist; rows whose table has a
754
- * `shardBy(field)` are bucketed using that field's value as the shard key,
755
- * other tables fall back to the runtime's default `__root__` shard.
756
- */
1231
+ * Fan an import admin RPC out by routing each row to its owning shard. The
1232
+ * shard registry resolves which shards exist; rows whose table has a
1233
+ * `shardBy(field)` are bucketed using that field's value as the shard key,
1234
+ * other tables fall back to the runtime's default `__root__` shard.
1235
+ */
757
1236
  orchestrateImport: (namespace: ShardNamespaceLike, request: ImportFanOutRequest) => Promise<ImportFanOutResult>;
758
1237
  /** Fan a migration admin RPC out to every live shard of a table and roll up the per-shard outcomes. */
759
1238
  orchestrateMigration: (namespace: ShardNamespaceLike, request: MigrationFanOutRequest) => Promise<MigrationFanOutResult>;
760
1239
  /**
761
- * Fan the `__lunora_admin__:rankBefore` admin RPC out to every live shard of
762
- * a table and roll up the per-shard `{before, total}` payloads into the
763
- * global rank (`{position: Σbefore + 1, total: Σtotal}`). The cross-shard
764
- * `rank()` path for a partition that spans shards.
765
- */
1240
+ * Fan the `__lunora_admin__:rankBefore` admin RPC out to every live shard of
1241
+ * a table and roll up the per-shard `{before, total}` payloads into the
1242
+ * global rank (`{position: Σbefore + 1, total: Σtotal}`). The cross-shard
1243
+ * `rank()` path for a partition that spans shards.
1244
+ */
766
1245
  orchestrateRank: (namespace: ShardNamespaceLike, request: RankFanOutRequest) => Promise<RankFanOutResult>;
767
1246
  /**
768
- * Page a ranked query across every live shard of a `.shardBy(...)` table.
769
- * Fans `__lunora_admin__:rankPage` out to each shard, gathers each shard's
770
- * local ranked slice (rows tagged with their rank-key tuple), and k-way
771
- * merges them by that tuple into one globally-ranked page of `take` rows.
772
- * The opaque `continueCursor` is a composite of per-shard cursors so the
773
- * next page resumes each shard strictly-after the last row the global page
774
- * consumed from it — pages never drop or duplicate a row at a shard
775
- * boundary. The cross-shard `rankPage()` path (PLAN5 §7.1 / PLAN2 #3).
776
- */
1247
+ * Page a ranked query across every live shard of a `.shardBy(...)` table.
1248
+ * Fans `__lunora_admin__:rankPage` out to each shard, gathers each shard's
1249
+ * local ranked slice (rows tagged with their rank-key tuple), and k-way
1250
+ * merges them by that tuple into one globally-ranked page of `take` rows.
1251
+ * The opaque `continueCursor` is a composite of per-shard cursors so the
1252
+ * next page resumes each shard strictly-after the last row the global page
1253
+ * consumed from it — pages never drop or duplicate a row at a shard
1254
+ * boundary. The cross-shard `rankPage()` path (PLAN5 §7.1 / PLAN2 #3).
1255
+ */
777
1256
  orchestrateRankPage: (namespace: ShardNamespaceLike, request: RankPageFanOutRequest) => Promise<RankPageFanOutResult>;
778
1257
  /**
779
- * Fan the `__lunora_admin__:getMetrics` admin RPC out to every live shard of
780
- * a table and collect each shard's lifetime `requests` total into a per-shard
781
- * `{ shardKey, requests }` distribution. The feed the studio's `hot_shard`
782
- * advisor lint needs: a single shard's snapshot can't reveal cross-shard
783
- * skew, so this fans the cheap metrics read out and returns the whole shard
784
- * set's request volumes (a failed shard surfaces as `requests: 0`).
785
- */
1258
+ * Fan the `__lunora_admin__:getMetrics` admin RPC out to every live shard of
1259
+ * a table and collect each shard's lifetime `requests` total into a per-shard
1260
+ * `{ shardKey, requests }` distribution. The feed the studio's `hot_shard`
1261
+ * advisor lint needs: a single shard's snapshot can't reveal cross-shard
1262
+ * skew, so this fans the cheap metrics read out and returns the whole shard
1263
+ * set's request volumes (a failed shard surfaces as `requests: 0`).
1264
+ */
786
1265
  orchestrateShardTraffic: (namespace: ShardNamespaceLike, request: ShardTrafficFanOutRequest) => Promise<ShardTrafficFanOutResult>;
787
1266
  readonly registry: ShardRegistry;
788
1267
  }
789
1268
  /**
790
- * Cross-shard export request. `tables` is the union of every table the caller
791
- * wants exported (shard-local **or** global); `headers` carries the admin
792
- * bearer the per-shard gate expects. Shard registries are queried for the
793
- * complete set of live shards across all listed shard-local tables.
794
- */
1269
+ * Cross-shard export request. `tables` is the union of every table the caller
1270
+ * wants exported (shard-local **or** global); `headers` carries the admin
1271
+ * bearer the per-shard gate expects. Shard registries are queried for the
1272
+ * complete set of live shards across all listed shard-local tables.
1273
+ */
795
1274
  interface ExportFanOutRequest {
796
1275
  args?: Record<string, unknown>;
797
1276
  headers?: Record<string, string>;
798
1277
  /**
799
- * Tables driving the fan-out. Shards are derived from the union of each
800
- * table's live shard keys — so an export of `["users","messages"]` reaches
801
- * every shard that holds either table. Globals are skipped here; the
802
- * worker reads them from D1 directly.
803
- */
1278
+ * Tables driving the fan-out. Shards are derived from the union of each
1279
+ * table's live shard keys — so an export of `["users","messages"]` reaches
1280
+ * every shard that holds either table. Globals are skipped here; the
1281
+ * worker reads them from D1 directly.
1282
+ */
804
1283
  tables: ReadonlyArray<string>;
805
1284
  }
806
1285
  /** Per-shard export outcome. */
@@ -822,11 +1301,11 @@ interface ExportFanOutResult {
822
1301
  shards: ReadonlyArray<ShardExportOutcome>;
823
1302
  }
824
1303
  /**
825
- * Cross-shard change-data-capture request. `tables` drives shard discovery (the
826
- * union of their live shard keys, like export); `cursors` maps each shard key
827
- * to the `seq` it was last read through (absent → from the beginning). `limit`
828
- * caps each shard's page.
829
- */
1304
+ * Cross-shard change-data-capture request. `tables` drives shard discovery (the
1305
+ * union of their live shard keys, like export); `cursors` maps each shard key
1306
+ * to the `seq` it was last read through (absent → from the beginning). `limit`
1307
+ * caps each shard's page.
1308
+ */
830
1309
  interface CdcSyncFanOutRequest {
831
1310
  cursors?: Record<string, number>;
832
1311
  headers?: Record<string, string>;
@@ -850,16 +1329,16 @@ interface CdcSyncFanOutResult {
850
1329
  shards: ReadonlyArray<ShardCdcOutcome>;
851
1330
  }
852
1331
  /**
853
- * Cross-shard import request. Rows have already been bucketed by the runtime
854
- * into one batch per shard key — the coordinator's job is to forward each
855
- * batch and roll up the per-shard insert counts + errors.
856
- */
1332
+ * Cross-shard import request. Rows have already been bucketed by the runtime
1333
+ * into one batch per shard key — the coordinator's job is to forward each
1334
+ * batch and roll up the per-shard insert counts + errors.
1335
+ */
857
1336
  interface ImportFanOutRequest {
858
1337
  /**
859
- * Per-shard batches keyed by shard key. Each entry will be POSTed as the
860
- * `rows` arg of `__lunora_admin__:importShard`. The shard's
861
- * starting-line-number for error attribution is carried in `startLine`.
862
- */
1338
+ * Per-shard batches keyed by shard key. Each entry will be POSTed as the
1339
+ * `rows` arg of `__lunora_admin__:importShard`. The shard's
1340
+ * starting-line-number for error attribution is carried in `startLine`.
1341
+ */
863
1342
  batches: ReadonlyArray<{
864
1343
  rows: ReadonlyArray<{
865
1344
  doc: Record<string, unknown>;
@@ -904,10 +1383,10 @@ interface ImportFanOutResult {
904
1383
  shards: ReadonlyArray<ShardImportOutcome>;
905
1384
  }
906
1385
  /**
907
- * Cross-shard CDC replay request (point-in-time recovery). Changes are
908
- * pre-bucketed by the runtime into one batch per shard key — the coordinator
909
- * forwards each batch to `__lunora_admin__:applyCdc` and rolls up the counts.
910
- */
1386
+ * Cross-shard CDC replay request (point-in-time recovery). Changes are
1387
+ * pre-bucketed by the runtime into one batch per shard key — the coordinator
1388
+ * forwards each batch to `__lunora_admin__:applyCdc` and rolls up the counts.
1389
+ */
911
1390
  interface ApplyCdcFanOutRequest {
912
1391
  batches: ReadonlyArray<{
913
1392
  changes: ReadonlyArray<Record<string, unknown>>;
@@ -922,17 +1401,17 @@ interface ApplyCdcFanOutResult {
922
1401
  ok: number;
923
1402
  }
924
1403
  /**
925
- * Cross-shard traffic request. Like {@link MigrationFanOutRequest} there is no
926
- * caller-supplied merge — each shard's `__lunora_admin__:getMetrics` payload
927
- * carries its own lifetime `requests` total, and {@link rollUpShardTraffic}
928
- * collects them into one `{ shardKey, requests }` entry per shard. `headers`
929
- * must carry the admin bearer the per-shard `getMetrics` gate requires.
930
- *
931
- * `table` drives shard discovery: the registry's live shard keys for the table
932
- * are the shards fanned out to. This is the feed the studio's `hot_shard`
933
- * runtime advisor consumes to compute cross-shard skew — a single shard's
934
- * snapshot can't, so the panel fans this out on demand.
935
- */
1404
+ * Cross-shard traffic request. Like {@link MigrationFanOutRequest} there is no
1405
+ * caller-supplied merge — each shard's `__lunora_admin__:getMetrics` payload
1406
+ * carries its own lifetime `requests` total, and {@link rollUpShardTraffic}
1407
+ * collects them into one `{ shardKey, requests }` entry per shard. `headers`
1408
+ * must carry the admin bearer the per-shard `getMetrics` gate requires.
1409
+ *
1410
+ * `table` drives shard discovery: the registry's live shard keys for the table
1411
+ * are the shards fanned out to. This is the feed the studio's `hot_shard`
1412
+ * runtime advisor consumes to compute cross-shard skew — a single shard's
1413
+ * snapshot can't, so the panel fans this out on demand.
1414
+ */
936
1415
  interface ShardTrafficFanOutRequest {
937
1416
  headers?: Record<string, string>;
938
1417
  /** Table whose live shard keys the traffic fan-out runs across. */
@@ -951,40 +1430,26 @@ interface ShardTrafficFanOutResult {
951
1430
  /** Shards that returned a 2xx `getMetrics` snapshot. */
952
1431
  ok: number;
953
1432
  /**
954
- * Per-shard request totals, in registry order. Shaped to plug straight into
955
- * the advisor's `LintContext.shardTraffic` so the `hot_shard` lint can
956
- * compute the cross-shard share. A failed shard still appears (with
957
- * `requests: 0`) so callers see the full shard set.
958
- */
1433
+ * Per-shard request totals, in registry order. Shaped to plug straight into
1434
+ * the advisor's `LintContext.shardTraffic` so the `hot_shard` lint can
1435
+ * compute the cross-shard share. A failed shard still appears (with
1436
+ * `requests: 0`) so callers see the full shard set.
1437
+ */
959
1438
  shards: ReadonlyArray<ShardTrafficEntry>;
960
1439
  }
961
1440
  declare const createQueryCoordinator: (options: QueryCoordinatorOptions) => QueryCoordinator;
962
- /**
963
- * Secure-by-default HTTP edge for the Lunora worker.
964
- *
965
- * The worker's top-level `fetch` (see `./create-worker`) is the single choke
966
- * point every response passes through — RPC, auth, admin, `httpRoute` handlers,
967
- * and the SSR fallback alike. This module supplies what is applied there:
968
- * `decorateResponse` adds baseline security headers plus, for allowed
969
- * cross-origin requests, the matching `Access-Control-Allow-*` headers (never
970
- * overwriting a header the inner handler set); `handleCorsPreflight` answers
971
- * `OPTIONS` preflights for allowlisted origins; `enforceOrigin` is a CSRF guard
972
- * that rejects state-changing, cookie-authenticated requests from untrusted
973
- * origins.
974
- *
975
- * Every layer is on by default and individually disable-able through the
976
- * `SecurityOptions` passed to `createWorker`. Resolution (`resolveSecurity`) is
977
- * pure and platform-agnostic — it touches only the global `Request`/`Response`/
978
- * `Headers`/`URL`, so it unit-tests under plain Node without workerd.
979
- */
980
1441
  /** Per-header overrides for {@link SecurityHeadersOptions}. `false` omits the header. */
981
1442
  interface SecurityHeadersOptions {
982
1443
  /**
983
- * `Content-Security-Policy`. Omitted (`undefined`) applies a restrictive
984
- * default to **non-HTML** responses only, so an SSR page is never broken by
985
- * a policy it didn't opt into. Pass a string to apply that policy to every
986
- * response (HTML included); `false` to never send one.
987
- */
1444
+ * `Content-Security-Policy`. Omitted (`undefined`) applies a restrictive
1445
+ * `default-src 'none'` policy to **non-HTML** responses, and a conservative
1446
+ * hardening policy to **HTML** responses (`base-uri 'none'; frame-ancestors
1447
+ * 'self'; object-src 'none'`) this does NOT set `default-src`/`script-src`,
1448
+ * so it never blocks an SSR page's own scripts/styles/images/fetches, it only
1449
+ * locks down the `base` element, framing (mirroring the `SAMEORIGIN`
1450
+ * X-Frame-Options default), and legacy plugins. Pass a string to apply that
1451
+ * exact policy to every response (HTML included); `false` to never send one.
1452
+ */
988
1453
  csp?: string | false;
989
1454
  /** `X-Frame-Options` (clickjacking). Defaults to `SAMEORIGIN`. `false` omits it. */
990
1455
  frameOptions?: "DENY" | "SAMEORIGIN" | false;
@@ -1018,9 +1483,9 @@ interface CsrfOptions {
1018
1483
  trustedOrigins?: string[];
1019
1484
  }
1020
1485
  /**
1021
- * The `security` option on `createWorker`. Every field is optional and defaults
1022
- * to a secure posture; set a field to `false` to opt out of that layer.
1023
- */
1486
+ * The `security` option on `createWorker`. Every field is optional and defaults
1487
+ * to a secure posture; set a field to `false` to opt out of that layer.
1488
+ */
1024
1489
  interface SecurityOptions {
1025
1490
  /** CORS. Defaults to **deny cross-origin**; supply an allowlist to permit specific origins. `false` disables CORS handling. */
1026
1491
  cors?: CorsOptions | false;
@@ -1032,7 +1497,7 @@ interface SecurityOptions {
1032
1497
  interface ResolvedHeaders {
1033
1498
  coop: string | undefined;
1034
1499
  csp: {
1035
- htmlToo: boolean;
1500
+ htmlValue: string | undefined;
1036
1501
  value: string;
1037
1502
  } | undefined;
1038
1503
  enabled: boolean;
@@ -1048,13 +1513,13 @@ interface ResolvedCors {
1048
1513
  enabled: boolean;
1049
1514
  isAllowed: (origin: string) => boolean;
1050
1515
  /**
1051
- * Like {@link ResolvedCors.isAllowed} but NEVER satisfied by a wildcard `*`
1052
- * allowlist — an origin counts only when matched by an explicit, non-wildcard
1053
- * rule (a named origin in the list, or a custom predicate the developer
1054
- * wrote). Used by the CSRF guard: a wildcard CORS allowlist means "any origin
1055
- * may read my non-credentialed responses", which must NOT be conflated with
1056
- * "I trust any origin to make authenticated state changes".
1057
- */
1516
+ * Like {@link ResolvedCors.isAllowed} but NEVER satisfied by a wildcard `*`
1517
+ * allowlist — an origin counts only when matched by an explicit, non-wildcard
1518
+ * rule (a named origin in the list, or a custom predicate the developer
1519
+ * wrote). Used by the CSRF guard: a wildcard CORS allowlist means "any origin
1520
+ * may read my non-credentialed responses", which must NOT be conflated with
1521
+ * "I trust any origin to make authenticated state changes".
1522
+ */
1058
1523
  isExplicitlyAllowed: (origin: string) => boolean;
1059
1524
  maxAge: number;
1060
1525
  }
@@ -1069,86 +1534,88 @@ interface ResolvedSecurity {
1069
1534
  headers: ResolvedHeaders;
1070
1535
  }
1071
1536
  /**
1072
- * Normalize the public {@link SecurityOptions} into the resolved form the
1073
- * request path applies. Pure — throws only on an invalid combination (wildcard
1074
- * CORS + credentials) so the misconfiguration surfaces at worker construction
1075
- * rather than silently shipping an unenforceable policy.
1076
- *
1077
- * `env` supplies the deployment-level security vars: `LUNORA_SECURITY_HEADERS` /
1078
- * `LUNORA_SECURITY_CSRF` opt out of those layers (set either to `off`/`false`/`0`),
1079
- * and `LUNORA_ALLOWED_ORIGINS` / `LUNORA_CORS_ALLOW_CREDENTIALS` configure CORS
1080
- * when it isn't set in code. **Code config wins** — an explicit `security.*` in
1081
- * {@link SecurityOptions} overrides the matching env knob — so the env var only
1082
- * relaxes or fills the secure default, and the DO security audit (which reads the
1083
- * same vars) and the running worker stay in agreement.
1084
- */
1537
+ * Normalize the public {@link SecurityOptions} into the resolved form the
1538
+ * request path applies. Pure — throws only on an invalid combination (wildcard
1539
+ * CORS + credentials) so the misconfiguration surfaces at worker construction
1540
+ * rather than silently shipping an unenforceable policy.
1541
+ *
1542
+ * `env` supplies the deployment-level security vars: `LUNORA_SECURITY_HEADERS` /
1543
+ * `LUNORA_SECURITY_CSRF` opt out of those layers (set either to `off`/`false`/`0`),
1544
+ * and `LUNORA_ALLOWED_ORIGINS` / `LUNORA_CORS_ALLOW_CREDENTIALS` configure CORS
1545
+ * when it isn't set in code. **Code config wins** — an explicit `security.*` in
1546
+ * {@link SecurityOptions} overrides the matching env knob — so the env var only
1547
+ * relaxes or fills the secure default, and the DO security audit (which reads the
1548
+ * same vars) and the running worker stay in agreement.
1549
+ */
1085
1550
  declare const resolveSecurity: (security: SecurityOptions | undefined, env?: Record<string, unknown>) => ResolvedSecurity;
1086
1551
  /**
1087
- * CSRF defense: reject an unsafe (state-changing), **cookie-authenticated**
1088
- * request whose `Origin`/`Referer` is neither same-origin nor allowlisted.
1089
- *
1090
- * Scoped deliberately to cookie-bearing browser requests — the only vector a
1091
- * cross-site forgery can ride, since a browser auto-attaches cookies but never a
1092
- * bearer token or custom header. Bearer/server-to-server traffic (no `Cookie`)
1093
- * is exempt, as are safe methods. Returns a `403` `Response` to short-circuit,
1094
- * or `undefined` when the request may proceed.
1095
- * @returns a `403` Response when the origin is untrusted, or `undefined` when the request is allowed.
1096
- */
1552
+ * CSRF defense: reject an unsafe (state-changing), **cookie-authenticated**
1553
+ * request whose `Origin`/`Referer` is neither same-origin nor allowlisted.
1554
+ *
1555
+ * Scoped deliberately to cookie-bearing browser requests — the only vector a
1556
+ * cross-site forgery can ride, since a browser auto-attaches cookies but never a
1557
+ * bearer token or custom header. Bearer/server-to-server traffic (no `Cookie`)
1558
+ * is exempt, as are safe methods. Returns a `403` `Response` to short-circuit,
1559
+ * or `undefined` when the request may proceed.
1560
+ * @returns a `403` Response when the origin is untrusted, or `undefined` when the request is allowed.
1561
+ */
1097
1562
  declare const enforceOrigin: (request: Request, resolved: ResolvedSecurity) => Response | undefined;
1098
1563
  /**
1099
- * Answer a CORS preflight (`OPTIONS` carrying `Access-Control-Request-Method`)
1100
- * for an allowlisted origin with a `204`. Returns `undefined` for non-preflight
1101
- * requests, a disabled CORS layer, or a disallowed origin — letting the request
1102
- * fall through to normal routing.
1103
- * @returns a `204` Response for valid preflights, or `undefined` to fall through.
1104
- */
1564
+ * Answer a CORS preflight (`OPTIONS` carrying `Access-Control-Request-Method`)
1565
+ * for an allowlisted origin with a `204`. Returns `undefined` for non-preflight
1566
+ * requests, a disabled CORS layer, or a disallowed origin — letting the request
1567
+ * fall through to normal routing.
1568
+ * @returns a `204` Response for valid preflights, or `undefined` to fall through.
1569
+ */
1105
1570
  declare const handleCorsPreflight: (request: Request, resolved: ResolvedSecurity) => Response | undefined;
1106
1571
  /**
1107
- * Apply baseline security headers and (for allowed cross-origin requests) CORS
1108
- * headers to an outgoing response, without overwriting anything the inner
1109
- * handler already set.
1110
- *
1111
- * WebSocket upgrade responses (`status 101` / a `webSocket` field) are returned
1112
- * untouched: re-wrapping them in a new `Response` would drop the socket and the
1113
- * hibernation handshake.
1114
- */
1572
+ * Apply baseline security headers and (for allowed cross-origin requests) CORS
1573
+ * headers to an outgoing response, without overwriting anything the inner
1574
+ * handler already set.
1575
+ *
1576
+ * WebSocket upgrade responses (`status 101` / a `webSocket` field) are returned
1577
+ * untouched: re-wrapping them in a new `Response` would drop the socket and the
1578
+ * hibernation handshake.
1579
+ */
1115
1580
  declare const decorateResponse: (response: Response, request: Request, resolved: ResolvedSecurity) => Response;
1116
1581
  /**
1117
- * Wire-format RPC envelope. Posted to `POST /_lunora/rpc`.
1118
- *
1119
- * `functionPath` is the `&lt;file>:&lt;function>` identifier emitted by codegen,
1120
- * e.g. `"messages:list"`. `shardKey` is optional — when omitted the runtime
1121
- * routes to {@link WorkerOptions.defaultShardKey} (default `"__root__"`).
1122
- *
1123
- * `fanOut` opts the envelope into cross-shard routing via the
1124
- * {@link WorkerOptions.queryCoordinator}; mutually exclusive with
1125
- * `shardKey` (specifying both is a 400 — fan-out *is* the shard choice).
1126
- */
1582
+ * Wire-format RPC envelope. Posted to `POST /_lunora/rpc`.
1583
+ *
1584
+ * `functionPath` is the `&lt;file>:&lt;function>` identifier emitted by codegen,
1585
+ * e.g. `"messages:list"`. `shardKey` is optional — when omitted the runtime
1586
+ * routes to {@link WorkerOptions.defaultShardKey} (default `"__root__"`).
1587
+ *
1588
+ * `fanOut` opts the envelope into cross-shard routing via the
1589
+ * {@link WorkerOptions.queryCoordinator}; mutually exclusive with
1590
+ * `shardKey` (specifying both is a 400 — fan-out *is* the shard choice).
1591
+ */
1127
1592
  interface RpcEnvelope {
1128
1593
  args?: Record<string, unknown>;
1129
1594
  fanOut?: FanOutSpec;
1130
1595
  functionPath: string;
1131
1596
  shardKey?: string;
1132
1597
  }
1133
- interface ExecutionContextLike {
1134
- passThroughOnException: () => void;
1135
- waitUntil: (promise: Promise<unknown>) => void;
1136
- }
1137
1598
  type Route = (request: Request, env: unknown, context: ExecutionContextLike) => Promise<Response> | Response;
1138
1599
  /**
1139
- * Context handed to HTTP-action handlers. Built per request by the worker; its
1140
- * `run*` methods forward an RPC envelope to the shard, so handlers reach
1141
- * queries/mutations/actions without a direct DB binding.
1142
- *
1143
- * `reference` is typed `unknown` so this structural contract stays free of a
1144
- * `@lunora/server` dependency while remaining assignable from the fully-typed
1145
- * `HttpActionCtx` on the server side (`{ __lunoraRef }` is read at runtime).
1146
- */
1600
+ * Context handed to HTTP-action handlers. Built per request by the worker; its
1601
+ * `run*` methods forward an RPC envelope to the shard, so handlers reach
1602
+ * queries/mutations/actions without a direct DB binding.
1603
+ *
1604
+ * `reference` is typed `unknown` so this structural contract stays free of a
1605
+ * `@lunora/server` dependency while remaining assignable from the fully-typed
1606
+ * `HttpActionCtx` on the server side (`{ __lunoraRef }` is read at runtime).
1607
+ */
1147
1608
  interface HttpActionContext {
1148
1609
  auth: {
1149
1610
  getIdentity: () => Promise<Record<string, unknown> | null>;
1150
1611
  userId: null | string;
1151
1612
  };
1613
+ cache?: {
1614
+ purge: (options: {
1615
+ purgeEverything?: boolean;
1616
+ tags?: string[];
1617
+ }) => Promise<unknown>;
1618
+ };
1152
1619
  fetch: typeof globalThis.fetch;
1153
1620
  runAction: <R>(reference: unknown, args?: Record<string, unknown>) => Promise<R>;
1154
1621
  runMutation: <R>(reference: unknown, args?: Record<string, unknown>) => Promise<R>;
@@ -1158,49 +1625,20 @@ interface HttpActionLike {
1158
1625
  handler: (context: HttpActionContext, request: Request) => Promise<Response> | Response;
1159
1626
  }
1160
1627
  /**
1161
- * Structural view of `@lunora/server`'s `httpRouter()`. The worker dispatches by
1162
- * calling `fetch` — the same shape as a hono app's `app.fetch` — so the runtime
1163
- * stays free of a hard dependency on the server package (and on hono). The
1164
- * per-request {@link HttpActionContext} is injected on the `__lunoraCtx` env
1165
- * binding; the router lifts it into the handler's context.
1166
- */
1628
+ * Structural view of `@lunora/server`'s `httpRouter()`. The worker dispatches by
1629
+ * calling `fetch` — the same shape as a hono app's `app.fetch` — so the runtime
1630
+ * stays free of a hard dependency on the server package (and on hono). The
1631
+ * per-request {@link HttpActionContext} is injected on the `__lunoraCtx` env
1632
+ * binding; the router lifts it into the handler's context.
1633
+ */
1167
1634
  interface HttpRouterLike {
1168
1635
  fetch(request: Request, env?: unknown, context?: ExecutionContextLike): Promise<Response> | Response;
1169
1636
  }
1170
1637
  /**
1171
- * Identity resolved from the inbound request by {@link WorkerOptions.resolveIdentity}.
1172
- *
1173
- * The `userId` field is special it becomes `ctx.auth.userId` inside the
1174
- * Durable Object. Any other keys (`email`, `name`, custom roles, etc.) are
1175
- * forwarded verbatim as `ctx.auth.getIdentity()`'s return value.
1176
- *
1177
- * Return `null` to signal that the request is anonymous; the runtime will
1178
- * skip both `x-lunora-userid` and `x-lunora-identity` headers, and
1179
- * `ctx.auth.userId` will be `undefined` on the shard side.
1180
- */
1181
- interface ResolvedIdentity {
1182
- /** Arbitrary additional claims. Must be JSON-serialisable. */
1183
- [key: string]: unknown;
1184
- /**
1185
- * JWT-standard expiry in epoch SECONDS. When present (and `expiresAtMs` is
1186
- * absent), the runtime forwards it as the socket's credential expiry — the
1187
- * DO drops the socket once it lapses. Used only on the WebSocket path.
1188
- */
1189
- exp?: number;
1190
- /**
1191
- * Credential expiry in epoch MILLISECONDS. Preferred over `exp` when
1192
- * both are present. Forwarded as the socket's expiry on the WebSocket path
1193
- * so the DO drops the socket once it lapses; omit for non-expiring sessions.
1194
- */
1195
- expiresAtMs?: number;
1196
- /** Stable user identifier (e.g. `"user_2k3..."` or `"u_42"`). */
1197
- userId: string;
1198
- }
1199
- /**
1200
- * Per-table sharding metadata the admin import endpoint needs to route rows.
1201
- * Structural so this package stays free of `@lunora/server`. The codegen-
1202
- * generated worker entry passes a thin projection of the user's schema.
1203
- */
1638
+ * Per-table sharding metadata the admin import endpoint needs to route rows.
1639
+ * Structural so this package stays free of `@lunora/server`. The codegen-
1640
+ * generated worker entry passes a thin projection of the user's schema.
1641
+ */
1204
1642
  interface ShardingInfo {
1205
1643
  /** `global` when the table lives in D1; `shardBy` when keyed by a field; `root` (or absent) otherwise. */
1206
1644
  mode: {
@@ -1209,15 +1647,15 @@ interface ShardingInfo {
1209
1647
  };
1210
1648
  }
1211
1649
  /**
1212
- * Lookup the runtime uses to bucket an import row to its owning shard. Returns
1213
- * `undefined` for unknown tables — the row is reported as a hard error.
1214
- */
1650
+ * Lookup the runtime uses to bucket an import row to its owning shard. Returns
1651
+ * `undefined` for unknown tables — the row is reported as a hard error.
1652
+ */
1215
1653
  type AdminTableResolver = (table: string) => ShardingInfo | undefined;
1216
1654
  /**
1217
- * Streamed bulk export of `.global()` tables, materialised as an async iterable
1218
- * of `{table, doc}` rows. The runtime concatenates this stream after the
1219
- * shard-local stream so the receiver sees a single NDJSON body.
1220
- */
1655
+ * Streamed bulk export of `.global()` tables, materialised as an async iterable
1656
+ * of `{table, doc}` rows. The runtime concatenates this stream after the
1657
+ * shard-local stream so the receiver sees a single NDJSON body.
1658
+ */
1221
1659
  type GlobalExportFunction = (request: {
1222
1660
  tables: ReadonlyArray<string>;
1223
1661
  }) => AsyncIterable<{
@@ -1225,10 +1663,10 @@ type GlobalExportFunction = (request: {
1225
1663
  table: string;
1226
1664
  }>;
1227
1665
  /**
1228
- * Read a page of the `.global()` (D1) change-data-capture log past `sinceSeq`
1229
- * for the admin sync endpoint. Wire it to `@lunora/d1`'s `readD1CdcChanges`.
1230
- * When omitted, the sync endpoint returns only shard-local changes.
1231
- */
1666
+ * Read a page of the `.global()` (D1) change-data-capture log past `sinceSeq`
1667
+ * for the admin sync endpoint. Wire it to `@lunora/d1`'s `readD1CdcChanges`.
1668
+ * When omitted, the sync endpoint returns only shard-local changes.
1669
+ */
1232
1670
  type GlobalCdcSyncFunction = (request: {
1233
1671
  limit?: number;
1234
1672
  sinceSeq: number;
@@ -1237,24 +1675,24 @@ type GlobalCdcSyncFunction = (request: {
1237
1675
  cursor: number;
1238
1676
  }>;
1239
1677
  /**
1240
- * Replay a batch of `.global()` (D1) CDC changes for the admin apply endpoint
1241
- * (point-in-time recovery). Wire it to `applyCdcChanges` on a D1 writer;
1242
- * returns the number applied. When omitted, the apply endpoint replays only
1243
- * shard-local changes.
1244
- */
1678
+ * Replay a batch of `.global()` (D1) CDC changes for the admin apply endpoint
1679
+ * (point-in-time recovery). Wire it to `applyCdcChanges` on a D1 writer;
1680
+ * returns the number applied. When omitted, the apply endpoint replays only
1681
+ * shard-local changes.
1682
+ */
1245
1683
  type GlobalCdcApplyFunction = (request: {
1246
1684
  changes: ReadonlyArray<Record<string, unknown>>;
1247
1685
  }) => Promise<number>;
1248
1686
  /**
1249
- * Bulk import of `.global()` rows. Returns insert counts + errors merged across
1250
- * tables.
1251
- *
1252
- * Each row carries its true physical source `line` so error attribution stays
1253
- * accurate even when global rows are interspersed with shard rows or blank lines
1254
- * in the NDJSON (a single `startLine` can't describe non-contiguous rows). The
1255
- * `startLine` field is the line of the FIRST global row, retained only as a
1256
- * backward-compatible fallback for importers that haven't adopted per-row lines.
1257
- */
1687
+ * Bulk import of `.global()` rows. Returns insert counts + errors merged across
1688
+ * tables.
1689
+ *
1690
+ * Each row carries its true physical source `line` so error attribution stays
1691
+ * accurate even when global rows are interspersed with shard rows or blank lines
1692
+ * in the NDJSON (a single `startLine` can't describe non-contiguous rows). The
1693
+ * `startLine` field is the line of the FIRST global row, retained only as a
1694
+ * backward-compatible fallback for importers that haven't adopted per-row lines.
1695
+ */
1258
1696
  type GlobalImportFunction = (request: {
1259
1697
  rows: ReadonlyArray<{
1260
1698
  doc: Record<string, unknown>;
@@ -1283,11 +1721,11 @@ interface StorageObject {
1283
1721
  size: number;
1284
1722
  }
1285
1723
  /**
1286
- * One registered function, as the discovery endpoint surfaces it. Structurally
1287
- * a subset of codegen's `RegisteredLunoraFunction` — only `kind` and
1288
- * `visibility` matter here, so the generated `LUNORA_FUNCTIONS` map satisfies
1289
- * the {@link FunctionRegistryLike} value shape.
1290
- */
1724
+ * One registered function, as the discovery endpoint surfaces it. Structurally
1725
+ * a subset of codegen's `RegisteredLunoraFunction` — only `kind` and
1726
+ * `visibility` matter here, so the generated `LUNORA_FUNCTIONS` map satisfies
1727
+ * the {@link FunctionRegistryLike} value shape.
1728
+ */
1291
1729
  interface FunctionDescriptor {
1292
1730
  /** The function's declared argument schema, derived from its `v.*` validators. */
1293
1731
  args: FunctionArgumentDescriptor[];
@@ -1302,24 +1740,56 @@ interface FunctionRegistryEntry {
1302
1740
  /** The function's `v.*` args validator map; read structurally for the signature view. */
1303
1741
  args?: unknown;
1304
1742
  /**
1305
- * The generated registry carries `"stream"` alongside query/mutation/action;
1306
- * the discovery endpoint surfaces the latter three only (a `stream` function
1307
- * isn't runnable from the function runner), but accepting the kind here lets
1308
- * callers pass the generated `LUNORA_FUNCTIONS` map without a cast.
1309
- */
1743
+ * The generated registry carries `"stream"` alongside query/mutation/action;
1744
+ * the discovery endpoint surfaces the latter three only (a `stream` function
1745
+ * isn't runnable from the function runner), but accepting the kind here lets
1746
+ * callers pass the generated `LUNORA_FUNCTIONS` map without a cast.
1747
+ */
1310
1748
  kind: "action" | "mutation" | "query" | "stream";
1311
1749
  visibility?: "internal" | "public";
1750
+ /**
1751
+ * x402 payment tag set by the `.x402({ price })` builder modifier. Present
1752
+ * only on paid public procedures; the origin worker answers an unpaid RPC
1753
+ * for such a function with a real `402` challenge (via the injected
1754
+ * {@link WorkerOptions.x402Charge} gate) before dispatching, then verifies +
1755
+ * settles at the origin boundary so the shard never sees payment state.
1756
+ * Rides along on the registered function object's identity — codegen casts
1757
+ * the real `fn` into `LUNORA_FUNCTIONS`, so reading it needs no change to the
1758
+ * generated shape (same as `fn.rls`).
1759
+ */
1760
+ x402?: {
1761
+ readonly price: number | string;
1762
+ };
1312
1763
  }
1313
1764
  /**
1314
- * The generated `LUNORA_FUNCTIONS` dispatch table, narrowed to what the
1315
- * discovery endpoint reads. Pass the map straight from `_generated/functions.ts`.
1316
- */
1765
+ * The generated `LUNORA_FUNCTIONS` dispatch table, narrowed to what the
1766
+ * discovery endpoint reads. Pass the map straight from `_generated/functions.ts`.
1767
+ */
1317
1768
  type FunctionRegistryLike = Record<string, FunctionRegistryEntry>;
1318
1769
  /**
1319
- * Lists objects in the storage bucket for the admin file browser. Structurally
1320
- * compatible with `@lunora/storage`'s `Storage["list"]` the runtime stays free
1321
- * of a hard dependency on the storage package.
1322
- */
1770
+ * Injected x402 charge gate — the seam that paywalls a `.x402({ price })`-tagged
1771
+ * procedure at the origin worker without the runtime importing `@lunora/x402`
1772
+ * (which would pull viem/solana into every worker bundle). Build it with
1773
+ * `createProcedureChargeGate(config)` from `@lunora/x402/charge` and pass it as
1774
+ * {@link WorkerOptions.x402Charge}.
1775
+ *
1776
+ * Given the inbound `request`, the paid procedure's `spec` (its `functionPath` —
1777
+ * used as the x402 challenge `resource` — and USD `price`), and a `dispatch`
1778
+ * that runs the real shard forward, it returns a real `402` + `PAYMENT-REQUIRED`
1779
+ * challenge when the request is unpaid, or the dispatched response (with
1780
+ * `X-PAYMENT-RESPONSE` attached) once the client's `X-PAYMENT` is verified and
1781
+ * settled. `dispatch` runs only after payment is verified — an unpaid or
1782
+ * invalid request never reaches the shard.
1783
+ */
1784
+ type X402ChargeGate = (request: Request, spec: {
1785
+ functionPath: string;
1786
+ price: number | string;
1787
+ }, dispatch: () => Promise<Response>) => Promise<Response>;
1788
+ /**
1789
+ * Lists objects in the storage bucket for the admin file browser. Structurally
1790
+ * compatible with `@lunora/storage`'s `Storage["list"]` — the runtime stays free
1791
+ * of a hard dependency on the storage package.
1792
+ */
1323
1793
  type StorageListFunction = (prefix?: string, options?: {
1324
1794
  bucket?: string;
1325
1795
  cursor?: string;
@@ -1329,20 +1799,20 @@ type StorageListFunction = (prefix?: string, options?: {
1329
1799
  objects: StorageObject[];
1330
1800
  }>;
1331
1801
  /**
1332
- * Deletes one object from a storage bucket for the admin file browser.
1333
- * Structurally compatible with `@lunora/storage`'s `Storage["delete"]`, so
1334
- * passing `createStorage(...).delete` satisfies it. The optional `bucket` selects
1335
- * a named bucket for a multi-bucket deployment (ignored by single-bucket hosts).
1336
- */
1802
+ * Deletes one object from a storage bucket for the admin file browser.
1803
+ * Structurally compatible with `@lunora/storage`'s `Storage["delete"]`, so
1804
+ * passing `createStorage(...).delete` satisfies it. The optional `bucket` selects
1805
+ * a named bucket for a multi-bucket deployment (ignored by single-bucket hosts).
1806
+ */
1337
1807
  type StorageDeleteFunction = (key: string, options?: {
1338
1808
  bucket?: string;
1339
1809
  }) => Promise<void> | void;
1340
1810
  /**
1341
- * Uploads one object to a storage bucket for the admin file browser. Mirrors
1342
- * `@lunora/storage`'s `Storage["upload"]` (only the bits the admin endpoint
1343
- * needs): the key, the raw bytes, an optional content-type, and an optional
1344
- * target `bucket` for multi-bucket deployments.
1345
- */
1811
+ * Uploads one object to a storage bucket for the admin file browser. Mirrors
1812
+ * `@lunora/storage`'s `Storage["upload"]` (only the bits the admin endpoint
1813
+ * needs): the key, the raw bytes, an optional content-type, and an optional
1814
+ * target `bucket` for multi-bucket deployments.
1815
+ */
1346
1816
  type StorageUploadFunction = (key: string, body: ArrayBuffer, options?: {
1347
1817
  bucket?: string;
1348
1818
  contentType?: string;
@@ -1354,11 +1824,11 @@ type StorageUploadFunction = (key: string, body: ArrayBuffer, options?: {
1354
1824
  key: string;
1355
1825
  };
1356
1826
  /**
1357
- * Mints a (signed or public) URL for one object so the admin file browser can
1358
- * offer a "copy URL" action. The optional `expiresInSeconds` lets the caller pick
1359
- * a share-link lifetime (the host clamps it); `bucket` selects a named bucket.
1360
- * Structurally compatible with `@lunora/storage`'s `Storage["getSignedUrl"]`.
1361
- */
1827
+ * Mints a (signed or public) URL for one object so the admin file browser can
1828
+ * offer a "copy URL" action. The optional `expiresInSeconds` lets the caller pick
1829
+ * a share-link lifetime (the host clamps it); `bucket` selects a named bucket.
1830
+ * Structurally compatible with `@lunora/storage`'s `Storage["getSignedUrl"]`.
1831
+ */
1362
1832
  type StorageSignedUrlFunction = (key: string, options?: {
1363
1833
  bucket?: string;
1364
1834
  expiresInSeconds?: number;
@@ -1390,11 +1860,11 @@ interface GlobalFacetResult {
1390
1860
  }[];
1391
1861
  }
1392
1862
  /**
1393
- * Introspect `.global()` (D1-backed) tables for the data browser. Structurally
1394
- * compatible with `@lunora/d1`'s `listGlobalTables` / `readGlobalTablePage` /
1395
- * `facetGlobalColumn` (curried with the D1 exec + schema) — the runtime stays
1396
- * free of a hard dependency on the D1 package.
1397
- */
1863
+ * Introspect `.global()` (D1-backed) tables for the data browser. Structurally
1864
+ * compatible with `@lunora/d1`'s `listGlobalTables` / `readGlobalTablePage` /
1865
+ * `facetGlobalColumn` (curried with the D1 exec + schema) — the runtime stays
1866
+ * free of a hard dependency on the D1 package.
1867
+ */
1398
1868
  interface GlobalIntrospector {
1399
1869
  facetColumn: (options: {
1400
1870
  column: string;
@@ -1411,12 +1881,12 @@ interface GlobalIntrospector {
1411
1881
  }) => Promise<GlobalTablePage>;
1412
1882
  }
1413
1883
  /**
1414
- * One vector index as the studio's vector browser lists it: the static schema
1415
- * metadata (name/table/field/dimensions/metric/metadata) merged with the live
1416
- * Vectorize `describe()` stats (`vectorsCount`, processing watermark) when the
1417
- * binding is reachable. The live fields are optional so a never-bound index
1418
- * still lists with its declared shape.
1419
- */
1884
+ * One vector index as the studio's vector browser lists it: the static schema
1885
+ * metadata (name/table/field/dimensions/metric/metadata) merged with the live
1886
+ * Vectorize `describe()` stats (`vectorsCount`, processing watermark) when the
1887
+ * binding is reachable. The live fields are optional so a never-bound index
1888
+ * still lists with its declared shape.
1889
+ */
1420
1890
  interface VectorIndexSummary {
1421
1891
  dimensions?: number;
1422
1892
  field?: string;
@@ -1436,13 +1906,13 @@ interface VectorQueryMatch {
1436
1906
  score: number;
1437
1907
  }
1438
1908
  /**
1439
- * Introspect Vectorize indexes for the studio's vector browser. Built in the
1440
- * worker entry from the generated `LUNORA_VECTOR_INDEXES` registry (Vectorize
1441
- * cannot enumerate indexes at runtime) paired with the env bindings + the
1442
- * schema's per-index embedders. `queryIndex` is optional: an index with no
1443
- * embedder (a `select`-derived Shape B index, or a deployment that withholds the
1444
- * embedder) lists but cannot be similarity-queried from the studio.
1445
- */
1909
+ * Introspect Vectorize indexes for the studio's vector browser. Built in the
1910
+ * worker entry from the generated `LUNORA_VECTOR_INDEXES` registry (Vectorize
1911
+ * cannot enumerate indexes at runtime) paired with the env bindings + the
1912
+ * schema's per-index embedders. `queryIndex` is optional: an index with no
1913
+ * embedder (a `select`-derived Shape B index, or a deployment that withholds the
1914
+ * embedder) lists but cannot be similarity-queried from the studio.
1915
+ */
1446
1916
  interface VectorIntrospector {
1447
1917
  listIndexes: () => Promise<VectorIndexSummary[]>;
1448
1918
  queryIndex?: (options: {
@@ -1454,51 +1924,57 @@ interface VectorIntrospector {
1454
1924
  }>;
1455
1925
  }
1456
1926
  /**
1457
- * Cron controller handed to the worker's `scheduled()` entry by the Workers
1458
- * runtime. `cron` is the exact trigger expression that fired (matched against
1459
- * {@link WorkerOptions.crons} keys and {@link WorkerOptions.backupCron});
1460
- * `scheduledTime` is the firing time in epoch-ms, used as the backup id so the
1461
- * snapshot is named after the moment it represents rather than wall-clock skew.
1462
- */
1927
+ * Cron controller handed to the worker's `scheduled()` entry by the Workers
1928
+ * runtime. `cron` is the exact trigger expression that fired (matched against
1929
+ * {@link WorkerOptions.crons} keys and {@link WorkerOptions.backupCron});
1930
+ * `scheduledTime` is the firing time in epoch-ms, used as the backup id so the
1931
+ * snapshot is named after the moment it represents rather than wall-clock skew.
1932
+ */
1463
1933
  interface ScheduledControllerLike {
1464
1934
  cron: string;
1465
1935
  noRetry?: () => void;
1466
1936
  scheduledTime: number;
1467
1937
  }
1468
1938
  /**
1469
- * A cron-trigger handler registered on {@link WorkerOptions.crons}. The worker's
1470
- * `scheduled()` entry invokes the handler whose map key equals the firing
1471
- * trigger's `cron` expression. Runs server-side with no end-user identity.
1472
- */
1939
+ * A cron-trigger handler registered on {@link WorkerOptions.crons}. The worker's
1940
+ * `scheduled()` entry invokes the handler whose map key equals the firing
1941
+ * trigger's `cron` expression. Runs server-side with no end-user identity.
1942
+ */
1473
1943
  type CronHandler = (controller: ScheduledControllerLike, env: unknown, context: ExecutionContextLike) => Promise<void> | void;
1474
1944
  /**
1475
- * A single code-defined cron job, shaped like an entry of the generated
1476
- * `LUNORA_CRONS` map. `functionPath` is the `"namespace:fn"` to run, `args` its
1477
- * bound arguments, and `name` the human label from the `cronJobs()` builder.
1478
- * Pass the whole `LUNORA_CRONS` map as {@link WorkerOptions.cronJobs}; the worker
1479
- * dispatches each job on its firing trigger via the same authorized shard path
1480
- * as the scheduler.
1481
- */
1945
+ * A Cloudflare Queues push-consumer handler the worker's `queue()` entry
1946
+ * forwards each delivered `MessageBatch` (typed `unknown` here to keep the
1947
+ * runtime decoupled from `@lunora/queue`'s structural batch type).
1948
+ */
1949
+ type QueueConsumerHandler = (batch: unknown, env: unknown, context: ExecutionContextLike) => Promise<void>;
1950
+ /**
1951
+ * A single code-defined cron job, shaped like an entry of the generated
1952
+ * `LUNORA_CRONS` map. `functionPath` is the `"namespace:fn"` to run, `args` its
1953
+ * bound arguments, and `name` the human label from the `cronJobs()` builder.
1954
+ * Pass the whole `LUNORA_CRONS` map as {@link WorkerOptions.cronJobs}; the worker
1955
+ * dispatches each job on its firing trigger via the same authorized shard path
1956
+ * as the scheduler.
1957
+ */
1482
1958
  interface CronJobDispatch {
1483
1959
  args?: Record<string, unknown>;
1484
1960
  functionPath?: string;
1485
1961
  name: string;
1486
1962
  shardKey?: string;
1487
1963
  /**
1488
- * Set when the job targets a durable workflow instead of a function: the
1489
- * `WORKFLOW_*` binding name on `env`. On a firing trigger the worker starts a
1490
- * NEW workflow instance (the {@link CronJobDispatch.args} become its
1491
- * `params`) rather than dispatching {@link CronJobDispatch.functionPath} to a
1492
- * shard. Mutually exclusive with `functionPath`.
1493
- */
1964
+ * Set when the job targets a durable workflow instead of a function: the
1965
+ * `WORKFLOW_*` binding name on `env`. On a firing trigger the worker starts a
1966
+ * NEW workflow instance (the {@link CronJobDispatch.args} become its
1967
+ * `params`) rather than dispatching {@link CronJobDispatch.functionPath} to a
1968
+ * shard. Mutually exclusive with `functionPath`.
1969
+ */
1494
1970
  workflow?: string;
1495
1971
  }
1496
1972
  /**
1497
- * One scheduled cron invocation as the discovery endpoint surfaces it: a
1498
- * {@link CronJobDispatch} flattened together with the `cron` expression that
1499
- * fires it. Cloudflare exposes no runtime cron introspection, so the injected
1500
- * `cronJobs` map is the only source of truth; the studio renders these read-only.
1501
- */
1973
+ * One scheduled cron invocation as the discovery endpoint surfaces it: a
1974
+ * {@link CronJobDispatch} flattened together with the `cron` expression that
1975
+ * fires it. Cloudflare exposes no runtime cron introspection, so the injected
1976
+ * `cronJobs` map is the only source of truth; the studio renders these read-only.
1977
+ */
1502
1978
  interface CronJobInfo {
1503
1979
  args?: Record<string, unknown>;
1504
1980
  /** The compiled cron expression, e.g. `"0 9 * * *"`. */
@@ -1510,12 +1986,12 @@ interface CronJobInfo {
1510
1986
  workflow?: string;
1511
1987
  }
1512
1988
  /**
1513
- * R2-like sink for scheduled backups. Structurally a subset of `@lunora/storage`'s
1514
- * `R2BucketLike` (and of the raw R2 binding), so passing `env.BACKUPS` straight
1515
- * through satisfies it. `put` writes the NDJSON snapshot and its manifest
1516
- * sidecar; `list`/`delete` drive retention pruning when
1517
- * {@link WorkerOptions.backupRetain} is set.
1518
- */
1989
+ * R2-like sink for scheduled backups. Structurally a subset of `@lunora/storage`'s
1990
+ * `R2BucketLike` (and of the raw R2 binding), so passing `env.BACKUPS` straight
1991
+ * through satisfies it. `put` writes the NDJSON snapshot and its manifest
1992
+ * sidecar; `list`/`delete` drive retention pruning when
1993
+ * {@link WorkerOptions.backupRetain} is set.
1994
+ */
1519
1995
  interface BackupStore {
1520
1996
  delete: (key: string) => Promise<unknown>;
1521
1997
  list: (options?: {
@@ -1537,11 +2013,11 @@ interface BackupStore {
1537
2013
  }) => Promise<unknown>;
1538
2014
  }
1539
2015
  /**
1540
- * Manifest sidecar written next to each scheduled backup's NDJSON object (at
1541
- * `&lt;file>.manifest.json`). Mirrors the manifest entry the CLI records for local
1542
- * backups so both backup planes describe a snapshot the same way;
1543
- * `cron`/`scheduledTime` additionally record which trigger produced it.
1544
- */
2016
+ * Manifest sidecar written next to each scheduled backup's NDJSON object (at
2017
+ * `&lt;file>.manifest.json`). Mirrors the manifest entry the CLI records for local
2018
+ * backups so both backup planes describe a snapshot the same way;
2019
+ * `cron`/`scheduledTime` additionally record which trigger produced it.
2020
+ */
1545
2021
  interface BackupManifest {
1546
2022
  bytes: number;
1547
2023
  createdAt: string;
@@ -1554,353 +2030,456 @@ interface BackupManifest {
1554
2030
  }
1555
2031
  interface WorkerOptions {
1556
2032
  /**
1557
- * Admin bearer token expected by the export/import endpoints. When unset,
1558
- * the endpoints respond with `ADMIN_FORBIDDEN` — the same posture the
1559
- * per-shard admin gate uses.
1560
- */
2033
+ * An additional, async authorization gate for the `/_lunora/admin/*` plane
2034
+ * (the Studio's HTTP + WS endpoints), OR-ed with the static {@link WorkerOptions.adminToken}
2035
+ * bearer. When it resolves `true` for a request, that request is treated as
2036
+ * admin-authorized even without the bearer; when it resolves `false` (or is
2037
+ * unset) the bearer remains the only path. Evaluated once per admin request
2038
+ * and never on the RPC/WebSocket data hot path.
2039
+ *
2040
+ * The intended producer is `@lunora/cloudflare-access`'s `accessAdminGate(...)`,
2041
+ * which verifies the request's `Cf-Access-Jwt-Assertion` JWT and applies an
2042
+ * `isAdmin(claims)` predicate — so the Studio can sit behind Cloudflare Access
2043
+ * instead of (or alongside) a shared admin token. It takes only the request
2044
+ * (verification needs static team-domain/aud config + the remote JWKS, no env
2045
+ * binding), so it composes without threading async through every admin route.
2046
+ */
2047
+ adminGate?: (request: Request) => boolean | Promise<boolean>;
2048
+ /**
2049
+ * Admin bearer token expected by the export/import endpoints. When unset,
2050
+ * the endpoints respond with `ADMIN_FORBIDDEN` — the same posture the
2051
+ * per-shard admin gate uses.
2052
+ */
1561
2053
  adminToken?: string;
1562
2054
  /**
1563
- * Acknowledge explicitly that sharded and fan-out access may be
1564
- * exercised by any caller (including unauthenticated ones) because no
1565
- * authorization callback is configured. When neither {@link WorkerOptions.authorizeShard}
1566
- * nor {@link WorkerOptions.authorizeFanOut} is set, naming a non-default shard or sending
1567
- * a fan-out envelope is authorization-open: this is the historical posture,
1568
- * preserved for backward compatibility. The runtime emits a single loud
1569
- * `console.warn` the first time such a request is seen so the gap is
1570
- * visible in logs. Set this to `true` to assert the posture is intentional
1571
- * and silence that warning. It does NOT change behaviour it is purely an
1572
- * acknowledgement flag and has no effect once an `authorize*` callback is
1573
- * configured.
1574
- */
2055
+ * Opt into an authorization-open posture for sharded and fan-out access.
2056
+ *
2057
+ * By default (this flag unset/`false`) the runtime FAILS CLOSED per
2058
+ * operation: naming a non-default shard (a potential cross-tenant hop) is
2059
+ * rejected with a `403` (`FORBIDDEN_SHARD`) unless
2060
+ * {@link WorkerOptions.authorizeShard} is configured, and a fan-out
2061
+ * envelope is rejected (`FORBIDDEN_FANOUT`) unless
2062
+ * {@link WorkerOptions.authorizeFanOut} is. Set this to `true` to allow
2063
+ * such requests from any caller (including unauthenticated ones)
2064
+ * appropriate only when every table is protected by per-row RLS. The
2065
+ * runtime then emits a single `console.warn` so the open posture stays
2066
+ * visible in logs. The flag is consulted per operation: it has no effect
2067
+ * on an operation whose own `authorize*` callback is configured (that
2068
+ * callback gates directly), but configuring only one of the two callbacks
2069
+ * does NOT cover the other operation.
2070
+ *
2071
+ * NOTE: this is a behaviour change from earlier alphas, where the same
2072
+ * situation was warn-once-then-allow. Apps that relied on client-chosen
2073
+ * shard keys without an `authorize*` callback must set this flag explicitly.
2074
+ */
1575
2075
  allowUnauthenticatedShardAccess?: boolean;
1576
2076
  /**
1577
- * Replay `.global()` (D1) CDC changes for the admin apply endpoint
1578
- * (point-in-time recovery). When omitted, apply covers only shard-local tables.
1579
- */
2077
+ * Replay `.global()` (D1) CDC changes for the admin apply endpoint
2078
+ * (point-in-time recovery). When omitted, apply covers only shard-local tables.
2079
+ */
1580
2080
  applyGlobals?: GlobalCdcApplyFunction;
1581
2081
  /**
1582
- * The auth user-management plane backing the studio's users dashboard:
1583
- * browse via `GET /_lunora/admin/auth/users` + `/sessions`, and (when the
1584
- * implementation provides the optional mutations) create/ban/role/revoke/
1585
- * delete/impersonate via the matching admin-gated `POST /_lunora/admin/auth/*`
1586
- * routes. Wire it with `@lunora/auth`'s `createAuthAdmin(auth)`. Omit it and
1587
- * every `/auth/*` endpoint responds `AUTH_NOT_CONFIGURED`.
1588
- */
2082
+ * The auth user-management plane backing the studio's users dashboard:
2083
+ * browse via `GET /_lunora/admin/auth/users` + `/sessions`, and (when the
2084
+ * implementation provides the optional mutations) create/ban/role/revoke/
2085
+ * delete/impersonate via the matching admin-gated `POST /_lunora/admin/auth/*`
2086
+ * routes. Wire it with `@lunora/auth`'s `createAuthAdmin(auth)`. Omit it and
2087
+ * every `/auth/*` endpoint responds `AUTH_NOT_CONFIGURED`.
2088
+ */
1589
2089
  authAdmin?: AuthAdmin;
1590
2090
  /**
1591
- * Base path the auth routes are mounted under (default `/api/auth`). Used
1592
- * to classify which inbound paths are auth ATTEMPTS for the app-level
1593
- * auth-failure SLO signal (PLAN3 §2.3) — see {@link WorkerOptions.authHandler}.
1594
- * Only meaningful alongside `authHandler`.
1595
- */
2091
+ * Base path the auth routes are mounted under (default `/api/auth`). Used
2092
+ * to classify which inbound paths are auth ATTEMPTS for the app-level
2093
+ * auth-failure SLO signal (PLAN3 §2.3) — see {@link WorkerOptions.authHandler}.
2094
+ * Only meaningful alongside `authHandler`.
2095
+ */
1596
2096
  authBasePath?: string;
1597
2097
  /**
1598
- * Optional prebound `@lunora/auth` handler (`handleAuthRequest(auth, …)`
1599
- * with its `auth` argument already bound) the worker dispatches BEFORE its
1600
- * own routing — auth runs as a top-level `/api/auth/*` route, not through
1601
- * lunora functions. It returns a `Response` for an auth route and
1602
- * `undefined` to let the request fall through to the worker.
1603
- *
1604
- * Wiring it here (rather than in the host entry) lets the runtime instrument
1605
- * it for the app-level auth-failure SLO (PLAN3 §2.3): after the handler
1606
- * answers a genuine auth ATTEMPT route (sign-in / sign-up / callback under
1607
- * {@link WorkerOptions.authBasePath}), the worker fires a fire-and-forget
1608
- * `recordAuthEvent` against the root shard via `ctx.waitUntil` — classifying
1609
- * the outcome by status (`≥ 400` ⇒ `fail`). The recording never blocks or
1610
- * fails the auth response, and is skipped silently when no admin token or
1611
- * shard namespace is configured (the SLO signal is simply absent).
1612
- *
1613
- * Omit it and the host keeps calling `handleAuthRequest` itself; the SLO
1614
- * signal is then absent but auth behaves identically.
1615
- */
2098
+ * Optional prebound `@lunora/auth` handler (`handleAuthRequest(auth, …)`
2099
+ * with its `auth` argument already bound) the worker dispatches BEFORE its
2100
+ * own routing — auth runs as a top-level `/api/auth/*` route, not through
2101
+ * lunora functions. It returns a `Response` for an auth route and
2102
+ * `undefined` to let the request fall through to the worker.
2103
+ *
2104
+ * Wiring it here (rather than in the host entry) lets the runtime instrument
2105
+ * it for the app-level auth-failure SLO (PLAN3 §2.3): after the handler
2106
+ * answers a genuine auth ATTEMPT route (sign-in / sign-up / callback under
2107
+ * {@link WorkerOptions.authBasePath}), the worker fires a fire-and-forget
2108
+ * `recordAuthEvent` against the root shard via `ctx.waitUntil` — classifying
2109
+ * the outcome by status (`≥ 400` ⇒ `fail`). The recording never blocks or
2110
+ * fails the auth response, and is skipped silently when no admin token or
2111
+ * shard namespace is configured (the SLO signal is simply absent).
2112
+ *
2113
+ * Omit it and the host keeps calling `handleAuthRequest` itself; the SLO
2114
+ * signal is then absent but auth behaves identically.
2115
+ */
1616
2116
  authHandler?: (request: Request) => Promise<Response | undefined>;
1617
2117
  /**
1618
- * @deprecated Use {@link WorkerOptions.authAdmin} (an {@link AuthAdmin}),
1619
- * which also lights up the user-management mutation endpoints. Still honored
1620
- * as a read-only fallback for the browse endpoints.
1621
- */
1622
- authIntrospector?: AuthIntrospector;
1623
- /**
1624
- * Optional table-level authorization callback for fan-out RPC envelopes.
1625
- * Called after `resolveIdentity` and before `coordinator.fanOut` walks
1626
- * the registry. Returning `false` rejects the request with 403
1627
- * `FORBIDDEN_FANOUT`. When unset, fan-out is denied by default
1628
- * whenever {@link WorkerOptions.authorizeShard} is configured — fan-out is a
1629
- * privileged operation (it dispatches the caller's function across
1630
- * every live shard for the table) and a per-shard gate is not
1631
- * sufficient to authorize it. Apps that need client-driven fan-out
1632
- * must opt in explicitly via this callback.
1633
- */
2118
+ * Optional table-level authorization callback for fan-out RPC envelopes.
2119
+ * Called after `resolveIdentity` and before `coordinator.fanOut` walks
2120
+ * the registry. Returning `false` rejects the request with 403
2121
+ * `FORBIDDEN_FANOUT`. When unset, fan-out is denied by default
2122
+ * whenever {@link WorkerOptions.authorizeShard} is configured — fan-out is a
2123
+ * privileged operation (it dispatches the caller's function across
2124
+ * every live shard for the table) and a per-shard gate is not
2125
+ * sufficient to authorize it. Apps that need client-driven fan-out
2126
+ * must opt in explicitly via this callback.
2127
+ */
1634
2128
  authorizeFanOut?: (identity: ResolvedIdentity | null, table: string, functionPath: string) => boolean | Promise<boolean>;
1635
2129
  /**
1636
- * Optional per-shard authorization callback. Called from both the RPC
1637
- * dispatch path and the WebSocket upgrade path after `resolveIdentity`
1638
- * has produced an identity but before the request is forwarded to the
1639
- * named shard. Returning `false` (or a promise resolving to `false`)
1640
- * causes the runtime to reject the request with a 403
1641
- * `FORBIDDEN_SHARD` error. When unset, the runtime allows the
1642
- * request — preserving the historical "any client may name any
1643
- * shard" posture.
1644
- *
1645
- * Note: this callback does NOT gate fan-out envelopes — fan-out
1646
- * targets every live shard for a table and must be authorized at the
1647
- * table level via {@link WorkerOptions.authorizeFanOut}. Configuring this callback
1648
- * without `authorizeFanOut` causes fan-out envelopes to be denied by
1649
- * default.
1650
- */
2130
+ * Optional per-shard authorization callback. Called from both the RPC
2131
+ * dispatch path and the WebSocket upgrade path after `resolveIdentity`
2132
+ * has produced an identity but before the request is forwarded to the
2133
+ * named shard. Returning `false` (or a promise resolving to `false`)
2134
+ * causes the runtime to reject the request with a 403
2135
+ * `FORBIDDEN_SHARD` error. When unset, the runtime allows the
2136
+ * request — preserving the historical "any client may name any
2137
+ * shard" posture.
2138
+ *
2139
+ * Note: this callback does NOT gate fan-out envelopes — fan-out
2140
+ * targets every live shard for a table and must be authorized at the
2141
+ * table level via {@link WorkerOptions.authorizeFanOut}. Configuring this callback
2142
+ * without `authorizeFanOut` causes fan-out envelopes to be denied by
2143
+ * default.
2144
+ */
1651
2145
  authorizeShard?: (identity: ResolvedIdentity | null, shardKey: string) => boolean | Promise<boolean>;
1652
2146
  /**
1653
- * Cron expression that triggers the built-in backup. When set alongside
1654
- * {@link WorkerOptions.backupStore} and {@link WorkerOptions.adminToken}, the
1655
- * worker's `scheduled()` entry runs a full export and writes an NDJSON
1656
- * snapshot + manifest sidecar to the backup store whenever a cron trigger
1657
- * with this exact expression fires. Must match an entry in the worker's
1658
- * wrangler `triggers.crons` (and the string is compared verbatim). Omit it
1659
- * and no automatic backup runs.
1660
- */
2147
+ * Cron expression that triggers the built-in backup. When set alongside
2148
+ * {@link WorkerOptions.backupStore} and {@link WorkerOptions.adminToken}, the
2149
+ * worker's `scheduled()` entry runs a full export and writes an NDJSON
2150
+ * snapshot + manifest sidecar to the backup store whenever a cron trigger
2151
+ * with this exact expression fires. Must match an entry in the worker's
2152
+ * wrangler `triggers.crons` (and the string is compared verbatim). Omit it
2153
+ * and no automatic backup runs.
2154
+ */
1661
2155
  backupCron?: string;
1662
2156
  /**
1663
- * Key prefix the scheduled backup writes under (default `"backups/"`). The
1664
- * NDJSON object lands at `&lt;prefix>lunora-backup-&lt;id>.ndjson` and its manifest
1665
- * at the same key plus `.manifest.json`.
1666
- */
2157
+ * Key prefix the scheduled backup writes under (default `"backups/"`). The
2158
+ * NDJSON object lands at `&lt;prefix>lunora-backup-&lt;id>.ndjson` and its manifest
2159
+ * at the same key plus `.manifest.json`.
2160
+ */
1667
2161
  backupPrefix?: string;
1668
2162
  /**
1669
- * Retention bound for scheduled backups: keep only the newest N snapshots
1670
- * under {@link WorkerOptions.backupPrefix}, pruning older NDJSON objects and
1671
- * their manifests after each run. Omit (or `0`) to keep every backup.
1672
- */
2163
+ * Retention bound for scheduled backups: keep only the newest N snapshots
2164
+ * under {@link WorkerOptions.backupPrefix}, pruning older NDJSON objects and
2165
+ * their manifests after each run. Omit (or `0`) to keep every backup.
2166
+ */
1673
2167
  backupRetain?: number;
1674
2168
  /**
1675
- * R2-like store the scheduled backup writes snapshots to. Pass the bound R2
1676
- * bucket (`env.BACKUPS`) directly — its shape satisfies {@link BackupStore}.
1677
- * Without it (or without {@link WorkerOptions.backupCron}) no automatic
1678
- * backup runs.
1679
- */
2169
+ * R2-like store the scheduled backup writes snapshots to. Pass the bound R2
2170
+ * bucket (`env.BACKUPS`) directly — its shape satisfies {@link BackupStore}.
2171
+ * Without it (or without {@link WorkerOptions.backupCron}) no automatic
2172
+ * backup runs.
2173
+ */
1680
2174
  backupStore?: BackupStore;
1681
2175
  /**
1682
- * Table allowlist for the scheduled backup. Omit to back up every table
1683
- * (shard-local + `.global()`). Mirrors the export endpoint's `tables`.
1684
- */
2176
+ * Table allowlist for the scheduled backup. Omit to back up every table
2177
+ * (shard-local + `.global()`). Mirrors the export endpoint's `tables`.
2178
+ */
1685
2179
  backupTables?: ReadonlyArray<string>;
1686
2180
  /**
1687
- * Code-defined cron jobs keyed by cron expression — pass the generated
1688
- * `LUNORA_CRONS` map directly. On a firing trigger the worker runs every job
1689
- * listed under the matching expression by dispatching its `functionPath`/`args`
1690
- * to the shard, server-side, through the same authorization as the scheduler.
1691
- * Runs alongside any {@link WorkerOptions.crons} handler and the backup.
1692
- */
2181
+ * Code-defined cron jobs keyed by cron expression — pass the generated
2182
+ * `LUNORA_CRONS` map directly. On a firing trigger the worker runs every job
2183
+ * listed under the matching expression by dispatching its `functionPath`/`args`
2184
+ * to the shard, server-side, through the same authorization as the scheduler.
2185
+ * Runs alongside any {@link WorkerOptions.crons} handler and the backup.
2186
+ */
1693
2187
  cronJobs?: Record<string, ReadonlyArray<CronJobDispatch>>;
1694
2188
  /**
1695
- * Cron-trigger handlers keyed by their exact cron expression. The worker's
1696
- * `scheduled()` entry dispatches the handler whose key equals the firing
1697
- * trigger's `cron`. Independent of the built-in backup — a handler keyed on
1698
- * the same expression as {@link WorkerOptions.backupCron} runs alongside it.
1699
- */
2189
+ * Cron-trigger handlers keyed by their exact cron expression. The worker's
2190
+ * `scheduled()` entry dispatches the handler whose key equals the firing
2191
+ * trigger's `cron`. Independent of the built-in backup — a handler keyed on
2192
+ * the same expression as {@link WorkerOptions.backupCron} runs alongside it.
2193
+ */
1700
2194
  crons?: Record<string, CronHandler>;
1701
2195
  /**
1702
- * D1 binding for `.global()` tables. Currently unused by the routing
1703
- * layer; downstream packages will read it from `env.DB` directly.
1704
- */
2196
+ * D1 binding for `.global()` tables. Currently unused by the routing
2197
+ * layer; downstream packages will read it from `env.DB` directly.
2198
+ */
1705
2199
  d1?: unknown;
1706
2200
  /** Default shard key used when an envelope omits one. */
1707
2201
  defaultShardKey?: string;
1708
2202
  /**
1709
- * Stream `.global()` rows for the admin export endpoint. When omitted,
1710
- * the export endpoint covers only shard-local tables.
1711
- */
2203
+ * Stream `.global()` rows for the admin export endpoint. When omitted,
2204
+ * the export endpoint covers only shard-local tables.
2205
+ */
1712
2206
  exportGlobals?: GlobalExportFunction;
1713
2207
  /**
1714
- * The generated `LUNORA_FUNCTIONS` map (from `_generated/functions.ts`). When
1715
- * set, the worker exposes the admin-gated `GET /_lunora/admin/functions`
1716
- * endpoint the studio uses to auto-discover queries/mutations/actions
1717
- * (internal functions are filtered out). Omit it and the endpoint responds
1718
- * `FUNCTIONS_NOT_CONFIGURED`.
1719
- */
2208
+ * The generated `LUNORA_FUNCTIONS` map (from `_generated/functions.ts`). When
2209
+ * set, the worker exposes the admin-gated `GET /_lunora/admin/functions`
2210
+ * endpoint the studio uses to auto-discover queries/mutations/actions
2211
+ * (internal functions are filtered out). Omit it and the endpoint responds
2212
+ * `FUNCTIONS_NOT_CONFIGURED`.
2213
+ */
1720
2214
  functions?: FunctionRegistryLike;
1721
2215
  /**
1722
- * Read-only introspector for `.global()` (D1) tables, backing the data
1723
- * browser's global mode via `GET /_lunora/admin/global/tables` and
1724
- * `/_lunora/admin/global/table`. Build it from `@lunora/d1`'s
1725
- * `listGlobalTables` / `readGlobalTablePage`. Omit it and those endpoints
1726
- * respond `GLOBALS_NOT_CONFIGURED`.
1727
- */
2216
+ * Read-only introspector for `.global()` (D1) tables, backing the data
2217
+ * browser's global mode via `GET /_lunora/admin/global/tables` and
2218
+ * `/_lunora/admin/global/table`. Build it from `@lunora/d1`'s
2219
+ * `listGlobalTables` / `readGlobalTablePage`. Omit it and those endpoints
2220
+ * respond `GLOBALS_NOT_CONFIGURED`.
2221
+ */
1728
2222
  globalIntrospector?: GlobalIntrospector;
1729
2223
  /**
1730
- * Router for HTTP actions (`httpRouter()` from `@lunora/server`, a hono app).
1731
- * Consulted for requests that miss the explicit {@link WorkerOptions.routes}
1732
- * map and the internal `/_lunora/*` endpoints. The runtime builds the action
1733
- * context, injects it on the `__lunoraCtx` env binding, and dispatches via
1734
- * `httpRouter.fetch`; matched handlers reach the data layer through
1735
- * `ctx.run*`, which forward to the shard. An unmatched request returns hono's
1736
- * own 404 (a path-match with the wrong verb is a 404, not a 405).
1737
- */
2224
+ * Router for HTTP actions (`httpRouter()` from `@lunora/server`, a hono app).
2225
+ * Consulted for requests that miss the explicit {@link WorkerOptions.routes}
2226
+ * map and the internal `/_lunora/*` endpoints. The runtime builds the action
2227
+ * context, injects it on the `__lunoraCtx` env binding, and dispatches via
2228
+ * `httpRouter.fetch`; matched handlers reach the data layer through
2229
+ * `ctx.run*`, which forward to the shard. An unmatched request returns hono's
2230
+ * own 404 (a path-match with the wrong verb is a 404, not a 405).
2231
+ */
1738
2232
  httpRouter?: HttpRouterLike;
1739
2233
  /**
1740
- * Insert `.global()` rows for the admin import endpoint. When omitted,
1741
- * rows targeting global tables are reported as hard errors.
1742
- */
2234
+ * The declared identity claim contract (`defineIdentity(...)` from
2235
+ * `@lunora/server`), passed by the generated worker entry. When present, the
2236
+ * worker validates every `resolveIdentity` result against it at the trust
2237
+ * boundary (on the public data paths — RPC / WebSocket / HTTP-action /
2238
+ * server-query, never the admin path) *before* the claims become `ctx.auth`.
2239
+ * A resolver output that violates the contract is downgraded to anonymous or
2240
+ * rejected with a `401`, per the contract's `onInvalid`. Omitted → no
2241
+ * validation, and the identity stays the historical untyped claim bag.
2242
+ */
2243
+ identity?: IdentityContractLike;
2244
+ /**
2245
+ * Insert `.global()` rows for the admin import endpoint. When omitted,
2246
+ * rows targeting global tables are reported as hard errors.
2247
+ */
1743
2248
  importGlobals?: GlobalImportFunction;
1744
2249
  /**
1745
- * Optional telemetry sink. When supplied, the worker emits one
1746
- * `onRpc` event per dispatched RPC (single-shard forward or fan-out)
1747
- * with duration / ok / error / shardKey or fanOut metadata. Sink
1748
- * throws are swallowed so a faulty adapter cannot break user-facing
1749
- * dispatch. See {@link ObservabilitySink}.
1750
- */
2250
+ * Restrict every Durable Object this worker reaches — shard DOs, the
2251
+ * scheduler DO, the fan-out coordinator, subscriptions to a Cloudflare
2252
+ * data-residency jurisdiction (`"eu"`, `"us"`, `"fedramp"`). The runtime
2253
+ * derives a jurisdiction-pinned subnamespace from {@link WorkerOptions.shardDO}
2254
+ * and {@link WorkerOptions.schedulerDO} once, so all routing inherits it.
2255
+ *
2256
+ * Fail-closed: if the bound namespace does not expose `.jurisdiction()`
2257
+ * (an older `@cloudflare/workers-types`), the worker throws rather than
2258
+ * silently routing to the un-pinned global namespace. Omit it for the
2259
+ * default, un-pinned behaviour.
2260
+ *
2261
+ * ⚠️ Set once, before the first deploy — changing it strands data. A DO name
2262
+ * maps to a *different* ID per jurisdiction, so toggling this on an existing
2263
+ * deployment makes every shard/scheduler call resolve to a new, empty DO; the
2264
+ * prior data stays in the old jurisdiction and is unreachable (no in-place
2265
+ * migration). Usually set via the schema's `.jurisdiction(...)`, which codegen
2266
+ * threads here.
2267
+ * @see https://developers.cloudflare.com/durable-objects/reference/data-location/
2268
+ */
2269
+ jurisdiction?: DurableObjectJurisdiction;
2270
+ /**
2271
+ * Introspector for Workers KV namespaces, backing the studio's KV browser
2272
+ * via `GET /_lunora/admin/kv/namespaces`, `GET /_lunora/admin/kv/keys`,
2273
+ * `GET|PUT|DELETE /_lunora/admin/kv/value`. Build it from the env's bound
2274
+ * KV namespaces with `createKvIntrospector` from `@lunora/bindings/kv`.
2275
+ * Omit it and those endpoints respond `KV_NOT_CONFIGURED`.
2276
+ */
2277
+ kvIntrospector?: KvIntrospector;
2278
+ /**
2279
+ * Optional telemetry sink. When supplied, the worker emits one
2280
+ * `onRpc` event per dispatched RPC (single-shard forward or fan-out)
2281
+ * with duration / ok / error / shardKey or fanOut metadata. Sink
2282
+ * throws are swallowed so a faulty adapter cannot break user-facing
2283
+ * dispatch. See {@link ObservabilitySink}.
2284
+ */
1751
2285
  observability?: ObservabilitySink;
1752
2286
  /**
1753
- * The generated OpenAPI 3.1 document. Import it from the codegen-emitted
1754
- * module and pass it through:
1755
- * `import { openApiSpec } from "./lunora/_generated/openapi"`. A Worker can't
1756
- * read the `_generated/openapi.json` file at runtime, so codegen also emits
1757
- * `openapi.ts` (the same document inlined as `export const openApiSpec`) for
1758
- * exactly this wiring — it regenerates on every `lunora/` change so the spec
1759
- * stays live.
1760
- *
1761
- * When set, the worker exposes the admin-gated `GET /_lunora/admin/openapi`
1762
- * endpoint the studio's API-reference view renders. The runtime does
1763
- * NOT assemble or validate the spec — it serves what the host injects verbatim.
1764
- * Omit it and the endpoint returns an empty-but-valid OpenAPI 3.1 document
1765
- * (no paths), so the studio shows a "not configured" state rather than erroring.
1766
- */
2287
+ * The generated OpenAPI 3.1 document. Import it from the codegen-emitted
2288
+ * module and pass it through:
2289
+ * `import { openApiSpec } from "./lunora/_generated/openapi"`. A Worker can't
2290
+ * read the `_generated/openapi.json` file at runtime, so codegen also emits
2291
+ * `openapi.ts` (the same document inlined as `export const openApiSpec`) for
2292
+ * exactly this wiring — it regenerates on every `lunora/` change so the spec
2293
+ * stays live.
2294
+ *
2295
+ * When set, the worker exposes the admin-gated `GET /_lunora/admin/openapi`
2296
+ * endpoint the studio's API-reference view renders. The runtime does
2297
+ * NOT assemble or validate the spec — it serves what the host injects verbatim.
2298
+ * Omit it and the endpoint returns an empty-but-valid OpenAPI 3.1 document
2299
+ * (no paths), so the studio shows a "not configured" state rather than erroring.
2300
+ */
1767
2301
  openApiSpec?: unknown;
1768
2302
  /**
1769
- * The generated OpenRPC 1.x document. Import it from the codegen-emitted
1770
- * module and pass it through:
1771
- * `import { openRpcSpec } from "./lunora/_generated/openrpc"` (only emitted
1772
- * when the project opts into `apiSpec: "openrpc"` or `"both"`). Like
1773
- * `openApiSpec`, codegen inlines the document into `openrpc.ts` because a
1774
- * Worker can't read the `.json` at runtime; both regenerate together.
1775
- *
1776
- * When set, the worker exposes the admin-gated `GET /_lunora/admin/openrpc`
1777
- * endpoint the studio's API-reference view can render. OpenRPC is the
1778
- * RPC-native spec (a `methods` array over the JSON-RPC-shaped
1779
- * `POST /_lunora/rpc` transport); it covers only the RPC functions, not
1780
- * `httpRouter()` REST routes. The runtime does NOT assemble or validate the
1781
- * spec — it serves what the host injects verbatim. Omit it and the endpoint
1782
- * returns an empty-but-valid OpenRPC 1.x document (no methods), so the studio
1783
- * shows a "not configured" state rather than erroring.
1784
- */
2303
+ * The generated OpenRPC 1.x document. Import it from the codegen-emitted
2304
+ * module and pass it through:
2305
+ * `import { openRpcSpec } from "./lunora/_generated/openrpc"` (only emitted
2306
+ * when the project opts into `apiSpec: "openrpc"` or `"both"`). Like
2307
+ * `openApiSpec`, codegen inlines the document into `openrpc.ts` because a
2308
+ * Worker can't read the `.json` at runtime; both regenerate together.
2309
+ *
2310
+ * When set, the worker exposes the admin-gated `GET /_lunora/admin/openrpc`
2311
+ * endpoint the studio's API-reference view can render. OpenRPC is the
2312
+ * RPC-native spec (a `methods` array over the JSON-RPC-shaped
2313
+ * `POST /_lunora/rpc` transport); it covers only the RPC functions, not
2314
+ * `httpRouter()` REST routes. The runtime does NOT assemble or validate the
2315
+ * spec — it serves what the host injects verbatim. Omit it and the endpoint
2316
+ * returns an empty-but-valid OpenRPC 1.x document (no methods), so the studio
2317
+ * shows a "not configured" state rather than erroring.
2318
+ */
1785
2319
  openRpcSpec?: unknown;
1786
2320
  /**
1787
- * When true, the runtime calls `ctx.passThroughOnException()` at the top
1788
- * of the fetch handler. Forwards uncaught exceptions to the origin
1789
- * instead of returning a synthetic 500.
1790
- */
2321
+ * When true, the runtime calls `ctx.passThroughOnException()` at the top
2322
+ * of the fetch handler. Forwards uncaught exceptions to the origin
2323
+ * instead of returning a synthetic 500.
2324
+ */
1791
2325
  passThroughOnException?: boolean;
1792
2326
  /**
1793
- * Coordinator for cross-shard RPCs. When absent, envelopes with
1794
- * `fanOut` set are rejected with a 400. Construct via
1795
- * `createQueryCoordinator({ registry })`.
1796
- */
2327
+ * Coordinator for cross-shard RPCs. When absent, envelopes with
2328
+ * `fanOut` set are rejected with a 400. Construct via
2329
+ * `createQueryCoordinator({ registry })`.
2330
+ */
1797
2331
  queryCoordinator?: QueryCoordinator;
1798
2332
  /**
1799
- * Resolve the calling identity from the inbound RPC request. Called once
1800
- * per RPC (and per fan-out) before the request is forwarded to the
1801
- * shard. The returned `userId` becomes `ctx.auth.userId` on the shard
1802
- * side; remaining keys (`email`, role flags, etc.) are JSON-encoded and
1803
- * forwarded as `x-lunora-identity` so `ctx.auth.getIdentity()` can
1804
- * return them. Returning `null` (or omitting this option) means
1805
- * anonymous — no identity headers are injected.
1806
- */
2333
+ * Cloudflare Queues push-consumer handler the worker's `queue(batch, …)`
2334
+ * entry forwards every delivered `MessageBatch` here. Built by codegen from
2335
+ * `lunora/queues.ts` (via `@lunora/queue`'s `dispatchQueueBatch`, which routes
2336
+ * by `batch.queue` to the matching `defineQueue` handler), so the runtime
2337
+ * stays decoupled from the queue package. Omitted when no push queues exist.
2338
+ */
2339
+ queue?: QueueConsumerHandler;
2340
+ /**
2341
+ * Enforce the ephemeral WS admin token: when `true`,
2342
+ * the worker's WS admin gate rejects the raw master admin token in the
2343
+ * `?token=` query parameter — only a short-lived sub-token minted by
2344
+ * `POST /_lunora/admin/ws-token` (or the master token in the
2345
+ * `Authorization` HEADER, which never leaks via URLs) authorizes. Off by
2346
+ * default (the master token in `?token=` keeps working); also settable per
2347
+ * deployment via `env.LUNORA_REQUIRE_EPHEMERAL_WS_TOKEN`
2348
+ * (`1`/`true`/`on`/`yes`/`enabled`), which the shard/relay Durable Objects
2349
+ * honor for their own upgrade gate too. Flipping it on is the step that
2350
+ * actually closes the URL/log leak — do so once every studio the
2351
+ * deployment uses mints ephemeral tokens.
2352
+ */
2353
+ requireEphemeralWsToken?: boolean;
2354
+ /**
2355
+ * Resolve the calling identity from the inbound RPC request. Called once
2356
+ * per RPC (and per fan-out) before the request is forwarded to the
2357
+ * shard. The returned `userId` becomes `ctx.auth.userId` on the shard
2358
+ * side; remaining keys (`email`, role flags, etc.) are JSON-encoded and
2359
+ * forwarded as `x-lunora-identity` so `ctx.auth.getIdentity()` can
2360
+ * return them. Returning `null` (or omitting this option) means
2361
+ * anonymous — no identity headers are injected.
2362
+ */
1807
2363
  resolveIdentity?: (request: Request, env: unknown) => Promise<ResolvedIdentity | null> | ResolvedIdentity | null;
1808
2364
  /**
1809
- * Resolve a table's sharding metadata. Required by the import endpoint to
1810
- * bucket rows; when omitted, every row routes to the default shard.
1811
- */
2365
+ * Resolve a table's sharding metadata. Required by the import endpoint to
2366
+ * bucket rows; when omitted, every row routes to the default shard.
2367
+ */
1812
2368
  resolveTableSharding?: AdminTableResolver;
1813
2369
  /**
1814
- * Map of routes for custom HTTP handlers (auth callbacks etc.). Keys can
1815
- * be either `"METHOD path"` (e.g. `"GET /healthz"`) or just `"path"`
1816
- * (e.g. `"/healthz"`) — the runtime will match the more specific form
1817
- * first.
1818
- */
2370
+ * Map of routes for custom HTTP handlers (auth callbacks etc.). Keys can
2371
+ * be either `"METHOD path"` (e.g. `"GET /healthz"`) or just `"path"`
2372
+ * (e.g. `"/healthz"`) — the runtime will match the more specific form
2373
+ * first.
2374
+ */
1819
2375
  routes?: Record<string, Route>;
1820
2376
  /**
1821
- * Namespace binding for the `SchedulerDO` (typically `env.SCHEDULER`). When
1822
- * set, the worker exposes the admin-gated `/_lunora/admin/scheduled`
1823
- * endpoints used by the studio to list and cancel `runAfter` / `runAt`
1824
- * jobs. Omit it and those endpoints respond `SCHEDULER_NOT_CONFIGURED`.
1825
- */
2377
+ * Namespace binding for the `SchedulerDO` (typically `env.SCHEDULER`). When
2378
+ * set, the worker exposes the admin-gated `/_lunora/admin/scheduled`
2379
+ * endpoints used by the studio to list and cancel `runAfter` / `runAt`
2380
+ * jobs. Omit it and those endpoints respond `SCHEDULER_NOT_CONFIGURED`.
2381
+ */
1826
2382
  schedulerDO?: ShardNamespaceLike;
1827
2383
  /**
1828
- * Named `SchedulerDO` instance the admin endpoints target. Must match the
1829
- * `instanceName` passed to `createScheduler` (both default to `default`).
1830
- */
2384
+ * Named `SchedulerDO` instance the admin endpoints target. Must match the
2385
+ * `instanceName` passed to `createScheduler` (both default to `default`).
2386
+ */
1831
2387
  schedulerInstanceName?: string;
1832
2388
  /**
1833
- * Secure-by-default HTTP edge applied to every response the worker emits
1834
- * (RPC, auth, admin, `httpRoute` handlers, SSR fallback): baseline security
1835
- * headers, deny-by-default CORS, and a CSRF/origin guard. Every layer is on
1836
- * by default and individually opt-out — see {@link SecurityOptions}. Omit it
1837
- * to take the hardened defaults; set a field to `false` to relax that layer
1838
- * (e.g. `security: { cors: { allowedOrigins: ["https://app.example.com"] } }`).
1839
- */
2389
+ * Secure-by-default HTTP edge applied to every response the worker emits
2390
+ * (RPC, auth, admin, `httpRoute` handlers, SSR fallback): baseline security
2391
+ * headers, deny-by-default CORS, and a CSRF/origin guard. Every layer is on
2392
+ * by default and individually opt-out — see {@link SecurityOptions}. Omit it
2393
+ * to take the hardened defaults; set a field to `false` to relax that layer
2394
+ * (e.g. `security: { cors: { allowedOrigins: ["https://app.example.com"] } }`).
2395
+ */
1840
2396
  security?: SecurityOptions;
1841
2397
  /** Namespace binding for the shard Durable Object (typically `env.SHARD`). */
1842
2398
  shardDO: ShardNamespaceLike;
1843
2399
  /**
1844
- * Names of the storage buckets the studio's file browser offers in its bucket
1845
- * picker, backing `GET /_lunora/admin/storage/buckets`. Supply the keys of a
1846
- * multi-bucket `createBucketStorage({...})` so the operator can switch buckets;
1847
- * the selected name is forwarded to the storage ops as `options.bucket`. Omit
1848
- * it (single-bucket deployments) and the picker is hidden — the ops target the
1849
- * default bucket.
1850
- */
2400
+ * Names of the storage buckets the studio's file browser offers in its bucket
2401
+ * picker, backing `GET /_lunora/admin/storage/buckets`. Supply the keys of a
2402
+ * multi-bucket `createBucketStorage({...})` so the operator can switch buckets;
2403
+ * the selected name is forwarded to the storage ops as `options.bucket`. Omit
2404
+ * it (single-bucket deployments) and the picker is hidden — the ops target the
2405
+ * default bucket.
2406
+ */
1851
2407
  storageBuckets?: string[];
1852
2408
  /**
1853
- * Deletes one object, backing the admin-gated `DELETE /_lunora/admin/storage`
1854
- * endpoint the studio's file browser calls. Passing
1855
- * `createStorage(...).delete` satisfies it. Omit it and the endpoint responds
1856
- * `STORAGE_DELETE_NOT_CONFIGURED` — the studio surfaces a clear inline error.
1857
- */
2409
+ * Deletes one object, backing the admin-gated `DELETE /_lunora/admin/storage`
2410
+ * endpoint the studio's file browser calls. Passing
2411
+ * `createStorage(...).delete` satisfies it. Omit it and the endpoint responds
2412
+ * `STORAGE_DELETE_NOT_CONFIGURED` — the studio surfaces a clear inline error.
2413
+ */
1858
2414
  storageDelete?: StorageDeleteFunction;
1859
2415
  /**
1860
- * Storage lister backing the admin-gated `GET /_lunora/admin/storage`
1861
- * endpoint the studio's file browser calls. The structural shape matches
1862
- * `@lunora/storage`'s `Storage["list"]`, so passing `createStorage(...).list`
1863
- * (or the raw R2 bucket's `list`) satisfies it. Omit it and the endpoint
1864
- * responds `STORAGE_NOT_CONFIGURED`.
1865
- */
2416
+ * Storage lister backing the admin-gated `GET /_lunora/admin/storage`
2417
+ * endpoint the studio's file browser calls. The structural shape matches
2418
+ * `@lunora/storage`'s `Storage["list"]`, so passing `createStorage(...).list`
2419
+ * (or the raw R2 bucket's `list`) satisfies it. Omit it and the endpoint
2420
+ * responds `STORAGE_NOT_CONFIGURED`.
2421
+ */
1866
2422
  storageList?: StorageListFunction;
1867
2423
  /**
1868
- * Mints a (signed or public) URL for one object, backing the admin-gated
1869
- * `GET /_lunora/admin/storage/url` endpoint the studio's "copy URL" action
1870
- * calls. Passing `createStorage(...).getSignedUrl` (or `.getUrl`) satisfies
1871
- * it. Omit it and the endpoint responds `STORAGE_URL_NOT_CONFIGURED` — the
1872
- * studio surfaces a clear inline error.
1873
- */
2424
+ * Mints a (signed or public) URL for one object, backing the admin-gated
2425
+ * `GET /_lunora/admin/storage/url` endpoint the studio's "copy URL" action
2426
+ * calls. Passing `createStorage(...).getSignedUrl` (or `.getUrl`) satisfies
2427
+ * it. Omit it and the endpoint responds `STORAGE_URL_NOT_CONFIGURED` — the
2428
+ * studio surfaces a clear inline error.
2429
+ */
1874
2430
  storageSignedUrl?: StorageSignedUrlFunction;
1875
2431
  /**
1876
- * Uploads one object, backing the admin-gated `PUT /_lunora/admin/storage`
1877
- * endpoint the studio's file browser calls. Passing `createStorage(...).upload`
1878
- * satisfies it. Omit it and the endpoint responds
1879
- * `STORAGE_UPLOAD_NOT_CONFIGURED` — the studio surfaces a clear inline error.
1880
- */
2432
+ * Uploads one object, backing the admin-gated `PUT /_lunora/admin/storage`
2433
+ * endpoint the studio's file browser calls. Passing `createStorage(...).upload`
2434
+ * satisfies it. Omit it and the endpoint responds
2435
+ * `STORAGE_UPLOAD_NOT_CONFIGURED` — the studio surfaces a clear inline error.
2436
+ */
1881
2437
  storageUpload?: StorageUploadFunction;
1882
2438
  /**
1883
- * Page the `.global()` (D1) change-data-capture log for the admin sync
1884
- * endpoint. When omitted, the sync feed covers only shard-local tables.
1885
- */
2439
+ * Page the `.global()` (D1) change-data-capture log for the admin sync
2440
+ * endpoint. When omitted, the sync feed covers only shard-local tables.
2441
+ */
1886
2442
  syncGlobals?: GlobalCdcSyncFunction;
1887
2443
  /**
1888
- * Read-only introspector for Vectorize indexes, backing the studio's vector
1889
- * browser via `GET /_lunora/admin/vector/indexes` and
1890
- * `POST /_lunora/admin/vector/query`. Build it from the generated
1891
- * `LUNORA_VECTOR_INDEXES` registry plus the env Vectorize bindings (and the
1892
- * schema's embedders, to enable similarity queries). Omit it and those
1893
- * endpoints respond `VECTORS_NOT_CONFIGURED`.
1894
- */
2444
+ * Read-only introspector for Vectorize indexes, backing the studio's vector
2445
+ * browser via `GET /_lunora/admin/vector/indexes` and
2446
+ * `POST /_lunora/admin/vector/query`. Build it from the generated
2447
+ * `LUNORA_VECTOR_INDEXES` registry plus the env Vectorize bindings (and the
2448
+ * schema's embedders, to enable similarity queries). Omit it and those
2449
+ * endpoints respond `VECTORS_NOT_CONFIGURED`.
2450
+ */
1895
2451
  vectorIntrospector?: VectorIntrospector;
1896
2452
  /**
1897
- * Resolver for the Cloudflare Workflows REST client, built from the
1898
- * deployment `env` (its `CLOUDFLARE_ACCOUNT_ID` / `CLOUDFLARE_API_TOKEN`).
1899
- * Set by the codegen-emitted worker entry (which depends on
1900
- * `@lunora/workflow`); when omitted, the `/_lunora/admin/workflows*` proxy
1901
- * reports "not configured" and the studio shows the credentials empty state.
1902
- */
2453
+ * Voice-session Durable Object namespaces, keyed by the agent's
2454
+ * `lunora/agents.ts` export name (e.g. `{ support: env.VOICE_SUPPORT }`).
2455
+ * Codegen wires this for every voice-enabled agent. When set, the worker
2456
+ * exposes `/_lunora/voice/&lt;agentExportName>` a WebSocket upgrade that
2457
+ * resolves the caller's identity, forwards it on the server-minted
2458
+ * `x-lunora-userid` / `x-lunora-identity` headers, and hands the socket to
2459
+ * the agent's `VoiceSessionDO`. Omit it (voice-free apps) and the route does
2460
+ * not exist.
2461
+ */
2462
+ voiceAgents?: Record<string, ShardNamespaceLike>;
2463
+ /**
2464
+ * Resolver for the Cloudflare Workflows REST client, built from the
2465
+ * deployment `env` (its `CLOUDFLARE_ACCOUNT_ID` / `CLOUDFLARE_API_TOKEN`).
2466
+ * Set by the codegen-emitted worker entry (which depends on
2467
+ * `@lunora/workflow`); when omitted, the `/_lunora/admin/workflows*` proxy
2468
+ * reports "not configured" and the studio shows the credentials empty state.
2469
+ */
1903
2470
  workflowsClient?: (env: unknown) => undefined | WorkflowsRestClient;
2471
+ /**
2472
+ * Injected x402 charge gate for paid (`.x402({ price })`) procedures. Build
2473
+ * it with `createProcedureChargeGate(config)` from `@lunora/x402/charge` and
2474
+ * pass it here; the runtime stays free of a hard `@lunora/x402` dependency
2475
+ * (and its viem/solana deps).
2476
+ *
2477
+ * **Required whenever any registered function is `.x402()`-tagged.** The
2478
+ * origin worker refuses to dispatch a paid procedure with a config error
2479
+ * (`500`) when this is absent, rather than serving it free — the paywall is
2480
+ * fail-closed by construction. See {@link X402ChargeGate}.
2481
+ */
2482
+ x402Charge?: X402ChargeGate;
1904
2483
  }
1905
2484
  interface RpcContext {
1906
2485
  ctx: ExecutionContextLike;
@@ -1909,140 +2488,195 @@ interface RpcContext {
1909
2488
  shardKey: string;
1910
2489
  }
1911
2490
  /**
1912
- * The composed Lunora worker. `fetch` / `scheduled` are the standard Cloudflare
1913
- * module-worker entrypoints (so the object can be re-exported directly as
1914
- * `export default createWorker(...)`). `serverQuery` is the in-process fast-path
1915
- * (PLAN4 §2.2) an SSR loader running inside the same worker calls to reach a
1916
- * Lunora query without a self-`fetch` to `/_lunora/rpc`, with identity / RLS /
1917
- * auth semantics identical to the HTTP path.
1918
- */
2491
+ * The composed Lunora worker. `fetch` / `scheduled` are the standard Cloudflare
2492
+ * module-worker entrypoints (so the object can be re-exported directly as
2493
+ * `export default createWorker(...)`). `serverQuery` is the in-process fast-path
2494
+ * (PLAN4 §2.2) an SSR loader running inside the same worker calls to reach a
2495
+ * Lunora query without a self-`fetch` to `/_lunora/rpc`, with identity / RLS /
2496
+ * auth semantics identical to the HTTP path.
2497
+ */
1919
2498
  interface LunoraWorker {
1920
2499
  fetch: (request: Request, env: unknown, context: ExecutionContextLike) => Promise<Response>;
2500
+ /**
2501
+ * Cloudflare Queues consumer entry — present only when the app declares push
2502
+ * queues. Forwards each delivered `MessageBatch` to the configured
2503
+ * {@link WorkerOptions.queue} handler; a no-op when none is set.
2504
+ */
2505
+ queue?: (batch: unknown, env: unknown, context: ExecutionContextLike) => Promise<void>;
1921
2506
  scheduled: (controller: ScheduledControllerLike, env: unknown, context: ExecutionContextLike) => Promise<void>;
1922
2507
  /**
1923
- * In-process query/mutation dispatch for SSR loaders co-located in this
1924
- * worker. Resolves identity off `request` (cookies / bearer / bookmark) and
1925
- * runs the per-shard authorization gate exactly like `POST /_lunora/rpc`,
1926
- * then dispatches to the owning shard — no network self-fetch. Returns the
1927
- * raw shard {@link Response}, byte-identical to the HTTP path's, so callers
1928
- * can `.json()` it (`{ result }` / `{ error }`) or forward it verbatim. Like
1929
- * the worker's `fetch`, it never throws on a request fault: a denied auth
1930
- * gate, a bad reference, or a downstream error comes back as the SAME JSON
1931
- * error `Response` (`toErrorResponse`) the HTTP path returns.
1932
- * @param request The inbound SSR request — its `cookie` / `authorization`
1933
- * / `x-d1-bookmark` headers drive identity, exactly as the
1934
- * HTTP RPC path reads them.
1935
- * @param env The worker `env`, forwarded to `resolveIdentity`.
1936
- * @param reference A generated function reference (`api.foo.bar`); its
1937
- * `__lunoraRef` is the `"namespace:fn"` dispatched.
1938
- * @param args The function arguments.
1939
- * @param options Call options mirroring the RPC envelope.
1940
- * @param options.shardKey Routes to a specific shard (omitted → the worker's
1941
- * `defaultShardKey`).
1942
- */
2508
+ * In-process query/mutation dispatch for SSR loaders co-located in this
2509
+ * worker. Resolves identity off `request` (cookies / bearer / bookmark) and
2510
+ * runs the per-shard authorization gate exactly like `POST /_lunora/rpc`,
2511
+ * then dispatches to the owning shard — no network self-fetch. Returns the
2512
+ * raw shard {@link Response}, byte-identical to the HTTP path's, so callers
2513
+ * can `.json()` it (`{ result }` / `{ error }`) or forward it verbatim. Like
2514
+ * the worker's `fetch`, it never throws on a request fault: a denied auth
2515
+ * gate, a bad reference, or a downstream error comes back as the SAME JSON
2516
+ * error `Response` (`toErrorResponse`) the HTTP path returns.
2517
+ * @param request The inbound SSR request — its `cookie` / `authorization`
2518
+ * / `x-d1-bookmark` headers drive identity, exactly as the
2519
+ * HTTP RPC path reads them.
2520
+ * @param env The worker `env`, forwarded to `resolveIdentity`.
2521
+ * @param reference A generated function reference (`api.foo.bar`); its
2522
+ * `__lunoraRef` is the `"namespace:fn"` dispatched.
2523
+ * @param args The function arguments.
2524
+ * @param options Call options mirroring the RPC envelope.
2525
+ * @param options.shardKey Routes to a specific shard (omitted → the worker's
2526
+ * `defaultShardKey`).
2527
+ */
1943
2528
  serverQuery: (request: Request, env: unknown, reference: unknown, args?: Record<string, unknown>, options?: {
1944
2529
  shardKey?: string;
1945
2530
  }) => Promise<Response>;
1946
2531
  }
1947
2532
  /**
1948
- * Build a Cloudflare Worker entry. Returns an object with `fetch` so it can
1949
- * be re-exported directly as `export default createWorker(...)`.
1950
- */
2533
+ * Build a Cloudflare Worker entry. Returns an object with `fetch` so it can
2534
+ * be re-exported directly as `export default createWorker(...)`.
2535
+ */
1951
2536
  declare const createWorker: (options: WorkerOptions) => LunoraWorker;
1952
2537
  /**
1953
- * Compose a meta-framework SSR handler and Lunora into a single Cloudflare
1954
- * Worker (PLAN4 §1, §2.2). Thin sugar over {@link createWorker} — a
1955
- * near-pass-through whose value is naming and a documented, framework-neutral
1956
- * entrypoint, so a template reads cleanly:
1957
- *
1958
- * ```ts
1959
- * import { composeWorker } from "@lunora/runtime";
1960
- *
1961
- * export default composeWorker({
1962
- * httpRouter: ssrHandler, // TanStack Start / React Router / SolidStart / …
1963
- * shardDO: env.SHARD,
1964
- * auth,
1965
- * });
1966
- * ```
1967
- *
1968
- * `httpRouter` is *any* meta-framework SSR handler — structurally an
1969
- * {@link HttpRouterLike} (`{ fetch(request, env?, ctx?) }`). It is the
1970
- * lowest-priority matcher: the worker dispatches auth (`/api/auth/*`), explicit
1971
- * {@link WorkerOptions.routes}, and the reserved realtime endpoints
1972
- * (`/_lunora/rpc`, `/_lunora/ws`, `/_lunora/admin/*`) first, then falls through
1973
- * to `httpRouter.fetch` for everything else. An SSR render that throws is
1974
- * contained at that seam and surfaced as a plain 500 — it can never take down
1975
- * the realtime plane (see `dispatchHttpRoute`). The two flows share one worker
1976
- * but never collide.
1977
- *
1978
- * The signature is identical to {@link createWorker}; pass exactly the same
1979
- * options. Prefer this name in framework templates to make the composition
1980
- * intent explicit.
1981
- */
2538
+ * Compose a meta-framework SSR handler and Lunora into a single Cloudflare
2539
+ * Worker (PLAN4 §1, §2.2). Thin sugar over {@link createWorker} — a
2540
+ * near-pass-through whose value is naming and a documented, framework-neutral
2541
+ * entrypoint, so a template reads cleanly:
2542
+ *
2543
+ * ```ts
2544
+ * import { composeWorker } from "@lunora/runtime";
2545
+ *
2546
+ * export default composeWorker({
2547
+ * httpRouter: ssrHandler, // TanStack Start / React Router / SolidStart / …
2548
+ * shardDO: env.SHARD,
2549
+ * auth,
2550
+ * });
2551
+ * ```
2552
+ *
2553
+ * `httpRouter` is *any* meta-framework SSR handler — structurally an
2554
+ * {@link HttpRouterLike} (`{ fetch(request, env?, ctx?) }`). It is the
2555
+ * lowest-priority matcher: the worker dispatches auth (`/api/auth/*`), explicit
2556
+ * {@link WorkerOptions.routes}, and the reserved realtime endpoints
2557
+ * (`/_lunora/rpc`, `/_lunora/ws`, `/_lunora/admin/*`) first, then falls through
2558
+ * to `httpRouter.fetch` for everything else. An SSR render that throws is
2559
+ * contained at that seam and surfaced as a plain 500 — it can never take down
2560
+ * the realtime plane (see `dispatchHttpRoute`). The two flows share one worker
2561
+ * but never collide.
2562
+ *
2563
+ * The signature is identical to {@link createWorker}; pass exactly the same
2564
+ * options. Prefer this name in framework templates to make the composition
2565
+ * intent explicit.
2566
+ */
1982
2567
  declare const composeWorker: (options: WorkerOptions) => LunoraWorker;
1983
2568
  /**
1984
- * A meta-framework's emitted Cloudflare handler: either a bare `fetch` function
1985
- * or a `{ fetch }` module object (optionally carrying its own `scheduled`). Every
1986
- * class-B adapter output (`@sveltejs/adapter-cloudflare`, Nitro's
1987
- * `cloudflare-module`, `@astrojs/cloudflare`) is structurally one of these.
1988
- */
2569
+ * A meta-framework's emitted Cloudflare handler: either a bare `fetch` function
2570
+ * or a `{ fetch }` module object (optionally carrying its own `scheduled`). Every
2571
+ * class-B adapter output (`@sveltejs/adapter-cloudflare`, Nitro's
2572
+ * `cloudflare-module`, `@astrojs/cloudflare`) is structurally one of these.
2573
+ */
1989
2574
  type FrameworkHostHandler = ((request: Request, env?: unknown, context?: ExecutionContextLike) => Promise<Response> | Response) | (HttpRouterLike & {
1990
2575
  scheduled?: (controller: ScheduledControllerLike, env: unknown, context: ExecutionContextLike) => Promise<void> | void;
1991
2576
  });
1992
2577
  /** Lunora worker options for {@link withFrameworkWorker} — everything except `httpRouter` (supplied from the framework host). */
1993
2578
  type FrameworkWorkerOptions = Omit<WorkerOptions, "httpRouter">;
1994
2579
  /**
1995
- * Either fixed {@link FrameworkWorkerOptions}, or a factory deriving them from the
1996
- * per-request `env` — for bindings (like `env.SHARD` → `shardDO`) that only exist
1997
- * at request time.
1998
- */
2580
+ * Either fixed {@link FrameworkWorkerOptions}, or a factory deriving them from the
2581
+ * per-request `env` — for bindings (like `env.SHARD` → `shardDO`) that only exist
2582
+ * at request time.
2583
+ */
1999
2584
  type FrameworkWorkerOptionsInput = ((env: unknown) => FrameworkWorkerOptions) | FrameworkWorkerOptions;
2000
2585
  /**
2001
- * Compose a meta-framework's Cloudflare Worker handler with Lunora's realtime
2002
- * plane into one `{ fetch, scheduled }` Worker — the **single, shared** class-B
2003
- * (own-CF-adapter, hook-injection) composer behind `@lunora/svelte/worker`,
2004
- * `@lunora/vue/worker`, and `@lunora/astro`'s `withLunora` (PLAN4 §3). It wraps
2005
- * the framework handler as {@link composeWorker}'s `httpRouter`, so the reserved
2006
- * realtime endpoints (`/_lunora/rpc`, `/_lunora/ws`, `/_lunora/admin/*`) plus
2007
- * auth/explicit `routes` go to Lunora and **everything else** delegates to the
2008
- * framework. A framework render that throws is contained at the seam and
2009
- * surfaced as a plain 500 — it can never take down the realtime plane.
2010
- *
2011
- * Owns the three behaviors the adapters otherwise each re-implemented (and
2012
- * diverged on): (1) the host may be a bare `fetch` fn or a `{ fetch }` object;
2013
- * (2) options may be a fixed object or an `(env) => options` factory, rebuilt per
2014
- * request so per-request bindings wire in; (3) **`scheduled` preservation** — when
2015
- * Lunora configures no cron surface, the framework host's own `scheduled` (if any)
2016
- * is preserved rather than silently dropped; otherwise Lunora owns it (crons /
2017
- * backup).
2018
- * @param host The framework's emitted Cloudflare handler.
2019
- * @param optionsInput Lunora options minus `httpRouter`, or an `(env) => options` factory.
2020
- */
2586
+ * Compose a meta-framework's Cloudflare Worker handler with Lunora's realtime
2587
+ * plane into one `{ fetch, scheduled }` Worker — the **single, shared** class-B
2588
+ * (own-CF-adapter, hook-injection) composer behind `@lunora/svelte/worker`,
2589
+ * `@lunora/vue/worker`, and `@lunora/astro`'s `withLunora` (PLAN4 §3). It wraps
2590
+ * the framework handler as {@link composeWorker}'s `httpRouter`, so the reserved
2591
+ * realtime endpoints (`/_lunora/rpc`, `/_lunora/ws`, `/_lunora/admin/*`) plus
2592
+ * auth/explicit `routes` go to Lunora and **everything else** delegates to the
2593
+ * framework. A framework render that throws is contained at the seam and
2594
+ * surfaced as a plain 500 — it can never take down the realtime plane.
2595
+ *
2596
+ * Owns the three behaviors the adapters otherwise each re-implemented (and
2597
+ * diverged on): (1) the host may be a bare `fetch` fn or a `{ fetch }` object;
2598
+ * (2) options may be a fixed object or an `(env) => options` factory, rebuilt per
2599
+ * request so per-request bindings wire in; (3) **`scheduled` preservation** — when
2600
+ * Lunora configures no cron surface, the framework host's own `scheduled` (if any)
2601
+ * is preserved rather than silently dropped; otherwise Lunora owns it (crons /
2602
+ * backup).
2603
+ * @param host The framework's emitted Cloudflare handler.
2604
+ * @param optionsInput Lunora options minus `httpRouter`, or an `(env) => options` factory.
2605
+ */
2021
2606
  declare const withFrameworkWorker: (host: FrameworkHostHandler, optionsInput: FrameworkWorkerOptionsInput) => LunoraWorker;
2607
+ /**
2608
+ * Options for {@link createLunoraHandler}. Either an `(env) => options` factory
2609
+ * (full control — for bindings that only exist at request time), or a partial
2610
+ * {@link FrameworkWorkerOptions} object whose `shardDO` defaults to the
2611
+ * conventional `env.SHARD` binding. Pass nothing for the common case.
2612
+ */
2613
+ type LunoraHandlerOptions = ((env: unknown) => FrameworkWorkerOptions) | Partial<FrameworkWorkerOptions>;
2614
+ /**
2615
+ * Resolve per-request Lunora worker options. A factory is called with the
2616
+ * request `env`; a partial object has its `shardDO` defaulted to `env.SHARD` so
2617
+ * the common case needs no configuration. Throws a clear error when no shard
2618
+ * namespace can be found — a wiring mistake, not a runtime condition to swallow.
2619
+ */
2620
+ declare const resolveLunoraOptions: (options: LunoraHandlerOptions, env: unknown) => FrameworkWorkerOptions;
2621
+ /**
2622
+ * Build a framework-neutral request handler for Lunora's realtime plane
2623
+ * (`/_lunora/rpc`, `/_lunora/ws`, `/_lunora/admin/*`). This is the **one shared
2624
+ * seam** every web-standard framework integration mounts — Hono, Nitro/h3,
2625
+ * Elysia, or any WinterCG host running on Cloudflare Workers — so each is a
2626
+ * 1–2 line bridge (`(request, env, ctx) => Response`) rather than a bespoke
2627
+ * adapter package.
2628
+ *
2629
+ * Mount it under `/_lunora/*` (or whatever path you reserve) inside your app's
2630
+ * router; everything else stays your framework's. The host supplies, per
2631
+ * request: a Web `Request`, the Cloudflare `env` (carrying the `SHARD` Durable
2632
+ * Object namespace), and — when available — the `ExecutionContext`. The
2633
+ * `101 Switching Protocols` WebSocket-upgrade `Response` (with its `webSocket`)
2634
+ * is returned verbatim, so the framework streams the socket through unchanged.
2635
+ *
2636
+ * ```ts
2637
+ * // Hono
2638
+ * const lunora = createLunoraHandler();
2639
+ * app.use("/_lunora/*", (c) => lunora(c.req.raw, c.env, c.executionCtx));
2640
+ *
2641
+ * // Nitro / h3
2642
+ * const lunora = createLunoraHandler();
2643
+ * export default defineEventHandler((event) => {
2644
+ * const { ctx, env } = event.context.cloudflare;
2645
+ * return lunora(toWebRequest(event), env, ctx);
2646
+ * });
2647
+ * ```
2648
+ *
2649
+ * `shardDO` defaults to `env.SHARD`; pass `options` (or an `(env) => options`
2650
+ * factory) to add `auth`, `crons`, a `security` posture, or a custom namespace.
2651
+ * A new worker is composed per request because the options (and the `SHARD`
2652
+ * binding they default from) are only known once `env` arrives.
2653
+ * @param options Partial worker options (default `shardDO: env.SHARD`), or an `(env) => options` factory.
2654
+ */
2655
+ declare const createLunoraHandler: (options?: LunoraHandlerOptions) => ((request: Request, env: unknown, context?: ExecutionContextLike) => Promise<Response>);
2022
2656
  /** Re-exported helper so callers can roundtrip envelopes in tests. */
2023
2657
  declare const defineRpcEnvelope: (envelope: RpcEnvelope) => RpcEnvelope;
2024
2658
  /**
2025
- * Reader / counter capabilities, typed against the SAME canonical
2026
- * `DatabaseWriterLike` the `@lunora/d1` ctx-db derives its `crossShardReader` /
2027
- * `crossShardCounter` options from (`DatabaseWriterLike["findMany"]` /
2028
- * `["count"]`) — so the pair drops straight into `createD1CtxDb` with no cast and
2029
- * no structural drift. The import is type-only: `@lunora/runtime` keeps no hard
2030
- * (value) dependency on `@lunora/do`.
2031
- */
2659
+ * Reader / counter capabilities, typed against the SAME canonical
2660
+ * `DatabaseWriterLike` the `@lunora/d1` ctx-db derives its `crossShardReader` /
2661
+ * `crossShardCounter` options from (`DatabaseWriterLike["findMany"]` /
2662
+ * `["count"]`) — so the pair drops straight into `createD1CtxDb` with no cast and
2663
+ * no structural drift. The import is type-only: `@lunora/runtime` keeps no hard
2664
+ * (value) dependency on `@lunora/do`.
2665
+ */
2032
2666
  type CrossShardCounter = DatabaseWriterLike["count"];
2033
2667
  type CrossShardReader = DatabaseWriterLike["findMany"];
2034
2668
  interface CrossShardRelationOptions {
2035
2669
  /**
2036
- * `fetch` used for the worker subrequest. Defaults to `globalThis.fetch`.
2037
- * Injectable so the in-DO loopback (or a test) can supply its own.
2038
- */
2670
+ * `fetch` used for the worker subrequest. Defaults to `globalThis.fetch`.
2671
+ * Injectable so the in-DO loopback (or a test) can supply its own.
2672
+ */
2039
2673
  fetch?: typeof globalThis.fetch;
2040
2674
  /** Forwarded identity claims (the `x-lunora-identity` envelope), when present. */
2041
2675
  identity?: Record<string, unknown>;
2042
2676
  /**
2043
- * Origin the worker is reachable at (`LUNORA_WORKER_ORIGIN`). The DO issues a
2044
- * loopback subrequest to `${origin}/_lunora/rpc`.
2045
- */
2677
+ * Origin the worker is reachable at (`LUNORA_WORKER_ORIGIN`). The DO issues a
2678
+ * loopback subrequest to `${origin}/_lunora/rpc`.
2679
+ */
2046
2680
  origin: string;
2047
2681
  /** Forwarded user id (the `x-lunora-userid` header), when authenticated. */
2048
2682
  userId?: string;
@@ -2052,70 +2686,88 @@ interface CrossShardRelationCapabilities {
2052
2686
  crossShardReader: CrossShardReader;
2053
2687
  }
2054
2688
  /**
2055
- * Build the `crossShardReader` / `crossShardCounter` pair for a single request,
2056
- * wired to fan reverse-relation reads out across every shard via the worker's
2057
- * coordinator. Pass the result straight into `createD1CtxDb`.
2058
- */
2689
+ * Build the `crossShardReader` / `crossShardCounter` pair for a single request,
2690
+ * wired to fan reverse-relation reads out across every shard via the worker's
2691
+ * coordinator. Pass the result straight into `createD1CtxDb`.
2692
+ */
2059
2693
  declare const createCrossShardRelationCapabilities: (options: CrossShardRelationOptions) => CrossShardRelationCapabilities;
2060
2694
  /**
2061
- * Conventional DO instance name. Kept in sync with `SHARD_REGISTRY_DO_NAME`
2062
- * in `@lunora/do` (not imported to avoid the runtime → do dependency edge —
2063
- * `@lunora/runtime` MUST stay free of a hard `@lunora/do` dep).
2064
- */
2695
+ * Conventional DO instance name. Kept in sync with `SHARD_REGISTRY_DO_NAME`
2696
+ * in `@lunora/do` (not imported to avoid the runtime → do dependency edge —
2697
+ * `@lunora/runtime` MUST stay free of a hard `@lunora/do` dep).
2698
+ */
2065
2699
  declare const SHARD_REGISTRY_DO_NAME: string;
2066
2700
  /**
2067
- * Default per-table cache TTL in milliseconds. 30s is a balance between
2068
- * read amplification (a wide fan-out costs N registry round-trips at
2069
- * minimum every 30s) and registration latency (newly registered shards
2070
- * take up to 30s to participate in fan-outs).
2071
- */
2701
+ * Default per-table cache TTL in milliseconds. 30s is a balance between
2702
+ * read amplification (a wide fan-out costs N registry round-trips at
2703
+ * minimum every 30s) and registration latency (newly registered shards
2704
+ * take up to 30s to participate in fan-outs).
2705
+ */
2072
2706
  declare const DEFAULT_REGISTRY_CACHE_TTL_MS: number;
2073
2707
  interface DynamicShardRegistryOptions {
2074
2708
  /**
2075
- * Override the in-process per-table cache TTL. Set to `0` to disable
2076
- * caching (every `listShardKeys` call hits the DO — useful only for
2077
- * tests).
2078
- */
2709
+ * Override the in-process per-table cache TTL. Set to `0` to disable
2710
+ * caching (every `listShardKeys` call hits the DO — useful only for
2711
+ * tests).
2712
+ */
2079
2713
  cacheTtlMs?: number;
2080
2714
  /**
2081
- * DO instance name. Defaults to {@link SHARD_REGISTRY_DO_NAME}. Override
2082
- * only if you run multiple isolated registries in one environment.
2083
- */
2715
+ * DO instance name. Defaults to {@link SHARD_REGISTRY_DO_NAME}. Override
2716
+ * only if you run multiple isolated registries in one environment.
2717
+ */
2084
2718
  instanceName?: string;
2719
+ /**
2720
+ * Pin the registry DO to a Cloudflare data-residency jurisdiction. Pass the
2721
+ * same value as the worker's `jurisdiction` so the registry co-locates with
2722
+ * the shards it tracks. Omit for the un-pinned global namespace.
2723
+ */
2724
+ jurisdiction?: DurableObjectJurisdiction;
2085
2725
  /** DO namespace binding (`env.SHARD_REGISTRY`). */
2086
2726
  namespace: ShardNamespaceLike;
2087
2727
  }
2088
2728
  /**
2089
- * Extension of {@link ShardRegistry} with the mutator surface a worker
2090
- * needs to register / unregister shard keys.
2091
- */
2729
+ * Extension of {@link ShardRegistry} with the mutator surface a worker
2730
+ * needs to register / unregister shard keys.
2731
+ */
2092
2732
  interface DynamicShardRegistry extends ShardRegistry {
2093
2733
  /** Drop the local cache. Pass a table to invalidate one entry; omit for everything. */
2094
2734
  invalidate: (table?: string) => void;
2095
2735
  /** Register a shard key as live for `table`. Idempotent. */
2096
2736
  register: (table: string, shardKey: string) => Promise<void>;
2097
2737
  /**
2098
- * Read the full `table → shardKeys` map. Useful for admin / debug UIs;
2099
- * not on the fan-out hot path.
2100
- */
2738
+ * Read the full `table → shardKeys` map. Useful for admin / debug UIs;
2739
+ * not on the fan-out hot path.
2740
+ */
2101
2741
  snapshot: () => Promise<Record<string, ReadonlyArray<string>>>;
2102
2742
  /** Remove a shard key from `table`'s live set. Idempotent. */
2103
2743
  unregister: (table: string, shardKey: string) => Promise<void>;
2104
2744
  }
2105
2745
  declare const createDynamicShardRegistry: (options: DynamicShardRegistryOptions) => DynamicShardRegistry;
2106
2746
  interface LunoraErrorBody {
2107
- error: {
2108
- code: string;
2109
- message: string;
2110
- };
2111
- }
2747
+ error: ErrorBody;
2748
+ }
2749
+ /**
2750
+ * Convert any thrown value into a JSON error response.
2751
+ *
2752
+ * Delegates the envelope + redaction to `@lunora/errors`' {@link toErrorBody} —
2753
+ * a non-internal `LunoraError` (from `@lunora/server`, this runtime, or the
2754
+ * `@lunora/do` data layer) is echoed with its `code`/`message`/`hint`/`docsUrl`;
2755
+ * an internal-coded error keeps its status but its message is redacted; anything
2756
+ * else becomes a generic `INTERNAL` 500. All of these share the unified shape
2757
+ * recognized by `isLunoraError`.
2758
+ */
2759
+ declare const toErrorResponse: (error: unknown) => Response;
2112
2760
  /**
2113
- * Error type recognised by the runtime's error middleware. Anything thrown
2114
- * that isn't a `LunoraError` is mapped to a generic 500 with code `INTERNAL`.
2115
- */
2116
- declare class LunoraError extends Error {
2117
- readonly code: string;
2118
- readonly status: number;
2761
+ * Transport-level error for the worker entry. A thin ergonomic wrapper over the
2762
+ * shared `@lunora/errors` `LunoraError` that keeps the runtime's historical
2763
+ * `(message, { code, status })` signature — the runtime mints these with
2764
+ * dispatch-specific codes (`METHOD_NOT_ALLOWED`, `*_NOT_CONFIGURED`, …) and an
2765
+ * explicit status, so they don't need a central catalog entry. Because it is a
2766
+ * real `LunoraError`, it carries the unified wire shape and is recognized by
2767
+ * `isLunoraError` everywhere. Anything thrown that isn't a `LunoraError`
2768
+ * is mapped to a generic 500 with code `INTERNAL`.
2769
+ */
2770
+ declare class LunoraError extends LunoraError$1 {
2119
2771
  constructor(message: string, options?: {
2120
2772
  cause?: unknown;
2121
2773
  code?: string;
@@ -2123,82 +2775,94 @@ declare class LunoraError extends Error {
2123
2775
  });
2124
2776
  toResponse(): Response;
2125
2777
  }
2126
- /** Shape recognised by the runtime's structural error checks. */
2127
-
2128
- /** Convert any thrown value into a JSON error response. */
2129
- declare const toErrorResponse: (error: unknown) => Response;
2130
2778
  /** Shared shape for sinks that can be limited to error events only. */
2131
2779
  interface OnlyErrorsOption {
2132
2780
  /** When true, only events with `ok === false` are forwarded. */
2133
2781
  onlyErrors?: boolean;
2134
2782
  }
2135
2783
  /**
2136
- * A sink that logs each event via `console`.
2137
- *
2138
- * Useful as a zero-config default during development, or wired behind
2139
- * {@link combineSinks} alongside a network sink. Successful events are logged
2140
- * with `console.log`; error events (`ok === false`) with `console.error`.
2141
- * @param options Sink options; set `onlyErrors` to log error events only.
2142
- */
2784
+ * A sink that logs each event via `console`.
2785
+ *
2786
+ * Useful as a zero-config default during development, or wired behind
2787
+ * {@link combineSinks} alongside a network sink. Successful events are logged
2788
+ * with `console.log`; error events (`ok === false`) with `console.error`.
2789
+ * @param options Sink options; set `onlyErrors` to log error events only.
2790
+ */
2143
2791
  declare const consoleSink: (options?: OnlyErrorsOption) => ObservabilitySink;
2144
2792
  /** Options for {@link webhookSink}. */
2145
2793
  interface WebhookSinkOptions extends OnlyErrorsOption {
2146
2794
  /**
2147
- * Extra headers merged onto the POST. `Content-Type: application/json` is
2148
- * set by default and may be overridden here (e.g. to add an
2149
- * `Authorization` / API-key header for Axiom, Datadog, etc.).
2150
- */
2795
+ * Extra headers merged onto the POST. `Content-Type: application/json` is
2796
+ * set by default and may be overridden here (e.g. to add an
2797
+ * `Authorization` / API-key header for Axiom, Datadog, etc.).
2798
+ */
2151
2799
  headers?: Record<string, string>;
2152
2800
  /**
2153
- * Optional redaction hook applied to each event immediately before it is
2154
- * serialized and shipped. Use it to scrub or drop PII (e.g. strip
2155
- * `error.message`) before it leaves the worker. Return the (possibly
2156
- * modified) event to send, or `null`/`undefined` to drop the event
2157
- * entirely. A throwing `transform` drops the event (fail-closed) so a buggy
2158
- * redactor can never leak the un-scrubbed payload.
2159
- */
2801
+ * Optional redaction hook applied to each event immediately before it is
2802
+ * serialized and shipped. Use it to scrub or drop PII (e.g. strip
2803
+ * `error.message`) before it leaves the worker. Return the (possibly
2804
+ * modified) event to send, or `null`/`undefined` to drop the event
2805
+ * entirely. A throwing `transform` drops the event (fail-closed) so a buggy
2806
+ * redactor can never leak the un-scrubbed payload.
2807
+ */
2160
2808
  transform?: (event: ObservabilityEvent) => null | ObservabilityEvent | undefined;
2809
+ /**
2810
+ * Optional redaction hook for `ctx.log` events (the `transform`
2811
+ * counterpart for log lines). Same fail-closed contract: return the event to
2812
+ * ship it, `null`/`undefined` to drop it, and a throw drops it. When unset,
2813
+ * log events are shipped as-is (message + structured fields — which may carry
2814
+ * user input; see the privacy note).
2815
+ */
2816
+ transformLog?: (event: LogEvent) => LogEvent | null | undefined;
2161
2817
  /** The ingestion endpoint to POST each event to. */
2162
2818
  url: string;
2163
2819
  }
2164
2820
  /**
2165
- * A fire-and-forget sink that POSTs each event as JSON to an HTTP endpoint.
2166
- *
2167
- * This covers Axiom, Datadog, and any generic webhook/log-ingestion service —
2168
- * point `url` at the ingestion endpoint and supply auth via `headers`. Each
2169
- * event is sent as its own `fetch`. When the runtime supplies a per-event
2170
- * `context.waitUntil` (the request's `ctx.waitUntil`), the send is registered
2171
- * with it so it survives isolate teardown after the response returns; otherwise
2172
- * it degrades to fire-and-forget. Either way its rejection is swallowed so a
2173
- * flaky endpoint never surfaces to the caller.
2174
- *
2175
- * Privacy: the full event is serialized, including `error.message`, which may
2176
- * contain user input. See the module-level note. Pass a `transform` callback to
2177
- * scrub or drop fields before they leave the worker.
2178
- * @param options Sink options: `url` is the POST target, `headers` are merged
2179
- * request headers (e.g. an API key), `onlyErrors` ships error events only, and
2180
- * `transform` redacts/drops each event before send.
2181
- */
2821
+ * A fire-and-forget sink that POSTs each event as JSON to an HTTP endpoint.
2822
+ *
2823
+ * This covers Axiom, Datadog, and any generic webhook/log-ingestion service —
2824
+ * point `url` at the ingestion endpoint and supply auth via `headers`. Each
2825
+ * event is sent as its own `fetch`. When the runtime supplies a per-event
2826
+ * `context.waitUntil` (the request's `ctx.waitUntil`), the send is registered
2827
+ * with it so it survives isolate teardown after the response returns; otherwise
2828
+ * it degrades to fire-and-forget. Either way its rejection is swallowed so a
2829
+ * flaky endpoint never surfaces to the caller.
2830
+ *
2831
+ * Privacy: the full event is serialized, including `error.message`, which may
2832
+ * contain user input. See the module-level note. Pass a `transform` callback to
2833
+ * scrub or drop fields before they leave the worker.
2834
+ * @param options Sink options: `url` is the POST target, `headers` are merged
2835
+ * request headers (e.g. an API key), `onlyErrors` ships error events only, and
2836
+ * `transform` redacts/drops each event before send.
2837
+ */
2182
2838
  declare const webhookSink: (options: WebhookSinkOptions) => ObservabilitySink;
2183
2839
  /** Options for {@link sentrySink}. */
2184
2840
  interface SentrySinkOptions extends OnlyErrorsOption {
2185
2841
  /**
2186
- * User-supplied capture callback. Wire this to your Sentry client, e.g.
2187
- * `(event) => Sentry.captureMessage(...)` or `captureException`. Kept as an
2188
- * injected callback so the runtime takes no dependency on `@sentry/*`.
2189
- */
2842
+ * User-supplied capture callback. Wire this to your Sentry client, e.g.
2843
+ * `(event) => Sentry.captureMessage(...)` or `captureException`. Kept as an
2844
+ * injected callback so the runtime takes no dependency on `@sentry/*`.
2845
+ */
2190
2846
  capture: (event: ObservabilityEvent) => void;
2191
- }
2192
- /**
2193
- * A thin adapter that forwards events to an injected `capture` callback.
2194
- *
2195
- * Intentionally does NOT bundle `@sentry/*`: the user wires their own Sentry
2196
- * client (`captureException` / `captureMessage`) into `capture`, giving Sentry
2197
- * parity without a hard dependency. The callback is invoked inside a try/catch
2198
- * so a throwing client can't break dispatch.
2199
- * @param options Sink options: `capture` is invoked per forwarded event;
2200
- * `onlyErrors` defaults to true (error events only) — pass `false` for all.
2201
- */
2847
+ /**
2848
+ * Optional callback for `ctx.log` events. Wire it to Sentry's structured
2849
+ * logging or a breadcrumb, e.g. `(e) => Sentry.logger[e.level]?.(e.message,
2850
+ * e.fields)`. Omit it to leave `ctx.log` lines out of Sentry entirely
2851
+ * (capturing every log line would usually flood the project). Invoked inside
2852
+ * a try/catch so a throwing client can't break the handler.
2853
+ */
2854
+ captureLog?: (event: LogEvent) => void;
2855
+ }
2856
+ /**
2857
+ * A thin adapter that forwards events to an injected `capture` callback.
2858
+ *
2859
+ * Intentionally does NOT bundle `@sentry/*`: the user wires their own Sentry
2860
+ * client (`captureException` / `captureMessage`) into `capture`, giving Sentry
2861
+ * parity without a hard dependency. The callback is invoked inside a try/catch
2862
+ * so a throwing client can't break dispatch.
2863
+ * @param options Sink options: `capture` is invoked per forwarded event;
2864
+ * `onlyErrors` defaults to true (error events only) — pass `false` for all.
2865
+ */
2202
2866
  declare const sentrySink: (options: SentrySinkOptions) => ObservabilitySink;
2203
2867
  /** One Analytics Engine data point — the structural subset {@link analyticsEngineSink} writes. */
2204
2868
  interface AnalyticsEngineDataPointLike {
@@ -2210,11 +2874,11 @@ interface AnalyticsEngineDataPointLike {
2210
2874
  indexes?: (null | string)[];
2211
2875
  }
2212
2876
  /**
2213
- * The Cloudflare Analytics Engine dataset binding surface this sink needs — the
2214
- * `env` binding declared in `wrangler.jsonc` under `analytics_engine_datasets`.
2215
- * Typed structurally so the runtime takes no dependency on
2216
- * `@cloudflare/workers-types`.
2217
- */
2877
+ * The Cloudflare Analytics Engine dataset binding surface this sink needs — the
2878
+ * `env` binding declared in `wrangler.jsonc` under `analytics_engine_datasets`.
2879
+ * Typed structurally so the runtime takes no dependency on
2880
+ * `@cloudflare/workers-types`.
2881
+ */
2218
2882
  interface AnalyticsEngineDatasetLike {
2219
2883
  writeDataPoint: (point: AnalyticsEngineDataPointLike) => void;
2220
2884
  }
@@ -2224,36 +2888,289 @@ interface AnalyticsEngineSinkOptions extends OnlyErrorsOption {
2224
2888
  dataset: AnalyticsEngineDatasetLike;
2225
2889
  }
2226
2890
  /**
2227
- * A sink that writes each event to a Cloudflare Analytics Engine dataset.
2228
- *
2229
- * Analytics Engine is the platform's unbounded-cardinality, sampled time-series
2230
- * store — the natural backing for high-volume RPC observability metrics, queried
2231
- * later over SQL. Prefer it over rolling your own counters table for anything
2232
- * that doesn't need to be exact. Each event maps to one data point.
2233
- *
2234
- * indexes: `[functionPath]` — the sampling key, so Analytics Engine samples per
2235
- * function rather than globally.
2236
- *
2237
- * blobs (string dimensions): `[functionPath, ok-or-error, shardKey, error.code,
2238
- * fanOut.table]` — group/filter dimensions; absent fields are the empty string.
2239
- *
2240
- * doubles (numeric metrics): `[durationMs, errorCount, fanOut.shards,
2241
- * fanOut.failed]` where errorCount is 0 or 1 — so `SUM(double2)` is the error
2242
- * count and `AVG(double1)` the latency.
2243
- *
2244
- * `writeDataPoint` is fire-and-forget on the platform; the call is still wrapped
2245
- * in a try/catch so a missing/throwing binding can never break dispatch.
2246
- * @param options Sink options: `dataset` is the AE binding; `onlyErrors` writes
2247
- * only error events (defaults to all events).
2248
- */
2891
+ * A sink that writes each event to a Cloudflare Analytics Engine dataset.
2892
+ *
2893
+ * Analytics Engine is the platform's unbounded-cardinality, sampled time-series
2894
+ * store — the natural backing for high-volume RPC observability metrics, queried
2895
+ * later over SQL. Prefer it over rolling your own counters table for anything
2896
+ * that doesn't need to be exact. Each event maps to one data point.
2897
+ *
2898
+ * indexes: `[functionPath]` — the sampling key, so Analytics Engine samples per
2899
+ * function rather than globally.
2900
+ *
2901
+ * blobs (string dimensions): `[functionPath, ok-or-error, shardKey, error.code,
2902
+ * fanOut.table]` — group/filter dimensions; absent fields are the empty string.
2903
+ *
2904
+ * doubles (numeric metrics): `[durationMs, errorCount, fanOut.shards,
2905
+ * fanOut.failed]` where errorCount is 0 or 1 — so `SUM(double2)` is the error
2906
+ * count and `AVG(double1)` the latency.
2907
+ *
2908
+ * `writeDataPoint` is fire-and-forget on the platform; the call is still wrapped
2909
+ * in a try/catch so a missing/throwing binding can never break dispatch.
2910
+ * @param options Sink options: `dataset` is the AE binding; `onlyErrors` writes
2911
+ * only error events (defaults to all events).
2912
+ */
2249
2913
  declare const analyticsEngineSink: (options: AnalyticsEngineSinkOptions) => ObservabilitySink;
2250
2914
  /**
2251
- * Combine several sinks into one that fans each event out to all of them.
2252
- *
2253
- * Each child sink is invoked in order; a throw from one does not prevent the
2254
- * others from running (each call is individually guarded).
2255
- * @param sinks The sinks to fan out to.
2256
- */
2915
+ * The Cloudflare Pipeline binding surface {@link pipelineLogSink} needs the
2916
+ * `env` binding declared in `wrangler.jsonc` under `pipelines`. Typed
2917
+ * structurally (mirrors `@lunora/bindings/pipelines`' `PipelineBindingLike`) so
2918
+ * the runtime takes no dependency on `@lunora/bindings` or `@cloudflare/workers-types`.
2919
+ */
2920
+ interface PipelineLike {
2921
+ /** Durably ingest a batch of records (buffered to R2, read back later with R2 SQL). */
2922
+ send: (records: Record<string, unknown>[]) => Promise<void>;
2923
+ }
2924
+ /** Options for {@link pipelineLogSink}. */
2925
+ interface PipelineLogSinkOptions {
2926
+ /** The Cloudflare Pipeline binding each log record is durably sent to. */
2927
+ pipeline: PipelineLike;
2928
+ /**
2929
+ * When true, `fields` is written as a **JSON string** (`JSON.stringify`)
2930
+ * rather than a nested object. Defaults to `false` for back-compatibility.
2931
+ *
2932
+ * Turn it on when the destination Iceberg table types `fields` as a `string`
2933
+ * column so the archive stays queryable (R2 SQL can `LIKE`/compare a string
2934
+ * column, but not index into an arbitrarily-shaped struct). The reader
2935
+ * (`createPipelineLogReader`) parses such a JSON string back to an object on
2936
+ * read. Leave it off when the table types `fields` as a native struct.
2937
+ */
2938
+ serializeFields?: boolean;
2939
+ }
2940
+ /**
2941
+ * A sink that durably persists each `ctx.log` line to a Cloudflare Pipeline
2942
+ * (→ R2), so an app has a queryable log store WITHOUT the Cloud — read the
2943
+ * archived records back with R2 SQL. This is the durable counterpart to the
2944
+ * network {@link otlpSink}: where OTLP streams to a collector, this lands the
2945
+ * structured record (message, level, function path, fields, trace ids, shard,
2946
+ * user, timestamp) in object storage under the app's own account.
2947
+ *
2948
+ * **Written-column contract.** Each record is a flat object; this is the exact
2949
+ * read-side schema `createPipelineLogReader` (`pipeline-log-reader.ts`) mirrors
2950
+ * in its `DEFAULT_LOG_COLUMNS`. Keep the two in lockstep — a column added here
2951
+ * must gain a default there:
2952
+ * - `functionPath` (string) — always present
2953
+ * - `level` (string severity) — always present
2954
+ * - `message` (string) — always present
2955
+ * - `ts` (number, epoch-millis) — always present
2956
+ * - `fields` (nested object, or a JSON string when `serializeFields`) — when set
2957
+ * - `shardKey` (string) — when set
2958
+ * - `userId` (string) — when set
2959
+ * - `traceId` (string) — when set
2960
+ * - `spanId` (string) — when set
2961
+ *
2962
+ * Only `onLog` is implemented — RPC-span metrics belong in
2963
+ * {@link analyticsEngineSink}. `Pipeline.send` is durable/fire-and-forget on the
2964
+ * platform; the call is registered with the request's `context.waitUntil` when
2965
+ * present (the DO threads its `state.waitUntil`) so the send survives isolate
2966
+ * teardown, and every rejection is swallowed so a flaky pipeline never surfaces
2967
+ * to the caller.
2968
+ *
2969
+ * Privacy: the persisted record carries `message` + structured `fields` (not the
2970
+ * raw positional args). They may include user input — the R2 bucket is your own,
2971
+ * but treat it as a log store and gate PII upstream if that is a concern.
2972
+ * @param options Sink options: `pipeline` is the Cloudflare Pipeline binding;
2973
+ * `serializeFields` stores `fields` as a queryable JSON string.
2974
+ */
2975
+ declare const pipelineLogSink: (options: PipelineLogSinkOptions) => ObservabilitySink;
2976
+ /** Options for {@link otlpSink}. */
2977
+ interface OtlpSinkOptions extends OnlyErrorsOption {
2978
+ /**
2979
+ * The OTLP-over-HTTP collector base endpoint (e.g.
2980
+ * `https://collector.example.com`). Following the OTel base-endpoint
2981
+ * convention, the sink POSTs spans to `${endpoint}/v1/traces` and log
2982
+ * records to `${endpoint}/v1/logs`; a trailing slash is tolerated.
2983
+ */
2984
+ endpoint: string;
2985
+ /**
2986
+ * Extra headers merged onto every OTLP POST — typically an `Authorization`
2987
+ * bearer plus the `x-lunora-deployment` / `x-lunora-org` correlation headers
2988
+ * the platform injects at deploy. `Content-Type: application/json` is set by
2989
+ * default and may be overridden here.
2990
+ */
2991
+ headers?: Record<string, string>;
2992
+ /**
2993
+ * Value of the `service.name` resource attribute on every exported span and
2994
+ * log — the logical service the telemetry belongs to. Defaults to `lunora`.
2995
+ */
2996
+ serviceName?: string;
2997
+ /**
2998
+ * Convenience bearer token: when set, an `Authorization: Bearer` header
2999
+ * carrying it is added to every POST (overriding any authorization in
3000
+ * `headers`). Mirrors the container exporter so the platform can inject the
3001
+ * same `LUNORA_OTLP_TOKEN` into both. Leave unset for an unauthenticated collector.
3002
+ */
3003
+ token?: string;
3004
+ }
3005
+ /**
3006
+ * A fire-and-forget sink that exports telemetry over OTLP-over-HTTP (JSON).
3007
+ *
3008
+ * This is the single, standard wire contract both the worker and (via the
3009
+ * container exporter helper) container processes use, so telemetry from either
3010
+ * side lands in the same collector. Each RPC dispatch becomes one OTLP **span**
3011
+ * (`${endpoint}/v1/traces`) named after its `functionPath`, with start/end
3012
+ * derived from `durationMs` and status OK/ERROR; each `ctx.log.*` line becomes
3013
+ * one OTLP **log record** (`${endpoint}/v1/logs`). Spans and log records reuse
3014
+ * the dispatch's `traceId`/`spanId` (minted at dispatch entry and propagated to
3015
+ * the shard and any container as a `traceparent`), so a handler's logs, its RPC
3016
+ * span, and the container spans beneath it all stitch into one trace; ids are
3017
+ * only randomised on paths that carry no trace context.
3018
+ *
3019
+ * Like {@link webhookSink}, each export is its own `fetch`, registered with the
3020
+ * request's `context.waitUntil` when present so it survives isolate teardown,
3021
+ * and every rejection is swallowed so a flaky collector never surfaces to the
3022
+ * caller.
3023
+ *
3024
+ * Privacy: spans carry `error.type`/`error.message` and logs carry the rendered
3025
+ * `message`, which may include user input. Point `endpoint` only at a collector
3026
+ * you trust, and gate PII upstream if that is a concern.
3027
+ * @param options Sink options: `endpoint` is the collector base URL, `headers`
3028
+ * are merged onto every POST (auth + correlation), `serviceName` sets the
3029
+ * resource `service.name`, and `onlyErrors` exports error spans only.
3030
+ */
3031
+ declare const otlpSink: (options: OtlpSinkOptions) => ObservabilitySink;
3032
+ /**
3033
+ * Combine several sinks into one that fans each event out to all of them.
3034
+ *
3035
+ * Each child sink is invoked in order; a throw from one does not prevent the
3036
+ * others from running (each call is individually guarded).
3037
+ * @param sinks The sinks to fan out to.
3038
+ */
2257
3039
  declare const combineSinks: (...sinks: ObservabilitySink[]) => ObservabilitySink;
3040
+ /**
3041
+ * The written-column contract: every field `pipelineLogSink` emits, mapped to the
3042
+ * column it is stored under by default (the identity mapping). Also the source of
3043
+ * truth for the {@link PipelineLogField} union. Mirrors the record built in
3044
+ * `pipelineLogSink` — the read side of the same contract.
3045
+ */
3046
+ declare const DEFAULT_COLUMNS: {
3047
+ readonly fields: "fields";
3048
+ readonly functionPath: "functionPath";
3049
+ readonly level: "level";
3050
+ readonly message: "message";
3051
+ readonly shardKey: "shardKey";
3052
+ readonly spanId: "spanId";
3053
+ readonly traceId: "traceId";
3054
+ readonly ts: "ts";
3055
+ readonly userId: "userId";
3056
+ };
3057
+ /**
3058
+ * The canonical field names of one persisted log record — the keys
3059
+ * `pipelineLogSink` writes. Used as the {@link PipelineLogColumnMap} keys and the
3060
+ * {@link PipelineLogRow} shape, so the reader stays decoupled from whatever
3061
+ * physical column names the operator's Iceberg table happens to use.
3062
+ */
3063
+ type PipelineLogField = keyof typeof DEFAULT_COLUMNS;
3064
+ /**
3065
+ * Field-to-column-name map. Defaults to the identity mapping (each field stored
3066
+ * under its own name, matching what `pipelineLogSink` writes). Override per-field
3067
+ * when the Iceberg schema renames a column; unspecified fields keep their
3068
+ * default. This is the single knob that lets one reader serve differently shaped
3069
+ * Data Catalog tables.
3070
+ */
3071
+ type PipelineLogColumnMap = Partial<Record<PipelineLogField, string>>;
3072
+ /** An opaque keyset cursor: the `ts` of the row after the last one returned. */
3073
+ interface PipelineLogCursor {
3074
+ /** Epoch-millis boundary; the next page is every row strictly older than this. */
3075
+ ts: number;
3076
+ }
3077
+ /** Filters for one {@link PipelineLogReader} query. Every value is inlined safely (`lit`/`sql`). */
3078
+ interface PipelineLogQuery {
3079
+ /** Continue after a previous page (keyset on `ts DESC`). Combined with the other filters. */
3080
+ cursor?: PipelineLogCursor;
3081
+ /** Match only this exact function path's records. Prefer `functionPathPrefix` for a namespace sweep. */
3082
+ functionPath?: string;
3083
+ /** Match records whose `functionPath` starts with this string (rendered as a `LIKE 'prefix%'`). */
3084
+ functionPathPrefix?: string;
3085
+ /** Match only this exact severity. When set, `minLevel` is ignored for the same field. */
3086
+ level?: ContextLogLevel;
3087
+ /** Max rows to return. Clamped to `[1, 10000]`; defaults to {@link DEFAULT_LOG_LIMIT}. */
3088
+ limit?: number;
3089
+ /** Severity floor: keep every level at or above this in {@link LOG_LEVEL_ORDER} (e.g. `warn` keeps warn, error, fatal). */
3090
+ minLevel?: ContextLogLevel;
3091
+ /** Match only this shard key. */
3092
+ shardKey?: string;
3093
+ /** Lower time bound, inclusive (`ts` at or after this), epoch-millis. */
3094
+ sinceTs?: number;
3095
+ /** Match only this trace id. */
3096
+ traceId?: string;
3097
+ /** Upper time bound, inclusive (`ts` at or before this), epoch-millis. */
3098
+ untilTs?: number;
3099
+ /** Match only this acting user id. */
3100
+ userId?: string;
3101
+ }
3102
+ /**
3103
+ * One decoded log record. Always keyed by the canonical {@link PipelineLogField}
3104
+ * names regardless of the physical columns (the reader remaps via `columnMap`),
3105
+ * so consumers never see the operator's storage names.
3106
+ */
3107
+ interface PipelineLogRow {
3108
+ /**
3109
+ * Structured fields, when the record carried them. A `serializeFields` sink
3110
+ * stores these as a JSON string, which the reader parses back to an object;
3111
+ * a plain string that is not valid JSON is returned verbatim.
3112
+ */
3113
+ fields?: unknown;
3114
+ /** Function path that emitted the line, e.g. `"messages:list"`. */
3115
+ functionPath: string;
3116
+ /** Severity the line was logged at. */
3117
+ level: ContextLogLevel;
3118
+ /** Rendered message. */
3119
+ message: string;
3120
+ /** Shard key for single-shard calls, when present. */
3121
+ shardKey?: string;
3122
+ /** Span id the line was emitted under, when present. */
3123
+ spanId?: string;
3124
+ /** Trace id the line belongs to, when present. */
3125
+ traceId?: string;
3126
+ /** Epoch-millis the line was emitted. */
3127
+ ts: number;
3128
+ /** Acting user id, when present. */
3129
+ userId?: string;
3130
+ }
3131
+ /** One page of {@link PipelineLogRow}s, newest first, plus the cursor for the next page (absent means last page). */
3132
+ interface PipelineLogPage {
3133
+ /** The cursor to pass as {@link PipelineLogQuery} `cursor` for the following page; absent when this is the last page. */
3134
+ nextCursor?: PipelineLogCursor;
3135
+ /** The rows, ordered `ts DESC` (newest first). At most `limit` of them. */
3136
+ rows: PipelineLogRow[];
3137
+ }
3138
+ /** Options for {@link createPipelineLogReader}. */
3139
+ interface PipelineLogReaderOptions {
3140
+ /**
3141
+ * Override any physical column name that diverges from the default (identity)
3142
+ * mapping. Unspecified fields keep their {@link DEFAULT_LOG_COLUMNS} name.
3143
+ */
3144
+ columnMap?: PipelineLogColumnMap;
3145
+ /**
3146
+ * The Iceberg namespace the `table` lives in (R2 Data Catalog database).
3147
+ * Combined as `namespace.table` in the `FROM` clause; omit when `table`
3148
+ * already carries its namespace.
3149
+ */
3150
+ namespace?: string;
3151
+ /** The Iceberg table name the Pipeline writes log records to (e.g. `"logs"`). */
3152
+ table: string;
3153
+ }
3154
+ /** The reader surface: a single keyset-paginated {@link PipelineLogPage} query. */
3155
+ interface PipelineLogReader {
3156
+ /** Run one filtered, keyset-paginated read and return a {@link PipelineLogPage}. */
3157
+ query: (query?: PipelineLogQuery) => Promise<PipelineLogPage>;
3158
+ }
3159
+ /** The written-column contract exposed publicly: canonical field to default physical column name. */
3160
+ declare const DEFAULT_LOG_COLUMNS: Readonly<Record<PipelineLogField, string>>;
3161
+ /** Default page size when a query omits `limit`. */
3162
+ declare const DEFAULT_LOG_LIMIT: number;
3163
+ /**
3164
+ * Build a durable-log reader over one R2 Data Catalog (Iceberg) table.
3165
+ *
3166
+ * The returned {@link PipelineLogReader} compiles each call to a safe
3167
+ * `SELECT ... WHERE ... ORDER BY ts DESC LIMIT n` (over-fetching by one) and
3168
+ * decodes the rows back to the canonical {@link PipelineLogRow} shape. All value
3169
+ * filters are escaped through `@lunora/bindings/r2sql`'s `sql`/`lit`; column
3170
+ * names come from `options.columnMap` (operator config), spliced with `raw`.
3171
+ * @param client An {@link R2SqlClient} (`createR2Sql({ accountId, apiToken, bucket })`).
3172
+ * @param options The target `table` (plus optional `namespace`) and any `columnMap` overrides.
3173
+ */
3174
+ declare const createPipelineLogReader: (client: R2SqlClient, options: PipelineLogReaderOptions) => PipelineLogReader;
2258
3175
  declare const VERSION: string;
2259
- export { type AdminTableResolver, type AirbyteMessage, type AnalyticsEngineDataPointLike, type AnalyticsEngineDatasetLike, type AnalyticsEngineSinkOptions, type AuthAdmin, type AuthCapabilities, type AuthImpersonation, type AuthIntrospector, type AuthPage, type AuthSession, type AuthUser, type BackupManifest, type BackupStore, type ConnectorChange, type ConnectorSyncPage, type CorsOptions, type CronHandler, type CronJobDispatch, type CronJobInfo, type CrossShardCounter, type CrossShardReader, type CrossShardRelationCapabilities, type CrossShardRelationOptions, type CsrfOptions, DEFAULT_REGISTRY_CACHE_TTL_MS, type DynamicShardRegistry, type DynamicShardRegistryOptions, type ExecutionContextLike, type ExportFanOutRequest, type ExportFanOutResult, type FanOutRequest, type FanOutResult, type FanOutSpec, type FivetranResponse, type FrameworkHostHandler, type FrameworkWorkerOptions, type FrameworkWorkerOptionsInput, type FunctionDescriptor, type FunctionRegistryEntry, type FunctionRegistryLike, type GlobalExportFunction as GlobalExportFn, type GlobalImportFunction as GlobalImportFn, type GlobalIntrospector, type GlobalTableInfo as GlobalTableInfoMeta, type GlobalTablePage as GlobalTablePageMeta, type HttpActionContext, type HttpActionLike, type HttpRouterLike, type ImportFanOutRequest, type ImportFanOutResult, type ListAuthUsersOptions, type LogEvent, type LogLevel, LunoraError, type LunoraErrorBody, type LunoraWorker, type MergeStrategy, type MigrationFanOutRequest, type MigrationFanOutResult, type ObservabilityEvent, type ObservabilitySink, type ObservabilitySinkContext, type QueryCoordinator, type QueryCoordinatorOptions, type RankFanOutRequest, type RankFanOutResult, type RankPageFanOutRequest, type RankPageFanOutResult, type ResolvedSecurity, type ResolvedShard, type Route, type RpcContext, type RpcEnvelope, SHARD_REGISTRY_DO_NAME, type ScheduledControllerLike, type SecurityHeadersOptions, type SecurityOptions, type SentrySinkOptions, type ShardError, type ShardExportOutcome, type ShardImportOutcome, type ShardMigrationOutcome, type ShardNamespaceLike, type ShardRankOutcome, type ShardRankPageOutcome, type ShardRegistry, type ShardTrafficEntry, type ShardTrafficFanOutRequest, type ShardTrafficFanOutResult, type ShardingInfo, type StorageListFunction as StorageListFn, type StorageObject, VERSION, type VectorIndexSummary, type VectorIntrospector, type VectorQueryMatch, type WebhookSinkOptions, type WorkerOptions, analyticsEngineSink, combineSinks, composeWorker, consoleSink, createCrossShardRelationCapabilities, createDynamicShardRegistry, createQueryCoordinator, createStaticShardRegistry, createWorker, decorateResponse, defineRpcEnvelope, emitLogEvent, emitRpcEvent, enforceOrigin, handleCorsPreflight, mergeStrategyForAggregate, resolveSecurity, resolveShard, sentrySink, toAirbyteMessages, toErrorResponse, toFivetranResponse, webhookSink, withFrameworkWorker };
3176
+ export { type AdminTableResolver, type AirbyteMessage, type AnalyticsEngineDataPointLike, type AnalyticsEngineDatasetLike, type AnalyticsEngineSinkOptions, type AuthAdmin, type AuthCapabilities, type AuthConfigInfo, type AuthImpersonation, type AuthPage, type AuthSession, type AuthUser, type AuthUserFieldSpec, type BackupManifest, type BackupStore, type ComposeIdentityResolversErrorMode, type ComposeIdentityResolversOptions, type ConnectorChange, type ConnectorSyncPage, type CorsOptions, type CronHandler, type CronJobDispatch, type CronJobInfo, type CrossShardCounter, type CrossShardReader, type CrossShardRelationCapabilities, type CrossShardRelationOptions, type CsrfOptions, DEFAULT_LOG_COLUMNS, DEFAULT_LOG_LIMIT, DEFAULT_REGISTRY_CACHE_TTL_MS, type DurableObjectJurisdiction, type DynamicShardRegistry, type DynamicShardRegistryOptions, type ExecutionContextLike, type ExportFanOutRequest, type ExportFanOutResult, type FanOutRequest, type FanOutResult, type FanOutSpec, type FivetranResponse, type FrameworkHostHandler, type FrameworkWorkerOptions, type FrameworkWorkerOptionsInput, type FunctionDescriptor, type FunctionRegistryEntry, type FunctionRegistryLike, type GlobalExportFunction as GlobalExportFn, type GlobalImportFunction as GlobalImportFn, type GlobalIntrospector, type GlobalTableInfo as GlobalTableInfoMeta, type GlobalTablePage as GlobalTablePageMeta, type HttpActionContext, type HttpActionLike, type HttpRouterLike, type IdentityContractLike, type IdentityResolver, type IdentityValidation, type ImportFanOutRequest, type ImportFanOutResult, type KvIntrospector, type KvKeyEntry, type KvKeyListResult, type KvNamespaceSummary, type KvValueResult, type ListAuthUsersOptions, type LogEvent, type LogFields, type LogLevel, LunoraError, type LunoraErrorBody, type LunoraHandlerOptions, type LunoraWorker, type MergeStrategy, type MetricEvent, type MetricKind, type MigrationFanOutRequest, type MigrationFanOutResult, NOOP_EXECUTION_CONTEXT, type ObservabilityEvent, type ObservabilitySink, type ObservabilitySinkContext, type OtlpSinkOptions, type PipelineLike, type PipelineLogColumnMap, type PipelineLogCursor, type PipelineLogField, type PipelineLogPage, type PipelineLogQuery, type PipelineLogReader, type PipelineLogReaderOptions, type PipelineLogRow, type PipelineLogSinkOptions, type QueryCoordinator, type QueryCoordinatorOptions, type RankFanOutRequest, type RankFanOutResult, type RankPageFanOutRequest, type RankPageFanOutResult, type ResolvedSecurity, type ResolvedShard, type Route, type RpcContext, type RpcEnvelope, SHARD_REGISTRY_DO_NAME, type ScheduledControllerLike, type SecurityHeadersOptions, type SecurityOptions, type SentrySinkOptions, type ShardError, type ShardExportOutcome, type ShardImportOutcome, type ShardMigrationOutcome, type ShardNamespaceLike, type ShardRankOutcome, type ShardRankPageOutcome, type ShardRegistry, type ShardTrafficEntry, type ShardTrafficFanOutRequest, type ShardTrafficFanOutResult, type ShardingInfo, type SpanEvent, type StorageListFunction as StorageListFn, type StorageObject, VERSION, type VectorIndexSummary, type VectorIntrospector, type VectorQueryMatch, type WebhookSinkOptions, type WorkerOptions, analyticsEngineSink, applyJurisdiction, combineSinks, composeIdentityResolvers, composeWorker, consoleSink, createCrossShardRelationCapabilities, createDynamicShardRegistry, createLunoraHandler, createPipelineLogReader, createQueryCoordinator, createStaticShardRegistry, createWorker, decorateResponse, defineRpcEnvelope, emitLogEvent, emitRpcEvent, enforceOrigin, handleCorsPreflight, mergeStrategyForAggregate, otlpSink, pipelineLogSink, resolveLunoraOptions, resolveSecurity, resolveShard, routeIdentityResolvers, sentrySink, toAirbyteMessages, toErrorResponse, toFivetranResponse, webhookSink, withFrameworkWorker };