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