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