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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE.md +219 -0
  2. package/README.md +50 -0
  3. package/dist/data-model.d.mts +231 -156
  4. package/dist/data-model.d.ts +231 -156
  5. package/dist/data-model.mjs +0 -1
  6. package/dist/drizzle.mjs +1 -1
  7. package/dist/index.d.mts +1304 -831
  8. package/dist/index.d.ts +1304 -831
  9. package/dist/index.mjs +1 -24
  10. package/dist/otel.d.mts +543 -0
  11. package/dist/otel.d.ts +543 -0
  12. package/dist/otel.mjs +1 -0
  13. package/dist/packem_shared/LunoraEnvError-CgpI2Mm_.mjs +3 -0
  14. package/dist/packem_shared/LunoraError-LVhdU0Lo.mjs +1 -0
  15. package/dist/packem_shared/PRESENCE_DEFAULT_TTL_MS-CF_9uiu1.mjs +1 -0
  16. package/dist/packem_shared/allowAll-BnyNbJZT.mjs +1 -0
  17. package/dist/packem_shared/asBucketStorage-1pFfH-Tn.mjs +1 -0
  18. package/dist/packem_shared/bindOrm-Bp9hsM2q.mjs +1 -0
  19. package/dist/packem_shared/buildRlsReadRegistry-WdiqSj87.mjs +1 -0
  20. package/dist/packem_shared/composePluginMiddleware-COr09CXA.mjs +1 -0
  21. package/dist/packem_shared/context-identity-GunnA6La.mjs +1 -0
  22. package/dist/packem_shared/createPolicyDsl-sV1swpkD.mjs +1 -0
  23. package/dist/packem_shared/createSecrets-CgVPiW2C.mjs +1 -0
  24. package/dist/packem_shared/defineAggregateIndex-CYuH6IbJ.mjs +1 -0
  25. package/dist/packem_shared/defineIdentity-B7gfAgxx.mjs +1 -0
  26. package/dist/packem_shared/defineMigration-Bfpwxv2f.mjs +1 -0
  27. package/dist/packem_shared/defineMutator-BgpQ-xUo.mjs +1 -0
  28. package/dist/packem_shared/defineShape-Ds8uNqzX.mjs +1 -0
  29. package/dist/packem_shared/defineStorageRule-BDu01PUn.mjs +1 -0
  30. package/dist/packem_shared/functions-CDC08CWY.mjs +1 -0
  31. package/dist/packem_shared/httpAction-C14NuF3V.mjs +4 -0
  32. package/dist/packem_shared/initLunora-CHh1EmR4.mjs +1 -0
  33. package/dist/packem_shared/mask-C6Bi78qj.mjs +1 -0
  34. package/dist/packem_shared/onConnect-CEtRmUpJ.mjs +1 -0
  35. package/dist/packem_shared/policy-tag-Dprt9JWo.mjs +1 -0
  36. package/dist/packem_shared/protectPublic-BhKewPqm.mjs +1 -0
  37. package/dist/packem_shared/rls-_iVsPvhX.mjs +1 -0
  38. package/dist/packem_shared/run-middleware-BeEEqmdE.mjs +1 -0
  39. package/dist/packem_shared/storageRules-BptPZbi8.mjs +1 -0
  40. package/dist/packem_shared/types.d-C4CMJK8x.d.mts +141 -0
  41. package/dist/packem_shared/types.d-DdYF8E18.d.ts +141 -0
  42. package/dist/rls/testing.d.mts +31 -31
  43. package/dist/rls/testing.d.ts +31 -31
  44. package/dist/rls/testing.mjs +1 -49
  45. package/dist/types.d.mts +1163 -440
  46. package/dist/types.d.ts +1163 -440
  47. package/dist/types.mjs +1 -31
  48. package/package.json +18 -5
  49. package/dist/packem_shared/LunoraEnvError-DjFkpkSP.mjs +0 -187
  50. package/dist/packem_shared/LunoraError-DhggBJZF.mjs +0 -51
  51. package/dist/packem_shared/PRESENCE_DEFAULT_TTL_MS-BgBQsqQ-.mjs +0 -114
  52. package/dist/packem_shared/asBucketStorage-Cnxd9y2q.mjs +0 -11
  53. package/dist/packem_shared/bindOrm-Ce57S3N9.mjs +0 -128
  54. package/dist/packem_shared/composePluginMiddleware-Ck5_TUO8.mjs +0 -100
  55. package/dist/packem_shared/createPolicyDsl-De67zPDS.mjs +0 -29
  56. package/dist/packem_shared/defineAggregateIndex-C2gT1GzM.mjs +0 -252
  57. package/dist/packem_shared/defineMigration-CAJLr6fx.mjs +0 -8
  58. package/dist/packem_shared/defineStorageRule-qu0mpilX.mjs +0 -20
  59. package/dist/packem_shared/httpAction-B7FYUEgr.mjs +0 -340
  60. package/dist/packem_shared/initLunora-CATvPsVt.mjs +0 -86
  61. package/dist/packem_shared/mask-eCUYOwhd.mjs +0 -211
  62. package/dist/packem_shared/onConnect-CIPXKPyw.mjs +0 -13
  63. package/dist/packem_shared/protectPublic-BjFkQ_Or.mjs +0 -15
  64. package/dist/packem_shared/rls-Bi9HiyDC.mjs +0 -567
  65. package/dist/packem_shared/run-middleware-CYQOuoV6.mjs +0 -18
  66. package/dist/packem_shared/storageRules-4a30FSpI.mjs +0 -88
  67. package/dist/packem_shared/types.d-BDY0FYHK.d.ts +0 -135
  68. package/dist/packem_shared/types.d-DmvyEMD6.d.mts +0 -135
package/dist/index.d.ts CHANGED
@@ -1,37 +1,39 @@
1
- import { Validator, Infer, v } from '@lunora/values';
2
- export { type ColumnValidator, type Id, type Infer, ValidationError, type Validator, type ValidatorKind, v } from '@lunora/values';
3
- import { ArgsValidator, InferArgs, RegisteredAction, ActionCtx, MutationCtx, RegisteredMutation, QueryCtx, RegisteredQuery, RegisteredStream, FunctionKind, LifecycleEvent, RegisteredLifecycleHook, TableDefinition, RegisteredFunction, VectorIndexDefinition, Schema, AggregateOp, DurableObjectJurisdiction, RelationDefinition, GlobalBackend, OnDeleteAction, TriggerBuilder, TriggerDefinition, VectorEmbedder, VectorMetric, AggregateIndexDefinition, RankIndexDefinition } from "./types.js";
4
- export { type AnyApi, type AuthState, type DatabaseReader, type DatabaseWriter, type FunctionVisibility, type IndexDefinition, type IndexRangeBuilder, type LifecycleEventKind, type LunoraLogger, type PaginationOptions, type PaginationResult, type RankSortKey, type ReadOnlyStorage, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type SearchFilterBuilder, type SearchIndexDefinition, type ShardMode, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerCtx, type TriggerDatabase, 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 VectorMatch, type VectorMatches, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi } from "./types.js";
1
+ import { Validator, Infer, ValidatorMap, InferValidatorMap, v } from '@lunora/values';
2
+ export { type ColumnValidator, type GeoPoint, type Id, type Infer, ValidationError, type Validator, type ValidatorKind, v } from '@lunora/values';
3
+ import { ArgsValidator, InferArgs, RegisteredAction, ExposeConfig, X402ProcedureConfig, ActionCtx, MutationCtx, RegisteredMutation, QueryCtx, RegisteredQuery, RegisteredStream, FunctionKind, Secrets, LifecycleEvent, RegisteredLifecycleHook, TableDefinition, RegisteredFunction, VectorIndexDefinition, Schema, AggregateOp, DurableObjectJurisdiction, RelationDefinition, GlobalBackend, OnDeleteAction, ExternalSourceDefinition, TriggerBuilder, TriggerDefinition, VectorEmbedder, VectorMetric, AggregateIndexDefinition, RankIndexDefinition } from "./types.js";
4
+ export { type AnyApi, type AuthState, type CachePurge, type DatabaseReader, type DatabaseWriter, type FunctionVisibility, type GeoBoundingBox, type GeoFilterBuilder, type GeoIndexDefinition, type GeoPointInput, type IndexDefinition, type IndexRangeBuilder, type LifecycleEventKind, type LogFields, type LunoraLogMethod, type LunoraLogger, type LunoraMetrics, type LunoraTracer, type LunoraWideEvent, type PaginationOptions, type PaginationResult, type RankSortKey, type ReadOnlyStorage, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type SearchFilterBuilder, type SearchIndexDefinition, type ShardMode, type SpanHandle, type SpanKind, type SpanLink, type SpanOptions, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerCtx, type TriggerDatabase, 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 VectorMatch, type VectorMatches, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi } from "./types.js";
5
+ import { LunoraError as LunoraError$1, LunoraErrorCode } from '@lunora/errors';
6
+ export type { LunoraErrorCode } from '@lunora/errors';
5
7
  import { Context, Hono } from 'hono';
6
- import { b as Permission, R as Role, T as TypedDefinePolicyInput, a as Policy, D as DefinePolicyInput, W as WhereInput, c as RlsOptions } from "./packem_shared/types.d-BDY0FYHK.js";
7
- export type { d as PolicyContext, e as PolicyDecision, f as PolicyDecisionOf, P as PolicyOperation } from "./packem_shared/types.d-BDY0FYHK.js";
8
+ import { b as Permission, R as Role, T as TypedDefinePolicyInput, a as Policy, D as DefinePolicyInput, W as WhereInput, c as RlsOptions } from "./packem_shared/types.d-DdYF8E18.js";
9
+ export type { d as PolicyContext, e as PolicyDecision, f as PolicyDecisionOf, P as PolicyOperation } from "./packem_shared/types.d-DdYF8E18.js";
8
10
  export { type CronJob, type CronJobsBuilder, type CronScheduleKind, type DailySchedule, type IntervalSchedule, type MonthlySchedule, type WeeklySchedule, cronJobs } from '@lunora/scheduler';
9
11
  import "./data-model.js";
10
12
  /**
11
- * Make any `config.storage` result bucket-aware so `ctx.storage.bucket(name)`
12
- * always resolves. A `createBucketStorage(...)` result already carries
13
- * `.bucket` / `.bucketName` and is returned as-is; a single `createStorage(...)`
14
- * (or the no-storage stub) is tagged as the `"default"` bucket, where
15
- * `.bucket(name)` is the identity — single-bucket apps address one binding under
16
- * every name.
17
- *
18
- * This is the runtime counterpart the generated `_generated/shard.ts` imports to
19
- * wrap `ctx.storage`; it lives here (the single source) rather than being stamped
20
- * inline into every generated file, so the bucket-tagging behaviour has one home
21
- * alongside the storage ctx types. The input is genuinely heterogeneous (a thunk
22
- * result cast through `unknown`), so the signature is `unknown → unknown`; the
23
- * generated caller casts the result to its storage type.
24
- */
13
+ * Make any `config.storage` result bucket-aware so `ctx.storage.bucket(name)`
14
+ * always resolves. A `createBucketStorage(...)` result already carries
15
+ * `.bucket` / `.bucketName` and is returned as-is; a single `createStorage(...)`
16
+ * (or the no-storage stub) is tagged as the `"default"` bucket, where
17
+ * `.bucket(name)` is the identity — single-bucket apps address one binding under
18
+ * every name.
19
+ *
20
+ * This is the runtime counterpart the generated `_generated/shard.ts` imports to
21
+ * wrap `ctx.storage`; it lives here (the single source) rather than being stamped
22
+ * inline into every generated file, so the bucket-tagging behaviour has one home
23
+ * alongside the storage ctx types. The input is genuinely heterogeneous (a thunk
24
+ * result cast through `unknown`), so the signature is `unknown → unknown`; the
25
+ * generated caller casts the result to its storage type.
26
+ */
25
27
  declare const asBucketStorage: (raw: unknown) => unknown;
26
28
  /** Builder discriminator. Codegen reads this kind. */
27
29
  type TerminalKind = FunctionKind;
28
30
  /** Initial (empty) accumulated args for a fresh builder. */
29
31
  type EmptyArgs = Record<never, never>;
30
32
  /**
31
- * `next()` advances the middleware chain. Called with no argument it forwards
32
- * the current context unchanged; called with `{ ctx }` it shallow-merges the
33
- * extension, and the result type reflects the widened context.
34
- */
33
+ * `next()` advances the middleware chain. Called with no argument it forwards
34
+ * the current context unchanged; called with `{ ctx }` it shallow-merges the
35
+ * extension, and the result type reflects the widened context.
36
+ */
35
37
  interface MiddlewareNext<ContextIn> {
36
38
  (): Promise<ContextIn>;
37
39
  <Extension extends Record<string, unknown>>(options: {
@@ -39,10 +41,10 @@ interface MiddlewareNext<ContextIn> {
39
41
  }): Promise<ContextIn & Extension>;
40
42
  }
41
43
  /**
42
- * A middleware receives the current context and a `next` continuation. Its
43
- * return type becomes the builder's new context, so `return next({ ctx })`
44
- * propagates the extension into every downstream `.use()` and the handler.
45
- */
44
+ * A middleware receives the current context and a `next` continuation. Its
45
+ * return type becomes the builder's new context, so `return next({ ctx })`
46
+ * propagates the extension into every downstream `.use()` and the handler.
47
+ */
46
48
  type Middleware<ContextIn, ContextOut> = (options: {
47
49
  ctx: ContextIn;
48
50
  next: MiddlewareNext<ContextIn>;
@@ -50,16 +52,23 @@ type Middleware<ContextIn, ContextOut> = (options: {
50
52
  /** Options accepted by `initLunora.dataModel&lt;DM>().create(...)`. Reserved for transformer/error-formatter wiring. */
51
53
  type CreateOptions = Record<never, never>;
52
54
  /**
53
- * `Output` carries the type declared by `.output(validator)`. It defaults to
54
- * the `undefined` sentinel meaning "not declared": in that state the terminal
55
- * stays generic over the handler's own return type. Once `.output()` sets it to
56
- * a concrete type, the terminal requires the handler to return that type and
57
- * the registration is typed to it (the runtime parses the result through the
58
- * validator). `[Output] extends [undefined]` is wrapped in a tuple so a union
59
- * `Output` doesn't distribute and so the test is for the exact sentinel.
60
- */
55
+ * `Output` carries the type declared by `.output(validator)`. It defaults to
56
+ * the `undefined` sentinel meaning "not declared": in that state the terminal
57
+ * stays generic over the handler's own return type. Once `.output()` sets it to
58
+ * a concrete type, the terminal requires the handler to return that type and
59
+ * the registration is typed to it (the runtime parses the result through the
60
+ * validator). `[Output] extends [undefined]` is wrapped in a tuple so a union
61
+ * `Output` doesn't distribute and so the test is for the exact sentinel.
62
+ */
61
63
  interface QueryBuilder<Context, Args extends ArgsValidator, Output = undefined> {
62
64
  readonly __lunoraProcedure: "query";
65
+ /**
66
+ * Publish this query on the opt-in public REST surface (plan 167) — the
67
+ * runtime mints `GET /_lunora/rest/&lt;namespace>/&lt;fn>` (and `POST`), dispatching
68
+ * THROUGH the procedure so `ctx.auth` / RLS / validators are enforced, and the
69
+ * generated OpenAPI describes it. Default-closed: omit to keep it RPC-only.
70
+ */
71
+ expose: (config: ExposeConfig) => QueryBuilder<Context, Args, Output>;
63
72
  input: <A extends ArgsValidator>(validators: A) => QueryBuilder<Context, A & Args, Output>;
64
73
  output: <V extends Validator>(validator: V) => QueryBuilder<Context, Args, Infer<V>>;
65
74
  query: [Output] extends [undefined] ? <R>(handler: (options: {
@@ -70,22 +79,36 @@ interface QueryBuilder<Context, Args extends ArgsValidator, Output = undefined>
70
79
  ctx: Context;
71
80
  }) => Output | Promise<Output>) => RegisteredQuery<Args, Output>;
72
81
  /**
73
- * Terminal: declare this procedure as a streaming query. The handler is an
74
- * async generator (or any function returning an `AsyncIterable&lt;R>`) that
75
- * yields one chunk per server-pushed frame. The third `signal` argument is
76
- * tripped when the client cancels — break out of the loop or check
77
- * `signal.aborted` between yields. `.output()` does not apply: per-chunk
78
- * validation is opt-in via the handler itself.
79
- */
82
+ * Terminal: declare this procedure as a streaming query. The handler is an
83
+ * async generator (or any function returning an `AsyncIterable&lt;R>`) that
84
+ * yields one chunk per server-pushed frame. The third `signal` argument is
85
+ * tripped when the client cancels — break out of the loop or check
86
+ * `signal.aborted` between yields. `.output()` does not apply: per-chunk
87
+ * validation is opt-in via the handler itself.
88
+ */
80
89
  stream: <R>(handler: (options: {
81
90
  args: InferArgs<Args>;
82
91
  ctx: Context;
83
92
  signal: AbortSignal;
84
93
  }) => AsyncGenerator<R, void, void> | AsyncIterable<R>) => RegisteredStream<Args, R>;
85
94
  use: <ContextOut>(middleware: Middleware<Context, ContextOut>) => QueryBuilder<ContextOut, Args, Output>;
95
+ /**
96
+ * Mark this query as paid. The origin worker answers an unpaid client RPC
97
+ * with HTTP 402, verifies + settles the x402 payment, then dispatches. `price`
98
+ * is USD (a number of dollars or a `"0.01"`/`"$0.01"` string); the network,
99
+ * recipient, and facilitator come from the worker-level x402 charge config.
100
+ */
101
+ x402: (config: X402ProcedureConfig) => QueryBuilder<Context, Args, Output>;
86
102
  }
87
103
  interface MutationBuilder<Context, Args extends ArgsValidator, Output = undefined> {
88
104
  readonly __lunoraProcedure: "mutation";
105
+ /**
106
+ * Publish this mutation on the opt-in public REST surface (plan 167) — the
107
+ * runtime mints `POST /_lunora/rest/&lt;namespace>/&lt;fn>`, dispatching THROUGH the
108
+ * procedure so `ctx.auth` / RLS / validators are enforced, and the generated
109
+ * OpenAPI describes it. Default-closed: omit to keep it RPC-only.
110
+ */
111
+ expose: (config: ExposeConfig) => MutationBuilder<Context, Args, Output>;
89
112
  input: <A extends ArgsValidator>(validators: A) => MutationBuilder<Context, A & Args, Output>;
90
113
  mutation: [Output] extends [undefined] ? <R>(handler: (options: {
91
114
  args: InferArgs<Args>;
@@ -96,6 +119,13 @@ interface MutationBuilder<Context, Args extends ArgsValidator, Output = undefine
96
119
  }) => Output | Promise<Output>) => RegisteredMutation<Args, Output>;
97
120
  output: <V extends Validator>(validator: V) => MutationBuilder<Context, Args, Infer<V>>;
98
121
  use: <ContextOut>(middleware: Middleware<Context, ContextOut>) => MutationBuilder<ContextOut, Args, Output>;
122
+ /**
123
+ * Mark this mutation as paid. The origin worker answers an unpaid client RPC
124
+ * with HTTP 402, verifies + settles the x402 payment, then dispatches. `price`
125
+ * is USD (a number of dollars or a `"0.01"`/`"$0.01"` string); the network,
126
+ * recipient, and facilitator come from the worker-level x402 charge config.
127
+ */
128
+ x402: (config: X402ProcedureConfig) => MutationBuilder<Context, Args, Output>;
99
129
  }
100
130
  interface ActionBuilder<Context, Args extends ArgsValidator, Output = undefined> {
101
131
  readonly __lunoraProcedure: "action";
@@ -106,16 +136,30 @@ interface ActionBuilder<Context, Args extends ArgsValidator, Output = undefined>
106
136
  args: InferArgs<Args>;
107
137
  ctx: Context;
108
138
  }) => Output | Promise<Output>) => RegisteredAction<Args, Output>;
139
+ /**
140
+ * Publish this action on the opt-in public REST surface (plan 167) — the
141
+ * runtime mints `POST /_lunora/rest/&lt;namespace>/&lt;fn>`, dispatching THROUGH the
142
+ * procedure so `ctx.auth` / RLS / validators are enforced, and the generated
143
+ * OpenAPI describes it. Default-closed: omit to keep it RPC-only.
144
+ */
145
+ expose: (config: ExposeConfig) => ActionBuilder<Context, Args, Output>;
109
146
  input: <A extends ArgsValidator>(validators: A) => ActionBuilder<Context, A & Args, Output>;
110
147
  output: <V extends Validator>(validator: V) => ActionBuilder<Context, Args, Infer<V>>;
111
148
  use: <ContextOut>(middleware: Middleware<Context, ContextOut>) => ActionBuilder<ContextOut, Args, Output>;
112
- }
113
- /**
114
- * Internal builder variants. Identical to their public counterparts but carry
115
- * the `__lunoraVisibility: "internal"` brand codegen keys off to route the
116
- * registration into the `internal` object (and keep it off `api`). `input`/`use`
117
- * return the internal builder type so the brand survives the whole chain.
118
- */
149
+ /**
150
+ * Mark this action as paid. The origin worker answers an unpaid client RPC
151
+ * with HTTP 402, verifies + settles the x402 payment, then dispatches. `price`
152
+ * is USD (a number of dollars or a `"0.01"`/`"$0.01"` string); the network,
153
+ * recipient, and facilitator come from the worker-level x402 charge config.
154
+ */
155
+ x402: (config: X402ProcedureConfig) => ActionBuilder<Context, Args, Output>;
156
+ }
157
+ /**
158
+ * Internal builder variants. Identical to their public counterparts but carry
159
+ * the `__lunoraVisibility: "internal"` brand codegen keys off to route the
160
+ * registration into the `internal` object (and keep it off `api`). `input`/`use`
161
+ * return the internal builder type so the brand survives the whole chain.
162
+ */
119
163
  interface InternalQueryBuilder<Context, Args extends ArgsValidator, Output = undefined> {
120
164
  readonly __lunoraProcedure: "query";
121
165
  readonly __lunoraVisibility: "internal";
@@ -179,31 +223,38 @@ interface DataModelInit<DataModel> {
179
223
  create: (options?: CreateOptions) => LunoraBuilders;
180
224
  }
181
225
  /**
182
- * Entry point for the procedure builder. `dataModel&lt;DM>()` binds the generated
183
- * `DataModel` (phantom for now), and `.create()` yields the public root builders
184
- * plus their `internal*` counterparts.
185
- */
226
+ * Entry point for the procedure builder. `dataModel&lt;DM>()` binds the generated
227
+ * `DataModel` (phantom for now), and `.create()` yields the public root builders
228
+ * plus their `internal*` counterparts.
229
+ */
186
230
  declare const initLunora: {
187
231
  dataModel: <DataModel>() => DataModelInit<DataModel>;
188
232
  };
189
233
  /**
190
- * Redact secrets from a free-form message. Masks, in order: any quoted value
191
- * whose contents look like a credential (so a value surfaced as `received string
192
- * "sk_live_…"` is masked even though the surrounding text is not a token); a
193
- * `scheme://user:password@host` URL credential (the password segment); any
194
- * known-prefix credential token wherever it appears, at any length; any value
195
- * following a secret-named key in `KEY=value` / `KEY: value` form; and any
196
- * remaining bare high-entropy ≥24-char token run anywhere in the message.
197
- *
198
- * This is BEST-EFFORT defense-in-depth, NOT a guarantee: a short, prefix-less
199
- * secret under a non-secret-named key (and embedded credentials in shapes not
200
- * enumerated here) can still slip through. Treat it as a backstop — prefer
201
- * structured logging that never serializes raw env/secret fields in the first
202
- * place over relying on post-hoc scrubbing of untrusted data.
203
- *
204
- * Exported because it is independently useful — call it before logging anything
205
- * derived from `env`, request bodies, or thrown errors.
206
- */
234
+ * Build the `ctx.secrets` reader from the worker `env`. `get(name)` resolves
235
+ * `env[name].get()` the `secrets_store_secrets[]` binding of that name. An
236
+ * absent or non-Secrets-Store binding throws a directed error pointing at the
237
+ * wrangler config; the lookup is lazy, so an unused secret never resolves.
238
+ */
239
+ declare const createSecrets: (env: Record<string, unknown>) => Secrets;
240
+ /**
241
+ * Redact secrets from a free-form message. Masks, in order: any quoted value
242
+ * whose contents look like a credential (so a value surfaced as `received string
243
+ * "sk_live_…"` is masked even though the surrounding text is not a token); a
244
+ * `scheme://user:password@host` URL credential (the password segment); any
245
+ * known-prefix credential token wherever it appears, at any length; any value
246
+ * following a secret-named key in `KEY=value` / `KEY: value` form; and any
247
+ * remaining bare high-entropy ≥24-char token run anywhere in the message.
248
+ *
249
+ * This is BEST-EFFORT defense-in-depth, NOT a guarantee: a short, prefix-less
250
+ * secret under a non-secret-named key (and embedded credentials in shapes not
251
+ * enumerated here) can still slip through. Treat it as a backstop — prefer
252
+ * structured logging that never serializes raw env/secret fields in the first
253
+ * place over relying on post-hoc scrubbing of untrusted data.
254
+ *
255
+ * Exported because it is independently useful — call it before logging anything
256
+ * derived from `env`, request bodies, or thrown errors.
257
+ */
207
258
  declare const redactSecrets: (message: string) => string;
208
259
  /** One key's validation failure, secrets already redacted out of `message`. */
209
260
  interface EnvKeyFailure {
@@ -213,32 +264,31 @@ interface EnvKeyFailure {
213
264
  message: string;
214
265
  }
215
266
  /**
216
- * Thrown when one or more env keys are missing or fail validation. Carries the
217
- * structured list of `failures` (each with the offending `key`) so callers can
218
- * react programmatically; `message` is the joined, secret-redacted summary.
219
- *
220
- * Named export only (no default) per the repo export convention.
221
- */
222
- declare class LunoraEnvError extends Error {
223
- override readonly name = "LunoraEnvError";
267
+ * Thrown when one or more env keys are missing or fail validation. Carries the
268
+ * structured list of `failures` (each with the offending `key`) so callers can
269
+ * react programmatically; `message` is the joined, secret-redacted summary.
270
+ *
271
+ * Named export only (no default) per the repo export convention.
272
+ */
273
+ declare class LunoraEnvError extends LunoraError$1 {
224
274
  readonly failures: ReadonlyArray<EnvKeyFailure>;
225
275
  constructor(failures: ReadonlyArray<EnvKeyFailure>);
226
276
  }
227
277
  /** A record of `v.*` validators describing the expected env shape. */
228
278
  type EnvShape = Record<string, Validator>;
229
279
  /**
230
- * The typed output of {@link defineEnv}. Optional validators (`v.optional(...)`)
231
- * become optional keys; everything else is required. Mirrors how `InferArgs`
232
- * derives an args object from a validator map.
233
- */
234
- type InferEnv<S extends EnvShape> = { [K in keyof S as undefined extends Infer<S[K]> ? K : never]?: Infer<S[K]> } & { [K in keyof S as undefined extends Infer<S[K]> ? never : K]: Infer<S[K]> };
235
- /**
236
- * The accessor returned by {@link defineEnv}. A typed view over an `env` object
237
- * plus a `.parse(env)` escape hatch that validates every key eagerly.
238
- *
239
- * Call the accessor with the worker's `env` to get the typed, lazily-validated
240
- * proxy: `const config = defineEnv({ … }); const { PORT } = config(env);`.
241
- */
280
+ * The typed output of {@link defineEnv}. Optional validators (`v.optional(...)`)
281
+ * become optional keys; everything else is required. Mirrors how `InferArgs`
282
+ * derives an args object from a validator map.
283
+ */
284
+ type InferEnv<S extends EnvShape> = { [K in keyof S as undefined extends Infer<S[K]> ? K : never]?: Infer<S[K]>; } & { [K in keyof S as undefined extends Infer<S[K]> ? never : K]: Infer<S[K]>; };
285
+ /**
286
+ * The accessor returned by {@link defineEnv}. A typed view over an `env` object
287
+ * plus a `.parse(env)` escape hatch that validates every key eagerly.
288
+ *
289
+ * Call the accessor with the worker's `env` to get the typed, lazily-validated
290
+ * proxy: `const config = defineEnv({ … }); const { PORT } = config(env);`.
291
+ */
242
292
  interface EnvAccessor<S extends EnvShape> {
243
293
  /** Validate every key eagerly and return the typed, plain (non-proxy) object. Use for fail-fast-at-boot. */
244
294
  parse: (env: unknown) => InferEnv<S>;
@@ -246,91 +296,40 @@ interface EnvAccessor<S extends EnvShape> {
246
296
  (env: unknown): InferEnv<S>;
247
297
  }
248
298
  /**
249
- * Define a typed, validated accessor over a Worker's `env`. Pass a record of
250
- * `v.*` validators; receive an accessor that validates lazily per key (cached
251
- * per `env` identity) and infers its output type from the validators.
252
- *
253
- * ```ts
254
- * import { defineEnv, v } from "@lunora/server";
255
- *
256
- * const config = defineEnv({
257
- * STRIPE_KEY: v.string(),
258
- * PORT: v.optional(v.number()),
259
- * });
260
- *
261
- * export default {
262
- * fetch(request, env) {
263
- * const { STRIPE_KEY, PORT } = config(env); // STRIPE_KEY: string, PORT?: number
264
- * // …
265
- * },
266
- * };
267
- * ```
268
- *
269
- * Throws {@link LunoraEnvError} (secrets redacted) when a key is missing or
270
- * invalid — lazily on first access of that key, or eagerly via `config.parse(env)`.
271
- */
299
+ * Define a typed, validated accessor over a Worker's `env`. Pass a record of
300
+ * `v.*` validators; receive an accessor that validates lazily per key (cached
301
+ * per `env` identity) and infers its output type from the validators.
302
+ *
303
+ * ```ts
304
+ * import { defineEnv, v } from "@lunora/server";
305
+ *
306
+ * const config = defineEnv({
307
+ * STRIPE_KEY: v.string(),
308
+ * PORT: v.optional(v.number()),
309
+ * });
310
+ *
311
+ * export default {
312
+ * fetch(request, env) {
313
+ * const { STRIPE_KEY, PORT } = config(env); // STRIPE_KEY: string, PORT?: number
314
+ * // …
315
+ * },
316
+ * };
317
+ * ```
318
+ *
319
+ * Throws {@link LunoraEnvError} (secrets redacted) when a key is missing or
320
+ * invalid — lazily on first access of that key, or eagerly via `config.parse(env)`.
321
+ */
272
322
  declare const defineEnv: <S extends EnvShape>(shape: S) => EnvAccessor<S>;
323
+ declare class LunoraError extends LunoraError$1 {
324
+ constructor(code: LunoraErrorCode, message?: string, data?: unknown);
325
+ }
273
326
  /**
274
- * Canonical error type for Lunora procedures and middleware.
275
- *
276
- * The runtime's structural error mapper keys off `name === "LunoraError"` plus
277
- * the numeric `status`, so throwing one of these from a handler or middleware
278
- * yields the right RPC/HTTP status without any further wiring. `code` carries
279
- * the machine-readable reason for clients.
280
- */
281
- declare const CODE_STATUS: {
282
- readonly BAD_REQUEST: 400;
283
- readonly CONFLICT: 409;
284
- /**
285
- * `count()` invoked against a table whose context carries an active RLS
286
- * policy. The operation itself is unsupported in an RLS-restricted reader
287
- * (kitcn's documented constraint) — the request is well-formed and the
288
- * caller is authorized, so this is a 422 (semantic conflict) rather than a
289
- * 403 (policy denial).
290
- */
291
- readonly COUNT_RLS_UNSUPPORTED: 422;
292
- readonly FORBIDDEN: 403;
293
- readonly INTERNAL_SERVER_ERROR: 500;
294
- /**
295
- * An analytical reduction (`aggregate` / `groupBy`) was invoked over a
296
- * column that the procedure's `mask()` middleware redacts. A masked column
297
- * can't be summed, averaged, or grouped without leaking the very values the
298
- * mask hides (a group key *is* the raw value; an aggregate is computed from
299
- * it), so the operation fails closed. The request is well-formed and the
300
- * caller is authorized — this is a 422 (semantic conflict), mirroring
301
- * `COUNT_RLS_UNSUPPORTED`.
302
- */
303
- readonly MASK_UNSUPPORTED: 422;
304
- readonly NOT_FOUND: 404;
305
- readonly NOT_IMPLEMENTED: 501;
306
- /**
307
- * A write policy's `when` returned a relation-crossing predicate
308
- * (`some`/`none`/`every`/`is`/`isNot`). The in-memory write-policy evaluator
309
- * has no child fetcher and cannot resolve a relation node, so the policy is
310
- * unsupported as written. Relation predicates are valid in *read* policies
311
- * and query `where` clauses (the pre-resolver handles them there). The
312
- * request is well-formed; this is a 422 (semantic conflict), mirroring the
313
- * sibling `*_UNSUPPORTED` codes.
314
- */
315
- readonly RELATION_PREDICATE_UNSUPPORTED: 422;
316
- readonly TOO_MANY_REQUESTS: 429;
317
- readonly UNAUTHORIZED: 401;
318
- readonly UNPROCESSABLE: 422;
319
- };
320
- type LunoraErrorCode = keyof typeof CODE_STATUS;
321
- declare class LunoraError extends Error {
322
- override readonly name = "LunoraError";
323
- readonly code: LunoraErrorCode;
324
- readonly status: number;
325
- constructor(code: LunoraErrorCode, message?: string);
326
- }
327
- /**
328
- * Minimal structural writer the facade binds over. Declared with **method**
329
- * syntax (not arrow properties) so a more-specifically-typed writer — both
330
- * `@lunora/do`'s `DatabaseWriterLike` and the RLS middleware's wrapped writer —
331
- * stays assignable under bivariant parameter checking. That is the whole reason
332
- * the shared helper can serve both callers, hence the rule exemption.
333
- */
327
+ * Minimal structural writer the facade binds over. Declared with **method**
328
+ * syntax (not arrow properties) so a more-specifically-typed writer — both
329
+ * `@lunora/do`'s `DatabaseWriterLike` and the RLS middleware's wrapped writer
330
+ * stays assignable under bivariant parameter checking. That is the whole reason
331
+ * the shared helper can serve both callers, hence the rule exemption.
332
+ */
334
333
  interface FacadeWriterLike {
335
334
  aggregate(tableName: string, options: unknown): Promise<unknown>;
336
335
  count(tableName: string, where?: unknown): Promise<number>;
@@ -342,6 +341,11 @@ interface FacadeWriterLike {
342
341
  }, expectedTable?: string): Promise<{
343
342
  deleted: number;
344
343
  }>;
344
+ deleteWhere?(tableName: string, where: Record<string, unknown>, options?: {
345
+ limit?: number;
346
+ }): Promise<{
347
+ deleted: number;
348
+ }>;
345
349
  findFirst(tableName: string, args?: unknown): Promise<unknown>;
346
350
  findFirstOrThrow(tableName: string, args?: unknown): Promise<unknown>;
347
351
  findMany(tableName: string, args?: unknown): Promise<unknown>;
@@ -350,15 +354,27 @@ interface FacadeWriterLike {
350
354
  insert(tableName: string, document: Record<string, unknown>): Promise<string>;
351
355
  insertMany?(tableName: string, documents: ReadonlyArray<Record<string, unknown>>, options?: {
352
356
  limit?: number;
353
- }): Promise<string[]>;
357
+ skipDuplicates?: boolean;
358
+ }): Promise<(string | null)[]>;
354
359
  patch(id: string, patch: Record<string, unknown>, expectedTable?: string): Promise<void>;
355
360
  patchMany?(patches: ReadonlyArray<{
356
361
  id: string;
357
362
  patch: Record<string, unknown>;
358
363
  }>, options?: {
359
364
  limit?: number;
360
- }, expectedTable?: string): Promise<void>;
365
+ }, expectedTable?: string): Promise<{
366
+ patched: number;
367
+ }>;
368
+ patchWhere?(tableName: string, args: {
369
+ patch: Record<string, unknown>;
370
+ where: Record<string, unknown>;
371
+ }, options?: {
372
+ limit?: number;
373
+ }): Promise<{
374
+ patched: number;
375
+ }>;
361
376
  query(tableName: string): {
377
+ withGeoIndex(indexName: string, build: (q: unknown) => unknown): unknown;
362
378
  withSearchIndex(indexName: string, search: (q: unknown) => unknown): unknown;
363
379
  };
364
380
  rank(tableName: string, indexName: string, options: unknown): Promise<unknown>;
@@ -371,11 +387,19 @@ interface FacadeEntry {
371
387
  aggregate: (options: unknown) => Promise<unknown>;
372
388
  count: (where?: unknown) => Promise<number>;
373
389
  delete: (id: string) => Promise<void>;
374
- deleteMany: (ids: ReadonlyArray<string>, options?: {
375
- limit?: number;
376
- }) => Promise<{
377
- deleted: number;
378
- }>;
390
+ deleteMany: {
391
+ (ids: ReadonlyArray<string>, options?: {
392
+ limit?: number;
393
+ }): Promise<{
394
+ deleted: number;
395
+ }>;
396
+ (args: {
397
+ limit?: number;
398
+ where: Record<string, unknown>;
399
+ }): Promise<{
400
+ deleted: number;
401
+ }>;
402
+ };
379
403
  /** `true` when at least one row matches `where` (or any row exists when omitted). Honors RLS like `findFirst`. */
380
404
  exists: (where?: unknown) => Promise<boolean>;
381
405
  findFirst: (args?: unknown) => Promise<unknown>;
@@ -386,16 +410,34 @@ interface FacadeEntry {
386
410
  /** Physically remove a row (and physically cascade), bypassing `.softDelete()`. */
387
411
  hardDelete: (id: string) => Promise<void>;
388
412
  insert: (document: Record<string, unknown>, options?: FacadeInsertOptions) => Promise<null | string>;
413
+ /**
414
+ * Insert many documents into this table in one call. With
415
+ * `{ skipDuplicates: true }`, UNIQUE breaches resolve to `null` for that row
416
+ * instead of failing the batch. The typed facade narrows the return to
417
+ * `Id&lt;T>[]` when skipDuplicates is not requested.
418
+ */
389
419
  insertMany: (documents: ReadonlyArray<Record<string, unknown>>, options?: {
390
420
  limit?: number;
391
- }) => Promise<string[]>;
421
+ skipDuplicates?: boolean;
422
+ }) => Promise<(string | null)[]>;
392
423
  patch: (id: string, patch: Record<string, unknown>) => Promise<void>;
393
- patchMany: (patches: ReadonlyArray<{
394
- id: string;
395
- values: Record<string, unknown>;
396
- }>, options?: {
397
- limit?: number;
398
- }) => Promise<void>;
424
+ patchMany: {
425
+ (patches: ReadonlyArray<{
426
+ id: string;
427
+ values: Record<string, unknown>;
428
+ }>, options?: {
429
+ limit?: number;
430
+ }): Promise<{
431
+ patched: number;
432
+ }>;
433
+ (args: {
434
+ limit?: number;
435
+ values: Record<string, unknown>;
436
+ where: Record<string, unknown>;
437
+ }): Promise<{
438
+ patched: number;
439
+ }>;
440
+ };
399
441
  rank: (indexName: string, options: unknown) => Promise<unknown>;
400
442
  rankPage: (indexName: string, options?: unknown) => Promise<unknown>;
401
443
  replace: (id: string, document: Record<string, unknown>) => Promise<void>;
@@ -405,15 +447,16 @@ interface FacadeEntry {
405
447
  upsert: (args: UpsertArgs) => Promise<UpsertResult>;
406
448
  /** Sequential `upsert` over many rows sharing one `target`; returns one result per input row in order. */
407
449
  upsertMany: (args: UpsertManyArgs) => Promise<UpsertResult[]>;
450
+ withGeoIndex: (indexName: string, build: (q: unknown) => unknown) => unknown;
408
451
  withSearchIndex: (indexName: string, search: (q: unknown) => unknown) => unknown;
409
452
  }
410
453
  /** Options accepted by the per-table `insert` accessor. */
411
454
  interface FacadeInsertOptions {
412
455
  /**
413
- * When `true`, a UNIQUE-constraint breach is swallowed: the insert becomes a
414
- * silent no-op and resolves to `null` instead of throwing a `CONFLICT`. Any
415
- * other error still propagates. Mirrors better-drizzle's `create({ skipDuplicates })`.
416
- */
456
+ * When `true`, a UNIQUE-constraint breach is swallowed: the insert becomes a
457
+ * silent no-op and resolves to `null` instead of throwing a `CONFLICT`. Any
458
+ * other error still propagates. Mirrors better-drizzle's `create({ skipDuplicates })`.
459
+ */
417
460
  skipDuplicates?: boolean;
418
461
  }
419
462
  /** The conflict target for `upsert`/`upsertMany`: one field name or a tuple of them. */
@@ -441,17 +484,17 @@ interface UpsertManyArgs {
441
484
  target: UpsertTarget;
442
485
  }
443
486
  /**
444
- * Bind a structural writer to one table, producing its `ctx.db` table accessor.
445
- *
446
- * The by-id accessors (`get`/`delete`/`patch`/`replace`) forward the bound
447
- * `tableName` as `expectedTable` so the underlying writer scopes its id lookup
448
- * to this table. Without it, a branded `Id&lt;"posts">` carrying another table's
449
- * id would resolve cross-table (the writer probes every table by id), letting
450
- * `ctx.db.posts.get(foreignId)` read — or `.delete`/`.patch`/`.replace`
451
- * mutate — a row in an unrelated table (IDOR). Writers that ignore the second
452
- * argument keep their previous global behaviour; the scoping is opt-in via this
453
- * forwarded name.
454
- */
487
+ * Bind a structural writer to one table, producing its `ctx.db` table accessor.
488
+ *
489
+ * The by-id accessors (`get`/`delete`/`patch`/`replace`) forward the bound
490
+ * `tableName` as `expectedTable` so the underlying writer scopes its id lookup
491
+ * to this table. Without it, a branded `Id&lt;"posts">` carrying another table's
492
+ * id would resolve cross-table (the writer probes every table by id), letting
493
+ * `ctx.db.posts.get(foreignId)` read — or `.delete`/`.patch`/`.replace`
494
+ * mutate — a row in an unrelated table (IDOR). Writers that ignore the second
495
+ * argument keep their previous global behaviour; the scoping is opt-in via this
496
+ * forwarded name.
497
+ */
455
498
  declare const bindTableFacade: (writer: FacadeWriterLike, tableName: string) => FacadeEntry;
456
499
  /** The kitcn-style `ctx.orm` namespace over a per-table facade map. */
457
500
  interface OrmLike {
@@ -472,21 +515,21 @@ declare const bindOrm: (facade: Record<string, FacadeEntry>) => OrmLike;
472
515
  /** HTTP verbs the typed {@link httpRoute} builder can bind to. */
473
516
  type HttpMethod = "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT";
474
517
  /**
475
- * Context handed to an HTTP action handler. A narrower view of {@link ActionContext}:
476
- * HTTP actions run in the worker (the "action runtime"), separate from the
477
- * transactional store, so there is no direct `db` / `vectors` / `scheduler` /
478
- * `storage` surface — reach the data layer through `runQuery` / `runMutation` /
479
- * `runAction`, which forward to the owning shard.
480
- */
481
- type HttpActionCtx = Pick<ActionCtx, "auth" | "fetch" | "runAction" | "runMutation" | "runQuery">;
518
+ * Context handed to an HTTP action handler. A narrower view of {@link ActionContext}:
519
+ * HTTP actions run in the worker (the "action runtime"), separate from the
520
+ * transactional store, so there is no direct `db` / `vectors` / `scheduler` /
521
+ * `storage` surface — reach the data layer through `runQuery` / `runMutation` /
522
+ * `runAction`, which forward to the owning shard.
523
+ */
524
+ type HttpActionCtx = Pick<ActionCtx, "auth" | "cache" | "fetch" | "runAction" | "runMutation" | "runQuery">;
482
525
  /** A raw handler wrapped by {@link httpAction}. Receives the raw request, returns the raw response. */
483
526
  type HttpActionHandler = (context: HttpActionCtx, request: Request) => Promise<Response> | Response;
484
527
  /**
485
- * The hono {@link https://hono.dev | Hono} environment used by {@link httpRouter}.
486
- * The runtime injects the per-request {@link HttpActionCtx} on the private
487
- * `__lunoraCtx` binding; the router's lifting middleware promotes it to
488
- * `c.var.lunora` so handlers can read it as a typed variable.
489
- */
528
+ * The hono {@link https://hono.dev | Hono} environment used by {@link httpRouter}.
529
+ * The runtime injects the per-request {@link HttpActionCtx} on the private
530
+ * `__lunoraCtx` binding; the router's lifting middleware promotes it to
531
+ * `c.var.lunora` so handlers can read it as a typed variable.
532
+ */
490
533
  interface LunoraHttpEnv {
491
534
  Bindings: Record<string, unknown> & {
492
535
  __lunoraCtx?: HttpActionCtx;
@@ -500,31 +543,31 @@ type LunoraHttpApp = Hono<LunoraHttpEnv>;
500
543
  /** A compiled route handler: a hono handler that resolves to a raw {@link Response}. */
501
544
  type LunoraRouteHandler = (c: Context<LunoraHttpEnv>) => Promise<Response>;
502
545
  /**
503
- * Wrap a `(ctx, request) => Response` handler as a hono handler. The raw escape
504
- * hatch — mount it with `app.all(path, httpAction(fn))`. `ctx` is the
505
- * runtime-injected {@link HttpActionCtx} lifted into `c.var.lunora` by
506
- * {@link httpRouter}; `request` is the underlying `c.req.raw`.
507
- */
546
+ * Wrap a `(ctx, request) => Response` handler as a hono handler. The raw escape
547
+ * hatch — mount it with `app.all(path, httpAction(fn))`. `ctx` is the
548
+ * runtime-injected {@link HttpActionCtx} lifted into `c.var.lunora` by
549
+ * {@link httpRouter}; `request` is the underlying `c.req.raw`.
550
+ */
508
551
  declare const httpAction: (handler: HttpActionHandler) => LunoraRouteHandler;
509
552
  /**
510
- * Create the hono app for HTTP actions. Pre-wired with a middleware that lifts
511
- * the runtime-injected `c.env.__lunoraCtx` into `c.var.lunora`, so both
512
- * {@link httpAction} and the typed {@link httpRoute} builder can read the action
513
- * context. The full hono surface is available — plugins, path params, `.route`:
514
- *
515
- * ```ts
516
- * const app = httpRouter();
517
- * app.use("*", cors());
518
- * app.post("/webhook", httpAction(onWebhook));
519
- * app.get("/users/:id", getUser);
520
- * export default createWorker({ httpRouter: app, ... });
521
- * ```
522
- *
523
- * The lifting middleware throws if the context is absent. `createWorker` injects
524
- * it on every request the router sees, so this only trips when the app is run
525
- * outside the runtime — a misconfiguration we surface loudly rather than let
526
- * `c.var.lunora` be silently `undefined` despite its non-optional type.
527
- */
553
+ * Create the hono app for HTTP actions. Pre-wired with a middleware that lifts
554
+ * the runtime-injected `c.env.__lunoraCtx` into `c.var.lunora`, so both
555
+ * {@link httpAction} and the typed {@link httpRoute} builder can read the action
556
+ * context. The full hono surface is available — plugins, path params, `.route`:
557
+ *
558
+ * ```ts
559
+ * const app = httpRouter();
560
+ * app.use("*", cors());
561
+ * app.post("/webhook", httpAction(onWebhook));
562
+ * app.get("/users/:id", getUser);
563
+ * export default createWorker({ httpRouter: app, ... });
564
+ * ```
565
+ *
566
+ * The lifting middleware throws if the context is absent. `createWorker` injects
567
+ * it on every request the router sees, so this only trips when the app is run
568
+ * outside the runtime — a misconfiguration we surface loudly rather than let
569
+ * `c.var.lunora` be silently `undefined` despite its non-optional type.
570
+ */
528
571
  declare const httpRouter: () => LunoraHttpApp;
529
572
  /** The `{ ctx, searchParams, body, params }` a typed route handler receives. */
530
573
  interface HttpRouteHandlerOptions<SearchParams extends ArgsValidator, Body extends ArgsValidator, Params extends ArgsValidator> {
@@ -534,11 +577,12 @@ interface HttpRouteHandlerOptions<SearchParams extends ArgsValidator, Body exten
534
577
  searchParams: InferArgs<SearchParams>;
535
578
  }
536
579
  /**
537
- * The `{ ctx, searchParams, params, request, signal }` a streaming HTTP
538
- * handler receives. There is no parsed `body` — streams are typically GET, and
539
- * the raw `request` is exposed if a handler needs to read the body itself.
540
- * `signal` is tripped when the client disconnects.
541
- */
580
+ * The `{ ctx, searchParams, params, request, signal }` a streaming HTTP
581
+ * handler receives. There is no parsed `body` — streams are typically GET, and
582
+ * the raw `request` is exposed if a handler needs to read the body itself.
583
+ * `signal` is tripped when the client disconnects.
584
+ * @experimental Part of the HTTP-SSE stream surface; reconnect/POST-body design questions are still open.
585
+ */
542
586
  interface HttpStreamHandlerOptions<SearchParams extends ArgsValidator, Params extends ArgsValidator> {
543
587
  ctx: HttpActionCtx;
544
588
  params: InferArgs<Params>;
@@ -547,35 +591,51 @@ interface HttpStreamHandlerOptions<SearchParams extends ArgsValidator, Params ex
547
591
  signal: AbortSignal;
548
592
  }
549
593
  /**
550
- * A typed REST route under construction. `.searchParams()` / `.body()` /
551
- * `.params()` accumulate validator maps (later calls merge, a colliding key
552
- * wins) that decode the URL query, JSON body, and hono path params into the
553
- * handler's typed `searchParams` / `body` / `params`. Like the procedure
554
- * builder, `.output(validator)` defaults to the `undefined` sentinel — while
555
- * unset the handler is generic over its own return; once set the handler must
556
- * return that type and the result is parsed through the validator before
557
- * serialization. `[Output] extends [undefined]` is tuple-wrapped so a union
558
- * `Output` doesn't distribute and the test is for the exact sentinel.
559
- *
560
- * The terminal `.handler()` yields a {@link LunoraRouteHandler} — mount it
561
- * directly with `app.get(path, route)`.
562
- */
594
+ * A typed REST route under construction. `.searchParams()` / `.body()` /
595
+ * `.params()` accumulate validator maps (later calls merge, a colliding key
596
+ * wins) that decode the URL query, JSON body, and hono path params into the
597
+ * handler's typed `searchParams` / `body` / `params`. Like the procedure
598
+ * builder, `.output(validator)` defaults to the `undefined` sentinel — while
599
+ * unset the handler is generic over its own return; once set the handler must
600
+ * return that type and the result is parsed through the validator before
601
+ * serialization. `[Output] extends [undefined]` is tuple-wrapped so a union
602
+ * `Output` doesn't distribute and the test is for the exact sentinel.
603
+ *
604
+ * The terminal `.handler()` yields a {@link LunoraRouteHandler} — mount it
605
+ * directly with `app.get(path, route)`.
606
+ */
563
607
  interface HttpRouteBuilder<SearchParams extends ArgsValidator, Body extends ArgsValidator, Params extends ArgsValidator, Output = undefined> {
564
608
  body: <B extends ArgsValidator>(validators: B) => HttpRouteBuilder<SearchParams, B & Body, Params, Output>;
609
+ /**
610
+ * Attach a `Cache-Control` header to the response. Only meaningful when
611
+ * Workers Cache is enabled in `wrangler.jsonc` (`"cache": { "enabled": true }`).
612
+ */
613
+ cacheControl: (value: string) => HttpRouteBuilder<SearchParams, Body, Params, Output>;
614
+ /**
615
+ * Attach a `Cache-Tag` header to the response for tag-based purging via
616
+ * `ctx.cache.purge({ tags: [...] })`.
617
+ */
618
+ cacheTag: (value: string) => HttpRouteBuilder<SearchParams, Body, Params, Output>;
565
619
  handler: [Output] extends [undefined] ? <R>(handler: (options: HttpRouteHandlerOptions<SearchParams, Body, Params>) => Promise<R> | R) => LunoraRouteHandler : (handler: (options: HttpRouteHandlerOptions<SearchParams, Body, Params>) => Output | Promise<Output>) => LunoraRouteHandler;
566
620
  output: <V extends Validator>(validator: V) => HttpRouteBuilder<SearchParams, Body, Params, Infer<V>>;
567
621
  params: <P extends ArgsValidator>(validators: P) => HttpRouteBuilder<SearchParams, Body, P & Params, Output>;
568
622
  searchParams: <S extends ArgsValidator>(validators: S) => HttpRouteBuilder<S & SearchParams, Body, Params, Output>;
569
623
  /**
570
- * Terminal: declare this route as a streaming Server-Sent Events endpoint.
571
- * The handler is an async generator (or any function returning an
572
- * `AsyncIterable&lt;R>`) that yields one chunk per SSE `data:` frame; on
573
- * iterator completion the route writes a final `event: complete` frame; on
574
- * throw, an `event: error` frame is written with `{code, message}` before
575
- * the stream closes. The chunks are JSON-encoded; `R` is inferred from the
576
- * handler's yielded type.
577
- */
624
+ * Terminal: declare this route as a streaming Server-Sent Events endpoint.
625
+ * The handler is an async generator (or any function returning an
626
+ * `AsyncIterable&lt;R>`) that yields one chunk per SSE `data:` frame; on
627
+ * iterator completion the route writes a final `event: complete` frame; on
628
+ * throw, an `event: error` frame is written with `{code, message}` before
629
+ * the stream closes. The chunks are JSON-encoded; `R` is inferred from the
630
+ * handler's yielded type.
631
+ * @experimental Reconnect/POST-body/wire-fidelity design questions are still open, so the shape may change.
632
+ */
578
633
  stream: <R>(handler: (options: HttpStreamHandlerOptions<SearchParams, Params>) => AsyncGenerator<R, void, void> | AsyncIterable<R>) => LunoraRouteHandler;
634
+ /**
635
+ * Attach a `Vary` header to the response so Cloudflare stores separate
636
+ * cached variants per distinct value of the listed request headers.
637
+ */
638
+ vary: (value: string) => HttpRouteBuilder<SearchParams, Body, Params, Output>;
579
639
  }
580
640
  /** Opens a fresh {@link HttpRouteBuilder}. The `path` documents intent; hono owns the actual routing at mount. */
581
641
  type HttpRouteFactory = (path: string) => HttpRouteBuilder<EmptyArgs, EmptyArgs, EmptyArgs>;
@@ -590,33 +650,33 @@ interface HttpRoute {
590
650
  put: HttpRouteFactory;
591
651
  }
592
652
  /**
593
- * Typed REST route builder. Compiles down to a {@link LunoraRouteHandler}, so a
594
- * typed route and a hand-written {@link httpAction} are interchangeable when
595
- * mounted on {@link httpRouter}:
596
- *
597
- * ```ts
598
- * export const listTodos = httpRoute
599
- * .get("/api/todos")
600
- * .searchParams({ limit: v.number(), q: v.optional(v.string()) })
601
- * .output(v.array(v.object({ id: v.string(), text: v.string() })))
602
- * .handler(async ({ ctx, searchParams }) => ctx.runQuery(api.todos.list, searchParams));
603
- *
604
- * export const getTodo = httpRoute
605
- * .get("/api/todos/:id")
606
- * .params({ id: v.string() })
607
- * .handler(async ({ ctx, params }) => ctx.runQuery(api.todos.get, params));
608
- *
609
- * const app = httpRouter();
610
- * app.get("/api/todos", listTodos);
611
- * app.get("/api/todos/:id", getTodo);
612
- * ```
613
- */
653
+ * Typed REST route builder. Compiles down to a {@link LunoraRouteHandler}, so a
654
+ * typed route and a hand-written {@link httpAction} are interchangeable when
655
+ * mounted on {@link httpRouter}:
656
+ *
657
+ * ```ts
658
+ * export const listTodos = httpRoute
659
+ * .get("/api/todos")
660
+ * .searchParams({ limit: v.number(), q: v.optional(v.string()) })
661
+ * .output(v.array(v.object({ id: v.string(), text: v.string() })))
662
+ * .handler(async ({ ctx, searchParams }) => ctx.runQuery(api.todos.list, searchParams));
663
+ *
664
+ * export const getTodo = httpRoute
665
+ * .get("/api/todos/:id")
666
+ * .params({ id: v.string() })
667
+ * .handler(async ({ ctx, params }) => ctx.runQuery(api.todos.get, params));
668
+ *
669
+ * const app = httpRouter();
670
+ * app.get("/api/todos", listTodos);
671
+ * app.get("/api/todos/:id", getTodo);
672
+ * ```
673
+ */
614
674
  declare const httpRoute: HttpRoute;
615
675
  /**
616
- * Structural view of an R2 object body, as returned by `@lunora/storage`'s
617
- * `download()`. Re-declared here (not imported) so `@lunora/server` takes no
618
- * runtime dependency on `@lunora/storage`; the real binding satisfies the shape.
619
- */
676
+ * Structural view of an R2 object body, as returned by `@lunora/storage`'s
677
+ * `download()`. Re-declared here (not imported) so `@lunora/server` takes no
678
+ * runtime dependency on `@lunora/storage`; the real binding satisfies the shape.
679
+ */
620
680
  interface StorageObjectBody {
621
681
  /** The object body stream (`null` for a zero-byte object). */
622
682
  body: ReadableStream | null;
@@ -647,21 +707,100 @@ interface ContextWithStorage {
647
707
  storage: StorageDownloader;
648
708
  }
649
709
  /**
650
- * Stream a stored object as an HTTP {@link Response} from an `httpAction`
651
- * handler, with correct `Content-Type`, `ETag`, and `Accept-Ranges: bytes`.
652
- * Honors a single-range `Range` request **206 Partial Content** with
653
- * `Content-Range` + `Content-Length`; otherwise **200**. A missing object is a
654
- * **404**; an out-of-bounds range is a **416** with a `Content-Range` of
655
- * `bytes` star-slash-size.
656
- *
657
- * A range request re-issues the `download()` with the resolved `{ offset, length }`
658
- * window so R2 streams only those bytes back to the Worker — the slice is never
659
- * buffered in the isolate. The first `download()` is used only for the object's
660
- * size + metadata (its body is left unread and cancelled). For very large
661
- * objects a signed URL (`ctx.storage.getSignedUrl`) is still cheaper since the
662
- * client then ranges against R2/CDN directly with no Worker hop.
663
- */
710
+ * True when `value` is safe to use as an HTTP header field-value: no CR, LF, or
711
+ * NUL. Guards against response-header injection / `Headers`-construction throws
712
+ * when reflecting attacker-influenced object metadata (e.g. a stored
713
+ * `Content-Type`). Exported (see the `export {}` at the file end) so an `httpAction`
714
+ * handler can guard a request-derived header value before writing it the fix the
715
+ * `http_action_response_header_injection` advisor lint points to.
716
+ */
717
+ declare const isSafeHeaderValue: (value: string) => boolean;
718
+ /**
719
+ * Stream a stored object as an HTTP {@link Response} from an `httpAction`
720
+ * handler, with correct `Content-Type`, `ETag`, and `Accept-Ranges: bytes`.
721
+ * Honors a single-range `Range` request **206 Partial Content** with
722
+ * `Content-Range` + `Content-Length`; otherwise **200**. A missing object is a
723
+ * **404**; an out-of-bounds range is a **416** with a `Content-Range` of
724
+ * `bytes` star-slash-size.
725
+ *
726
+ * A range request re-issues the `download()` with the resolved `{ offset, length }`
727
+ * window so R2 streams only those bytes back to the Worker — the slice is never
728
+ * buffered in the isolate. The first `download()` is used only for the object's
729
+ * size + metadata (its body is left unread and cancelled). For very large
730
+ * objects a signed URL (`ctx.storage.getSignedUrl`) is still cheaper since the
731
+ * client then ranges against R2/CDN directly with no Worker hop.
732
+ */
664
733
  declare const serveStorageObject: (context: ContextWithStorage, key: string, request: Request) => Promise<Response>;
734
+ /**
735
+ * What the worker does with a resolver's identity when it fails contract
736
+ * validation (a forged / malformed claim set arriving from an untrusted token).
737
+ * `"anonymous"` (default, safe) treats the request as anonymous, so the bad
738
+ * identity never reaches a policy as a valid identity (`ctx.auth.userId`
739
+ * becomes `undefined`). `"reject"` fails the request closed (a `401`) — use
740
+ * when a malformed credential should be a hard error, not a silent downgrade.
741
+ */
742
+ type IdentityRejectMode = "anonymous" | "reject";
743
+ /** Options for {@link defineIdentity}. */
744
+ interface DefineIdentityOptions {
745
+ /**
746
+ * How to handle a resolver identity that violates the contract at the trust
747
+ * boundary. Defaults to `"anonymous"` (a forged claim set is downgraded to
748
+ * anonymous rather than flowing in as an unchecked cast).
749
+ */
750
+ readonly onInvalid?: IdentityRejectMode;
751
+ }
752
+ /** Result of validating a candidate identity against the contract. */
753
+ type IdentityValidation = {
754
+ ok: true;
755
+ } | {
756
+ error: string;
757
+ ok: false;
758
+ };
759
+ /**
760
+ * A declared identity claim contract. Carries the codegen discovery brand, the
761
+ * declared claim validators, the reject policy, and a runtime `validate`. The
762
+ * `TClaims` type parameter is the inferred claim shape (always extending
763
+ * `{ userId: string }`); it is phantom (no runtime field) and exists so
764
+ * `@lunora/codegen` and {@link InferIdentity} can recover the type.
765
+ */
766
+ interface IdentityContract<TClaims extends {
767
+ userId: string;
768
+ } = {
769
+ userId: string;
770
+ }> {
771
+ /**
772
+ * Phantom carrier for the inferred claim type. Never populated at runtime
773
+ * (`undefined`); present only so the type flows to codegen / {@link InferIdentity}.
774
+ */
775
+ readonly __claimType?: TClaims;
776
+ readonly __lunoraIdentity: true;
777
+ /** The declared claim validators (a `@lunora/values` validator map). */
778
+ readonly claims: ValidatorMap;
779
+ /** Reject policy applied at the trust boundary. See {@link IdentityRejectMode}. */
780
+ readonly onInvalid: IdentityRejectMode;
781
+ /**
782
+ * Validate a resolver's returned identity against the declared claims. On
783
+ * success the caller keeps the original identity untouched (so undeclared
784
+ * claims are forwarded verbatim, preserving today's behaviour); on failure
785
+ * the worker applies the `onInvalid` policy.
786
+ */
787
+ validate: (identity: Record<string, unknown>) => IdentityValidation;
788
+ }
789
+ /** Recover the declared claim type from a {@link defineIdentity} contract. */
790
+ type InferIdentity<T> = T extends IdentityContract<infer TClaims> ? TClaims : never;
791
+ /**
792
+ * Declare the identity claim contract. `claims` is a `@lunora/values` validator
793
+ * map whose inferred type must extend `{ userId: string }` — if it does not
794
+ * (e.g. `userId` is missing or not a required string), the argument type
795
+ * collapses to `never` and the call fails to typecheck.
796
+ * @example
797
+ * export const identity = defineIdentity({ userId: v.string(), tenantId: v.optional(v.string()), scopes: v.optional(v.array(v.string())) });
798
+ */
799
+ declare const defineIdentity: <A extends ValidatorMap>(claims: InferValidatorMap<A> extends {
800
+ userId: string;
801
+ } ? A : never, options?: DefineIdentityOptions) => IdentityContract<InferValidatorMap<A> & {
802
+ userId: string;
803
+ }>;
665
804
  /** Handler for a connection-lifecycle hook. */
666
805
  type LifecycleHandler = (context: MutationCtx, event: LifecycleEvent) => Promise<void> | void;
667
806
  /** Register a hook that fires once when a client's WebSocket connects. */
@@ -669,14 +808,14 @@ declare const onConnect: (handler: LifecycleHandler) => RegisteredLifecycleHook;
669
808
  /** Register a hook that fires once when a client's WebSocket disconnects. */
670
809
  declare const onDisconnect: (handler: LifecycleHandler) => RegisteredLifecycleHook;
671
810
  /**
672
- * Context handed to a {@link MaskFn} (and to {@link MaskOptions.bypass}). The
673
- * `auth` shape mirrors RLS's `PolicyContext.auth` one-for-one — same identity
674
- * resolver, same `can(...)` permission check — so an author can branch a mask
675
- * on the caller's role/permission. `row` is the full pre-mask row the column
676
- * belongs to; `column` is the column currently being masked. Both are absent
677
- * when the context is used for the procedure-wide `bypass` check (no specific
678
- * cell is in play yet).
679
- */
811
+ * Context handed to a {@link MaskFn} (and to {@link MaskOptions.bypass}). The
812
+ * `auth` shape mirrors RLS's `PolicyContext.auth` one-for-one — same identity
813
+ * resolver, same `can(...)` permission check — so an author can branch a mask
814
+ * on the caller's role/permission. `row` is the full pre-mask row the column
815
+ * belongs to; `column` is the column currently being masked. Both are absent
816
+ * when the context is used for the procedure-wide `bypass` check (no specific
817
+ * cell is in play yet).
818
+ */
680
819
  interface MaskContext<Context = unknown> {
681
820
  readonly auth: {
682
821
  /** `true` when any of the request's `roles` grants `permission` (see {@link MaskOptions.roles}). Fails closed for unregistered roles. */
@@ -692,50 +831,50 @@ interface MaskContext<Context = unknown> {
692
831
  readonly row?: Record<string, unknown>;
693
832
  }
694
833
  /**
695
- * A custom masking function. Receives the raw cell value and the
696
- * {@link MaskContext}, returns the value to surface. Use it for partial masks
697
- * (`maskMiddle(phone)`), role-aware reveals (`ctx.auth.can(...) ? value : null`),
698
- * or format-preserving tokens. A function that **throws** fails closed — the
699
- * cell is redacted to `null`, never leaked raw.
700
- */
834
+ * A custom masking function. Receives the raw cell value and the
835
+ * {@link MaskContext}, returns the value to surface. Use it for partial masks
836
+ * (`maskMiddle(phone)`), role-aware reveals (`ctx.auth.can(...) ? value : null`),
837
+ * or format-preserving tokens. A function that **throws** fails closed — the
838
+ * cell is redacted to `null`, never leaked raw.
839
+ */
701
840
  type MaskFn<Context = unknown> = (value: unknown, context: MaskContext<Context>) => unknown;
702
841
  /**
703
- * How a column is masked:
704
- *
705
- * - `"redact"` — drop the value to `null`. The simplest, safest strategy, and
706
- * the right choice for any value that must actually be kept secret.
707
- * - `"hash"` — replace with a stable token (unsalted 32-bit FNV-1a hex) so the
708
- * same input always yields the same token (joinable/groupable client-side).
709
- * **This is NOT a confidentiality control.** It is a non-cryptographic,
710
- * unsalted, deterministic, narrow (~2^32) digest: low-entropy values (emails,
711
- * phone numbers, SSNs) are brute-force-recoverable by the very caller you are
712
- * masking from, and identical values always produce identical tokens across
713
- * rows/columns/tenants (enabling correlation). Use `"hash"` ONLY when you want a
714
- * stable pseudonym for grouping/joining and leaking the value is acceptable —
715
- * never to hide sensitive PII. For PII that must stay hidden, use `"redact"`.
716
- * - a {@link MaskFn} — author-defined transform (partial mask, role-aware reveal).
717
- */
842
+ * How a column is masked:
843
+ *
844
+ * - `"redact"` — drop the value to `null`. The simplest, safest strategy, and
845
+ * the right choice for any value that must actually be kept secret.
846
+ * - `"hash"` — replace with a stable token (unsalted 32-bit FNV-1a hex) so the
847
+ * same input always yields the same token (joinable/groupable client-side).
848
+ * **This is NOT a confidentiality control.** It is a non-cryptographic,
849
+ * unsalted, deterministic, narrow (~2^32) digest: low-entropy values (emails,
850
+ * phone numbers, SSNs) are brute-force-recoverable by the very caller you are
851
+ * masking from, and identical values always produce identical tokens across
852
+ * rows/columns/tenants (enabling correlation). Use `"hash"` ONLY when you want a
853
+ * stable pseudonym for grouping/joining and leaking the value is acceptable —
854
+ * never to hide sensitive PII. For PII that must stay hidden, use `"redact"`.
855
+ * - a {@link MaskFn} — author-defined transform (partial mask, role-aware reveal).
856
+ */
718
857
  type MaskStrategy<Context = unknown> = "hash" | "redact" | MaskFn<Context>;
719
858
  /** Per-column strategy map for one table: `{ email: "redact", phone: maskMiddle }`. */
720
859
  type MaskColumns<Context = unknown> = Record<string, MaskStrategy<Context>>;
721
860
  /**
722
- * The mask declaration passed to `mask(...)`: a table → column → strategy map.
723
- * Deliberately a plain object literal so the codegen feeder can statically read
724
- * which columns a procedure masks (powering the `mask_uncovered_pii_column`
725
- * advisor lint), exactly as the RLS feeder reads policy tables.
726
- */
861
+ * The mask declaration passed to `mask(...)`: a table → column → strategy map.
862
+ * Deliberately a plain object literal so the codegen feeder can statically read
863
+ * which columns a procedure masks (powering the `mask_uncovered_pii_column`
864
+ * advisor lint), exactly as the RLS feeder reads policy tables.
865
+ */
727
866
  type MaskPolicies<Context = unknown> = Record<string, MaskColumns<Context>>;
728
867
  /**
729
- * Options for `mask(policies, options)`.
730
- *
731
- * - `roles` registers the role→permission grants that back `ctx.auth.can(...)`
732
- * inside a {@link MaskFn} — identical to `rls(policies, { roles })`. A role
733
- * not listed grants no permissions (fails closed for unknown roles).
734
- * - `bypass` is a procedure-wide escape hatch: when it returns `true` the whole
735
- * mask is skipped (the caller sees raw values). Use it for a privileged
736
- * viewer — `bypass: ({ auth }) => auth.can("pii:view")`. Prefer this over
737
- * branching every column when an entire class of caller should see clear data.
738
- */
868
+ * Options for `mask(policies, options)`.
869
+ *
870
+ * - `roles` registers the role→permission grants that back `ctx.auth.can(...)`
871
+ * inside a {@link MaskFn} — identical to `rls(policies, { roles })`. A role
872
+ * not listed grants no permissions (fails closed for unknown roles).
873
+ * - `bypass` is a procedure-wide escape hatch: when it returns `true` the whole
874
+ * mask is skipped (the caller sees raw values). Use it for a privileged
875
+ * viewer — `bypass: ({ auth }) => auth.can("pii:view")`. Prefer this over
876
+ * branching every column when an entire class of caller should see clear data.
877
+ */
739
878
  interface MaskOptions<Context = unknown> {
740
879
  readonly bypass?: (context: MaskContext<Context>) => boolean;
741
880
  readonly roles?: ReadonlyArray<Role>;
@@ -749,12 +888,14 @@ interface QueryArgs$1 {
749
888
  baseWhere?: unknown;
750
889
  cursor?: null | string;
751
890
  limit?: number;
891
+ orderBy?: ReadonlyArray<Record<string, unknown>>;
752
892
  where?: unknown;
753
893
  with?: Record<string, unknown>;
754
894
  }
755
895
  interface AggregateArgs$1 {
756
896
  field?: string;
757
897
  op: string;
898
+ where?: unknown;
758
899
  }
759
900
  interface GroupByArgs$1 {
760
901
  agg?: {
@@ -762,6 +903,7 @@ interface GroupByArgs$1 {
762
903
  op: string;
763
904
  };
764
905
  by: ReadonlyArray<string>;
906
+ where?: unknown;
765
907
  }
766
908
  interface TableReaderLike$1 {
767
909
  collect: () => Promise<Record<string, unknown>[]>;
@@ -774,15 +916,16 @@ interface TableReaderLike$1 {
774
916
  }) => Promise<QueryPage$1>;
775
917
  take: (limit: number) => Promise<Record<string, unknown>[]>;
776
918
  unique: () => Promise<Record<string, unknown> | null>;
919
+ withGeoIndex: (indexName: string, build: (q: unknown) => unknown) => TableReaderLike$1;
777
920
  withIndex: (indexName: string, range?: (q: unknown) => unknown) => TableReaderLike$1;
778
921
  withSearchIndex: (indexName: string, search: (q: unknown) => unknown) => TableReaderLike$1;
779
922
  }
780
923
  /**
781
- * Structural projection of the runtime ORM writer — the same subset
782
- * `../rls/middleware` mirrors, so the wrapper is interchangeable between
783
- * `@lunora/do`'s and `@lunora/d1`'s `DatabaseWriterLike` without an
784
- * inter-package dependency. `rankBefore` is optional (the D1 twin omits it).
785
- */
924
+ * Structural projection of the runtime ORM writer — the same subset
925
+ * `../rls/middleware` mirrors, so the wrapper is interchangeable between
926
+ * `@lunora/do`'s and `@lunora/d1`'s `DatabaseWriterLike` without an
927
+ * inter-package dependency. `rankBefore` is optional (the D1 twin omits it).
928
+ */
786
929
  interface MaskDatabase {
787
930
  aggregate: (tableName: string, options: AggregateArgs$1) => Promise<null | number>;
788
931
  count: (tableName: string, whereOrArgs?: unknown) => Promise<number>;
@@ -792,6 +935,11 @@ interface MaskDatabase {
792
935
  }) => Promise<{
793
936
  deleted: number;
794
937
  }>;
938
+ deleteWhere?: (tableName: string, where: Record<string, unknown>, options?: {
939
+ limit?: number;
940
+ }) => Promise<{
941
+ deleted: number;
942
+ }>;
795
943
  findFirst: (tableName: string, args?: QueryArgs$1) => Promise<Record<string, unknown> | null>;
796
944
  findFirstOrThrow: (tableName: string, args?: QueryArgs$1) => Promise<Record<string, unknown>>;
797
945
  findMany: (tableName: string, args?: QueryArgs$1) => Promise<QueryPage$1>;
@@ -803,7 +951,8 @@ interface MaskDatabase {
803
951
  insert: (tableName: string, document: Record<string, unknown>) => Promise<string>;
804
952
  insertMany: (tableName: string, documents: ReadonlyArray<Record<string, unknown>>, options?: {
805
953
  limit?: number;
806
- }) => Promise<string[]>;
954
+ skipDuplicates?: boolean;
955
+ }) => Promise<(string | null)[]>;
807
956
  lookupById?: (id: string, expectedTable?: string) => Promise<null | {
808
957
  row: Record<string, unknown>;
809
958
  tableName: string;
@@ -814,7 +963,17 @@ interface MaskDatabase {
814
963
  patch: Record<string, unknown>;
815
964
  }>, options?: {
816
965
  limit?: number;
817
- }) => Promise<void>;
966
+ }) => Promise<{
967
+ patched: number;
968
+ }>;
969
+ patchWhere?: (tableName: string, args: {
970
+ patch: Record<string, unknown>;
971
+ where: Record<string, unknown>;
972
+ }, options?: {
973
+ limit?: number;
974
+ }) => Promise<{
975
+ patched: number;
976
+ }>;
818
977
  query: (tableName: string) => TableReaderLike$1;
819
978
  rank: (tableName: string, indexName: string, options: unknown) => Promise<null | {
820
979
  position: number;
@@ -838,37 +997,22 @@ interface MaskContextIn {
838
997
  db: MaskDatabase;
839
998
  }
840
999
  /**
841
- * Procedure-builder middleware. Apply per-request via `.use(mask(policies))`.
842
- * Closes over the policy map at builder-construction time; resolves identity +
843
- * the `bypass` decision per call against the live ctx.
844
- *
845
- * IMPORTANT: a mask is in scope only for procedures whose builder chain
846
- * includes this middleware — opt-in, never global (the same invariant as RLS).
847
- */
1000
+ * Procedure-builder middleware. Apply per-request via `.use(mask(policies))`.
1001
+ * Closes over the policy map at builder-construction time; resolves identity +
1002
+ * the `bypass` decision per call against the live ctx.
1003
+ *
1004
+ * IMPORTANT: a mask is in scope only for procedures whose builder chain
1005
+ * includes this middleware — opt-in, never global (the same invariant as RLS).
1006
+ */
848
1007
  declare const mask: <Context extends MaskContextIn = MaskContextIn>(policies: MaskPolicies<Context>, options?: MaskOptions<Context>) => Middleware<Context, Context>;
849
- /**
850
- * Online data-migration authoring API.
851
- *
852
- * `defineMigration` declares a per-document backfill over one table: `up`
853
- * transforms every existing row, `down` (optional) reverses it. Unlike the D1
854
- * SQL schema migrations in `@lunora/d1`, these run *inside each shard's*
855
- * Durable Object against live documents, in keyset batches, and are resumable —
856
- * the per-shard runner in `@lunora/do` tracks progress in a reserved
857
- * `__lunora_migrations` table so an interrupted run picks up where it stopped.
858
- *
859
- * The returned object carries a `__lunoraMigration` brand so codegen can
860
- * discover declarations through the type checker (mirroring the procedure
861
- * builder's `__lunoraProcedure` brand) and emit them into a `LUNORA_MIGRATIONS`
862
- * registry the DO and CLI look migrations up by id.
863
- */
864
1008
  /** A document handed to a migration transform: the stored row including `_id`/`_creationTime`. */
865
1009
  type MigrationDocument = Record<string, unknown>;
866
1010
  /**
867
- * Transform applied to one document. Return a new document to rewrite the row,
868
- * or `undefined` to leave it untouched (skipped, not counted as changed). The
869
- * runner always preserves the original `_id` and `_creationTime`, so the
870
- * returned document neither needs to nor should change row identity.
871
- */
1011
+ * Transform applied to one document. Return a new document to rewrite the row,
1012
+ * or `undefined` to leave it untouched (skipped, not counted as changed). The
1013
+ * runner always preserves the original `_id` and `_creationTime`, so the
1014
+ * returned document neither needs to nor should change row identity.
1015
+ */
872
1016
  type MigrationTransform = (document: MigrationDocument) => MigrationDocument | undefined | void;
873
1017
  interface MigrationDefinition {
874
1018
  /** Rows fetched and rewritten per batch. Defaults to the runner's batch size when omitted. */
@@ -889,83 +1033,150 @@ interface RegisteredMigration extends MigrationDefinition {
889
1033
  /** Declare an online data migration. See the module docs for runtime semantics. */
890
1034
  declare const defineMigration: (definition: MigrationDefinition) => RegisteredMigration;
891
1035
  /**
892
- * The prefixed tables a single plugin `P` contributes, or an empty map when it
893
- * ships no schema extension. Mirrors {@link PrefixedTables} at the plugin level
894
- * so {@link InstalledTables} can fold a tuple of plugins.
895
- */
1036
+ * A mutator declaration. `server` is authoritative; `client` is the optimistic
1037
+ * twin (optional omit it to let the optimistic write fall through to the
1038
+ * server round-trip with no local preview). Both receive the same validated
1039
+ * `args`.
1040
+ */
1041
+ interface MutatorDefinition<Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown> {
1042
+ /**
1043
+ * Validator for the mutator's arguments. Validated on the DO before `server`
1044
+ * runs and (when present) on the client before `client` runs, so both impls
1045
+ * see the same parsed shape. Omit for a parameterless mutator.
1046
+ */
1047
+ readonly args?: Args;
1048
+ /**
1049
+ * Optimistic client implementation. Runs in a TanStack DB transaction
1050
+ * against the local collections; its writes are applied immediately and
1051
+ * automatically rolled back / rebased as the authoritative result syncs
1052
+ * back. Pure and side-effect-free beyond the local store. Omit to skip the
1053
+ * local preview.
1054
+ */
1055
+ readonly client?: (tx: ClientTx, args: InferValidatorMap<Args>) => Promise<void> | void;
1056
+ /**
1057
+ * Owner-scope the write: names the column carrying the row owner (e.g.
1058
+ * `owner: "userId"`). Before `server` runs, the mutator requires a verified
1059
+ * identity, rejects a client-supplied owner that disagrees with it, and sets
1060
+ * the column to the verified value — so the impl reads `args[owner]` without
1061
+ * trusting the client and never repeats the check by hand.
1062
+ *
1063
+ * This replaces the "every mutator opens with `assertOwner(ctx, args.userId)`"
1064
+ * pattern, and is the write-side counterpart to an `owner`-scoped
1065
+ * {@link import("./shapes").ShapeDefinition}. Unlike a shape it takes the column
1066
+ * NAME rather than `true`: a shape is bound to one `table`, so the table's
1067
+ * `.ownedBy(field)` resolves unambiguously, whereas one mutator may write
1068
+ * several tables and has no single owning table to read it from.
1069
+ *
1070
+ * Declare the column `v.optional(...)` to leave it off the wire entirely; it is
1071
+ * injected either way.
1072
+ */
1073
+ readonly owner?: string;
1074
+ /**
1075
+ * Authoritative server implementation. Runs inside the shard DO with a full
1076
+ * {@link MutationContext} (`ctx.db` writer); its writes append to `__cdc_log`
1077
+ * and poke back to subscribers. This is the source of truth — the client
1078
+ * impl is only a prediction of it.
1079
+ */
1080
+ readonly server: (context: ServerContext, args: InferValidatorMap<Args>) => Promise<R> | R;
1081
+ }
1082
+ /**
1083
+ * A {@link MutatorDefinition} plus the codegen discovery marker and a
1084
+ * dispatch-shaped `handler` (validates `args`, then runs `server`) so the DO
1085
+ * invokes a mutator exactly like a registered procedure.
1086
+ */
1087
+ interface RegisteredMutator<Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown> extends MutatorDefinition<Args, ServerContext, ClientTx, R> {
1088
+ readonly __lunoraMutator: true;
1089
+ /** Validate `rawArgs`, then run the authoritative `server` impl. Used by the DO push path. */
1090
+ readonly handler: (context: ServerContext, rawArgs: Record<string, unknown>) => Promise<R>;
1091
+ /**
1092
+ * Marks the dispatch kind so codegen can register the mutator in the same
1093
+ * `LUNORA_FUNCTIONS` table queries/mutations use — the DO's `handleRpc`
1094
+ * reads `kind === "mutation"` to wrap the authoritative `server` impl in the
1095
+ * shard's BEGIN/COMMIT span (all-or-nothing writes), exactly like an
1096
+ * ordinary `mutation`.
1097
+ */
1098
+ readonly kind: "mutation";
1099
+ }
1100
+ /** Declare a custom mutator. See the module docs for runtime semantics. */
1101
+ declare const defineMutator: <Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown>(definition: MutatorDefinition<Args, ServerContext, ClientTx, R>) => RegisteredMutator<Args, ServerContext, ClientTx, R>;
1102
+ /**
1103
+ * The prefixed tables a single plugin `P` contributes, or an empty map when it
1104
+ * ships no schema extension. Mirrors {@link PrefixedTables} at the plugin level
1105
+ * so {@link InstalledTables} can fold a tuple of plugins.
1106
+ */
896
1107
  type ExtensionTablesOf<P> = P extends {
897
1108
  readonly extension: SchemaExtension<infer X> & {
898
1109
  readonly key: infer K;
899
1110
  };
900
1111
  } ? K extends string ? PrefixedTables<X, K> : Record<never, never> : Record<never, never>;
901
1112
  /**
902
- * Fold a tuple of plugins onto a base table map `T`, accumulating each plugin's
903
- * auto-prefixed extension tables left-to-right — the type-level mirror of
904
- * {@link installPlugins} applying `mergeSchemaExtension` for each plugin in turn.
905
- */
1113
+ * Fold a tuple of plugins onto a base table map `T`, accumulating each plugin's
1114
+ * auto-prefixed extension tables left-to-right — the type-level mirror of
1115
+ * {@link installPlugins} applying `mergeSchemaExtension` for each plugin in turn.
1116
+ */
906
1117
  type InstalledTables<T extends Record<string, TableDefinition>, Plugins extends ReadonlyArray<unknown>> = Plugins extends readonly [infer Head, ...infer Rest] ? InstalledTables<ExtensionTablesOf<Head> & T, Rest> : T;
907
1118
  /**
908
- * Union every plugin's `ContextOut` in a tuple — the type-level mirror of the
909
- * `ctx.api.&lt;key>` additions {@link composePluginMiddleware} accumulates as each
910
- * plugin middleware runs. Independent of the incoming context, which the builder
911
- * infers at the `.use(...)` site.
912
- */
1119
+ * Union every plugin's `ContextOut` in a tuple — the type-level mirror of the
1120
+ * `ctx.api.&lt;key>` additions {@link composePluginMiddleware} accumulates as each
1121
+ * plugin middleware runs. Independent of the incoming context, which the builder
1122
+ * infers at the `.use(...)` site.
1123
+ */
913
1124
  type ComposedOut<Plugins extends ReadonlyArray<unknown>> = Plugins extends readonly [infer Head, ...infer Rest] ? ComposedOut<Rest> & (Head extends Plugin<any, any, infer Out> ? Out : unknown) : unknown;
914
1125
  /**
915
- * Schema fragment a plugin contributes. Same shape as the `tables` map
916
- * passed to `defineSchema`. Optional `vectorIndexes` mirror the top-level
917
- * `defineSchema` argument so a plugin can ship vector decls alongside its
918
- * tables.
919
- */
1126
+ * Schema fragment a plugin contributes. Same shape as the `tables` map
1127
+ * passed to `defineSchema`. Optional `vectorIndexes` mirror the top-level
1128
+ * `defineSchema` argument so a plugin can ship vector decls alongside its
1129
+ * tables.
1130
+ */
920
1131
  interface SchemaExtension<T extends Record<string, TableDefinition> = Record<string, TableDefinition>> {
921
1132
  /** Stable key identifying the plugin that owns this extension. */
922
1133
  readonly key: string;
923
1134
  /**
924
- * Extension tables, keyed by **bare** name (e.g. `buckets`). At merge time
925
- * each is auto-prefixed with `key` (`ratelimit_buckets`) so it can't
926
- * collide with an app table; do **not** namespace manually.
927
- */
1135
+ * Extension tables, keyed by **bare** name (e.g. `buckets`). At merge time
1136
+ * each is auto-prefixed with `key` (`ratelimit_buckets`) so it can't
1137
+ * collide with an app table; do **not** namespace manually.
1138
+ */
928
1139
  readonly tables: T;
929
1140
  /**
930
- * Optional standalone vector indexes the plugin ships, keyed by index
931
- * name. Merged into the host schema's `vectorIndexes`; a key collision
932
- * with the base schema is a hard error (same policy as tables).
933
- */
1141
+ * Optional standalone vector indexes the plugin ships, keyed by index
1142
+ * name. Merged into the host schema's `vectorIndexes`; a key collision
1143
+ * with the base schema is a hard error (same policy as tables).
1144
+ */
934
1145
  readonly vectorIndexes?: Record<string, VectorIndexDefinition>;
935
1146
  }
936
1147
  /**
937
- * Build a {@link SchemaExtension}. The `key` is a runtime tag (used for
938
- * error messages on collision) and a type-level brand.
939
- */
1148
+ * Build a {@link SchemaExtension}. The `key` is a runtime tag (used for
1149
+ * error messages on collision) and a type-level brand.
1150
+ */
940
1151
  declare const defineSchemaExtension: <T extends Record<string, TableDefinition>>(key: string, options: {
941
1152
  tables: T;
942
1153
  vectorIndexes?: Record<string, VectorIndexDefinition>;
943
1154
  }) => SchemaExtension<T>;
944
1155
  /**
945
- * A plugin packages an optional schema extension and optional middleware.
946
- * Both are independently usable: an app can install only the schema (e.g.
947
- * for plugins that ship background workers but no per-request behavior)
948
- * or only the middleware (plugins that augment ctx without persistent
949
- * state).
950
- */
1156
+ * A plugin packages an optional schema extension and optional middleware.
1157
+ * Both are independently usable: an app can install only the schema (e.g.
1158
+ * for plugins that ship background workers but no per-request behavior)
1159
+ * or only the middleware (plugins that augment ctx without persistent
1160
+ * state).
1161
+ */
951
1162
  interface Plugin<TExtension extends Record<string, TableDefinition> = Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn> {
952
1163
  /**
953
- * Optional schema extension. Apps install via
954
- * `defineSchema(...).extend(plugin.extension)`.
955
- */
1164
+ * Optional schema extension. Apps install via
1165
+ * `defineSchema(...).extend(plugin.extension)`.
1166
+ */
956
1167
  readonly extension?: SchemaExtension<TExtension>;
957
1168
  /** Stable key identifying the plugin. Matches `extension.key` when set. */
958
1169
  readonly key: string;
959
1170
  /**
960
- * Optional middleware. Users attach with `c.query.use(plugin.middleware)`.
961
- * The middleware can extend `ctx`; convention is to attach helpers under
962
- * `ctx.api.&lt;key>`, e.g.
963
- *
964
- * ```ts
965
- * middleware: ({ ctx, next }) =>
966
- * next({ ctx: { api: { ...ctx.api, ratelimit: api } } })
967
- * ```
968
- */
1171
+ * Optional middleware. Users attach with `c.query.use(plugin.middleware)`.
1172
+ * The middleware can extend `ctx`; convention is to attach helpers under
1173
+ * `ctx.api.&lt;key>`, e.g.
1174
+ *
1175
+ * ```ts
1176
+ * middleware: ({ ctx, next }) =>
1177
+ * next({ ctx: { api: { ...ctx.api, ratelimit: api } } })
1178
+ * ```
1179
+ */
969
1180
  readonly middleware?: Middleware<TContextIn, TContextOut>;
970
1181
  }
971
1182
  /** Options to {@link definePlugin}. */
@@ -974,35 +1185,51 @@ interface DefinePluginOptions<TExtension extends Record<string, TableDefinition>
974
1185
  middleware?: Middleware<TContextIn, TContextOut>;
975
1186
  }
976
1187
  /**
977
- * Package a schema extension + middleware as a reusable plugin. Either
978
- * field is optional `definePlugin("foo", {})` is valid but degenerate.
979
- */
980
- declare const definePlugin: <TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut>) => Plugin<TExtension, TContextIn, TContextOut>;
981
- /**
982
- * Bundle of registered functions a {@link Component} ships. Keys are the
983
- * function's local name (e.g. `check`, `reset`); the registered function
984
- * value carries its own kind / args / handler.
985
- *
986
- * Users re-export from their own lunora module so codegen picks them up:
987
- *
988
- * ```ts
989
- * // lunora/ratelimit.ts
990
- * import { ratelimit } from "@vendor/ratelimit-component";
991
- * export const { check, reset } = ratelimit.functions;
992
- * // Emits as `ratelimit:check` / `ratelimit:reset` in the generated `api`.
993
- * ```
994
- *
995
- * Codegen follows the re-export back to the bundled `query/mutation/action`
996
- * call (property access or destructuring both work), so the functions land in
997
- * the generated `api` under the re-exporting file's namespace.
998
- */
1188
+ * Call signatures for {@link definePlugin}. When `extension` is supplied the
1189
+ * returned plugin's `extension` is typed as PRESENT (not `?`), so the
1190
+ * canonical install pattern `defineSchema(...).extend(plugin.extension)`
1191
+ * typechecks without a non-null assertion the shape every scaffold template
1192
+ * ships. The bare-options signature keeps `extension` optional for plugins
1193
+ * that carry only middleware.
1194
+ */
1195
+ interface DefinePluginFunction {
1196
+ <TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut> & {
1197
+ extension: SchemaExtension<TExtension>;
1198
+ }): Plugin<TExtension, TContextIn, TContextOut> & {
1199
+ readonly extension: SchemaExtension<TExtension>;
1200
+ };
1201
+ <TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut>): Plugin<TExtension, TContextIn, TContextOut>;
1202
+ }
1203
+ /**
1204
+ * Package a schema extension + middleware as a reusable plugin. Either
1205
+ * field is optional — `definePlugin("foo", {})` is valid but degenerate.
1206
+ */
1207
+ declare const definePlugin: DefinePluginFunction;
1208
+ /**
1209
+ * Bundle of registered functions a {@link Component} ships. Keys are the
1210
+ * function's local name (e.g. `check`, `reset`); the registered function
1211
+ * value carries its own kind / args / handler.
1212
+ *
1213
+ * Users re-export from their own lunora module so codegen picks them up:
1214
+ *
1215
+ * ```ts
1216
+ * // lunora/ratelimit.ts
1217
+ * import { ratelimit } from "@vendor/ratelimit-component";
1218
+ * export const { check, reset } = ratelimit.functions;
1219
+ * // Emits as `ratelimit:check` / `ratelimit:reset` in the generated `api`.
1220
+ * ```
1221
+ *
1222
+ * Codegen follows the re-export back to the bundled `query/mutation/action`
1223
+ * call (property access or destructuring both work), so the functions land in
1224
+ * the generated `api` under the re-exporting file's namespace.
1225
+ */
999
1226
  type ComponentFunctions = Readonly<Record<string, RegisteredFunction<any, any, FunctionKind>>>;
1000
1227
  /**
1001
- * Component = {@link Plugin} with a bundle of registered functions. The
1002
- * extension + middleware + functions are independent: a component can ship
1003
- * functions without a schema (e.g. a stateless utility), or a schema
1004
- * without functions (e.g. shared table definitions), and any combination.
1005
- */
1228
+ * Component = {@link Plugin} with a bundle of registered functions. The
1229
+ * extension + middleware + functions are independent: a component can ship
1230
+ * functions without a schema (e.g. a stateless utility), or a schema
1231
+ * without functions (e.g. shared table definitions), and any combination.
1232
+ */
1006
1233
  interface Component<TExtension extends Record<string, TableDefinition> = Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn, F extends ComponentFunctions = ComponentFunctions> extends Plugin<TExtension, TContextIn, TContextOut> {
1007
1234
  readonly functions: F;
1008
1235
  }
@@ -1011,81 +1238,81 @@ interface DefineComponentOptions<TExtension extends Record<string, TableDefiniti
1011
1238
  functions?: F;
1012
1239
  }
1013
1240
  /**
1014
- * Convenience wrapper around {@link definePlugin} that also bundles a set
1015
- * of registered functions. The resulting `component.functions` object is a
1016
- * record of `name → registered query/mutation/action`; consumers
1017
- * re-export entries so codegen discovers them as user functions:
1018
- *
1019
- * ```ts
1020
- * export const ratelimit = defineComponent("ratelimit", {
1021
- * // Bare `buckets` merges in as `ratelimit_buckets`.
1022
- * extension: defineSchemaExtension("ratelimit", { tables: { buckets } }),
1023
- * middleware: ({ ctx, next }) => next({ ctx: { ...ctx, ratelimit: api(ctx) } }),
1024
- * functions: {
1025
- * check: query.input({ key: v.string() }).query(async ({ ctx, args }) => ...),
1026
- * reset: mutation.input({ key: v.string() }).mutation(async ({ ctx, args }) => ...),
1027
- * },
1028
- * });
1029
- * ```
1030
- *
1031
- * Re-exporting an entry (by property access or destructuring) is enough for
1032
- * codegen to discover it in the host app's namespace — the discovery resolver
1033
- * chases the re-export back to the bundled registration call.
1034
- */
1241
+ * Convenience wrapper around {@link definePlugin} that also bundles a set
1242
+ * of registered functions. The resulting `component.functions` object is a
1243
+ * record of `name → registered query/mutation/action`; consumers
1244
+ * re-export entries so codegen discovers them as user functions:
1245
+ *
1246
+ * ```ts
1247
+ * export const ratelimit = defineComponent("ratelimit", {
1248
+ * // Bare `buckets` merges in as `ratelimit_buckets`.
1249
+ * extension: defineSchemaExtension("ratelimit", { tables: { buckets } }),
1250
+ * middleware: ({ ctx, next }) => next({ ctx: { ...ctx, ratelimit: api(ctx) } }),
1251
+ * functions: {
1252
+ * check: query.input({ key: v.string() }).query(async ({ ctx, args }) => ...),
1253
+ * reset: mutation.input({ key: v.string() }).mutation(async ({ ctx, args }) => ...),
1254
+ * },
1255
+ * });
1256
+ * ```
1257
+ *
1258
+ * Re-exporting an entry (by property access or destructuring) is enough for
1259
+ * codegen to discover it in the host app's namespace — the discovery resolver
1260
+ * chases the re-export back to the bundled registration call.
1261
+ */
1035
1262
  declare const defineComponent: <TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn, F extends ComponentFunctions = ComponentFunctions>(key: string, options: DefineComponentOptions<TExtension, TContextIn, TContextOut, F>) => Component<TExtension, TContextIn, TContextOut, F>;
1036
1263
  /**
1037
- * Map every key `K` of an extension's table map `X` to its auto-prefixed name
1038
- * `${Key}_${K}`. Mirrors the runtime prefixing in {@link mergeSchemaExtension}
1039
- * so the typed `.extend(...)` chain reflects the real merged table names.
1040
- */
1041
- type PrefixedTables<X extends Record<string, TableDefinition>, Key extends string> = { [K in keyof X as K extends string ? `${Key}_${K}` : K]: X[K] };
1042
- /**
1043
- * Merge a {@link SchemaExtension} into an existing schema. Returns a new
1044
- * schema object — never mutates the input.
1045
- *
1046
- * Extension tables are auto-namespaced: each bare table name is prefixed with
1047
- * the extension `key` (`buckets` → `ratelimit_buckets`), Convex-Components
1048
- * style, and every intra-extension reference (relation targets, aggregate /
1049
- * rank index `on`, standalone vector index `table`) is rewritten to match.
1050
- * References to base/app tables are left untouched.
1051
- *
1052
- * Because each extension lives in its own `key` namespace, app↔component
1053
- * collisions are impossible. The only remaining hard error is two extensions
1054
- * sharing the same `key` and producing the same prefixed table (or vector
1055
- * index) name — silent shadow would let one plugin hijack another's data.
1056
- */
1264
+ * Map every key `K` of an extension's table map `X` to its auto-prefixed name
1265
+ * `${Key}_${K}`. Mirrors the runtime prefixing in {@link mergeSchemaExtension}
1266
+ * so the typed `.extend(...)` chain reflects the real merged table names.
1267
+ */
1268
+ type PrefixedTables<X extends Record<string, TableDefinition>, Key extends string> = { [K in keyof X as K extends string ? `${Key}_${K}` : K]: X[K]; };
1269
+ /**
1270
+ * Merge a {@link SchemaExtension} into an existing schema. Returns a new
1271
+ * schema object — never mutates the input.
1272
+ *
1273
+ * Extension tables are auto-namespaced: each bare table name is prefixed with
1274
+ * the extension `key` (`buckets` → `ratelimit_buckets`), Convex-Components
1275
+ * style, and every intra-extension reference (relation targets, aggregate /
1276
+ * rank index `on`, standalone vector index `table`) is rewritten to match.
1277
+ * References to base/app tables are left untouched.
1278
+ *
1279
+ * Because each extension lives in its own `key` namespace, app↔component
1280
+ * collisions are impossible. The only remaining hard error is two extensions
1281
+ * sharing the same `key` and producing the same prefixed table (or vector
1282
+ * index) name — silent shadow would let one plugin hijack another's data.
1283
+ */
1057
1284
  declare const mergeSchemaExtension: <T extends Record<string, TableDefinition>, X extends Record<string, TableDefinition>, Key extends string = string>(base: Schema<T>, extension: SchemaExtension<X> & {
1058
1285
  readonly key: Key;
1059
1286
  }) => Schema<PrefixedTables<X, Key> & T>;
1060
1287
  /**
1061
- * Install several plugins' schema extensions in one call — the one-shot
1062
- * counterpart to chaining `defineSchema(...).extend(a).extend(b)`. Plugins
1063
- * without an `extension` (middleware-only) are skipped; tables from those that
1064
- * do are auto-prefixed and reference-rewritten exactly as
1065
- * {@link mergeSchemaExtension} does for a single `.extend(...)`.
1066
- *
1067
- * ```ts
1068
- * const schema = installPlugins(defineSchema({ todos }), [ratelimit, audit]);
1069
- * // → todos + ratelimit_* + audit_*
1070
- * ```
1071
- *
1072
- * Pair it with {@link composePluginMiddleware} to attach every plugin's
1073
- * middleware in a single `.use(...)`, so installing N plugins is two calls
1074
- * rather than N `.extend(...)` + N `.use(...)`.
1075
- */
1288
+ * Install several plugins' schema extensions in one call — the one-shot
1289
+ * counterpart to chaining `defineSchema(...).extend(a).extend(b)`. Plugins
1290
+ * without an `extension` (middleware-only) are skipped; tables from those that
1291
+ * do are auto-prefixed and reference-rewritten exactly as
1292
+ * {@link mergeSchemaExtension} does for a single `.extend(...)`.
1293
+ *
1294
+ * ```ts
1295
+ * const schema = installPlugins(defineSchema({ todos }), [ratelimit, audit]);
1296
+ * // → todos + ratelimit_* + audit_*
1297
+ * ```
1298
+ *
1299
+ * Pair it with {@link composePluginMiddleware} to attach every plugin's
1300
+ * middleware in a single `.use(...)`, so installing N plugins is two calls
1301
+ * rather than N `.extend(...)` + N `.use(...)`.
1302
+ */
1076
1303
  declare const installPlugins: <T extends Record<string, TableDefinition>, const Plugins extends ReadonlyArray<Plugin<any, any, any>>>(base: Schema<T>, plugins: Plugins) => Schema<InstalledTables<T, Plugins>>;
1077
1304
  /**
1078
- * Compose every plugin's middleware into a single middleware you attach with one
1079
- * `.use(...)`. Plugins without middleware (schema-only) are skipped; the rest run
1080
- * in array order, each seeing the context the previous one widened, so the final
1081
- * `next({ ctx })` the builder receives carries every plugin's `ctx.api.&lt;key>`
1082
- * additions. Equivalent to `.use(a.middleware).use(b.middleware)…` but as one
1083
- * value, the middleware sibling of {@link installPlugins}.
1084
- *
1085
- * `ContextIn` is left free so the builder infers it from the context at the
1086
- * `.use(...)` site; the result type widens it by the union of the plugins'
1087
- * outputs.
1088
- */
1305
+ * Compose every plugin's middleware into a single middleware you attach with one
1306
+ * `.use(...)`. Plugins without middleware (schema-only) are skipped; the rest run
1307
+ * in array order, each seeing the context the previous one widened, so the final
1308
+ * `next({ ctx })` the builder receives carries every plugin's `ctx.api.&lt;key>`
1309
+ * additions. Equivalent to `.use(a.middleware).use(b.middleware)…` but as one
1310
+ * value, the middleware sibling of {@link installPlugins}.
1311
+ *
1312
+ * `ContextIn` is left free so the builder infers it from the context at the
1313
+ * `.use(...)` site; the result type widens it by the union of the plugins'
1314
+ * outputs.
1315
+ */
1089
1316
  declare const composePluginMiddleware: <ContextIn = unknown, const Plugins extends ReadonlyArray<Plugin<any, any, any>> = ReadonlyArray<Plugin<any, any, any>>>(plugins: Plugins) => Middleware<ContextIn, ComposedOut<Plugins> & ContextIn>;
1090
1317
  /** Options for `.vectorize(field, opts)` (DSL Shape A). */
1091
1318
  interface VectorizeOptions<Shape extends Record<string, Validator> = Record<string, Validator>> {
@@ -1122,10 +1349,10 @@ interface RelationBuilder {
1122
1349
  }) => OneRelation<Target>;
1123
1350
  }
1124
1351
  /**
1125
- * Options for the inline `.aggregateIndex(name, opts)` builder. `op` defaults to
1126
- * `count` so `aggregateIndex("byUser", { by: ["userId"] })` is a single-line
1127
- * `COUNT(*) GROUP BY userId` accelerator.
1128
- */
1352
+ * Options for the inline `.aggregateIndex(name, opts)` builder. `op` defaults to
1353
+ * `count` so `aggregateIndex("byUser", { by: ["userId"] })` is a single-line
1354
+ * `COUNT(*) GROUP BY userId` accelerator.
1355
+ */
1129
1356
  interface InlineAggregateIndexOptions<Shape extends Record<string, Validator> = Record<string, Validator>> {
1130
1357
  /** Group keys; counter rows are one per distinct tuple. Omitted = single-row aggregate over the whole table. */
1131
1358
  by?: ReadonlyArray<keyof Shape & string>;
@@ -1137,11 +1364,11 @@ interface InlineAggregateIndexOptions<Shape extends Record<string, Validator> =
1137
1364
  where?: Record<string, unknown>;
1138
1365
  }
1139
1366
  /**
1140
- * Options for the inline `.rankIndex(name, opts)` builder. `sortBy` is required;
1141
- * accepts either an array of `{ field, direction }` keys, or the shorthand
1142
- * `["field"]` (asc) / `{ field: "desc" }` map entries. `partitionBy` scopes the
1143
- * rank — omitted ⇒ one global rank over the whole table.
1144
- */
1367
+ * Options for the inline `.rankIndex(name, opts)` builder. `sortBy` is required;
1368
+ * accepts either an array of `{ field, direction }` keys, or the shorthand
1369
+ * `["field"]` (asc) / `{ field: "desc" }` map entries. `partitionBy` scopes the
1370
+ * rank — omitted ⇒ one global rank over the whole table.
1371
+ */
1145
1372
  interface InlineRankIndexOptions<Shape extends Record<string, Validator> = Record<string, Validator>> {
1146
1373
  /** Columns that scope each ranking; omitted ⇒ one global rank. */
1147
1374
  partitionBy?: ReadonlyArray<keyof Shape & string>;
@@ -1157,19 +1384,29 @@ interface TableBuilder<Shape extends Record<string, Validator> = Record<string,
1157
1384
  /** Declare an aggregate (counter/sum/…) maintained by triggers for O(1) reads. */
1158
1385
  aggregateIndex: (name: string, options?: InlineAggregateIndexOptions<Shape>) => TableBuilder<Shape>;
1159
1386
  /**
1160
- * Mark this table as written outside Lunora's discoverable insert path —
1161
- * by an adapter, a migration, or framework middleware (e.g. `@lunora/auth`'s
1162
- * better-auth tables, `@lunora/ratelimit`'s store). Advisor insert-path lints
1163
- * (`table_without_insert`) then skip it instead of flagging the absent
1164
- * `ctx.db.insert(...)`.
1165
- */
1387
+ * Mark this table as written outside Lunora's discoverable insert path —
1388
+ * by an adapter, a migration, or framework middleware (e.g. `@lunora/auth`'s
1389
+ * better-auth tables, `@lunora/ratelimit`'s store). Advisor insert-path lints
1390
+ * (`table_without_insert`) then skip it instead of flagging the absent
1391
+ * `ctx.db.insert(...)`.
1392
+ */
1166
1393
  externallyManaged: () => TableBuilder<Shape>;
1167
1394
  /**
1168
- * Mark this table as global (cross-shard). Backed by **D1** by default;
1169
- * pass `{ backend: "hyperdrive" }` to store it in a Postgres/MySQL database
1170
- * via Cloudflare Hyperdrive (PlanetScale, Neon, …) instead. Either way the
1171
- * table stays reactive live queries re-run on write.
1172
- */
1395
+ * Declare a geospatial index over a `v.geoPoint()` column. The runtime keeps
1396
+ * a geohash companion so `withGeoIndex(name, q => q.near(point, radius))` and
1397
+ * `.within(bbox)` resolve as a geohash-prefix range scan + Haversine
1398
+ * refine/sort. `options.precision` tunes the geohash length (default 9).
1399
+ */
1400
+ geoIndex: (name: string, options: {
1401
+ field: keyof Shape & string;
1402
+ precision?: number;
1403
+ }) => TableBuilder<Shape>;
1404
+ /**
1405
+ * Mark this table as global (cross-shard). Backed by **D1** by default;
1406
+ * pass `{ backend: "hyperdrive" }` to store it in a Postgres/MySQL database
1407
+ * via Cloudflare Hyperdrive (PlanetScale, Neon, …) instead. Either way the
1408
+ * table stays reactive — live queries re-run on write.
1409
+ */
1173
1410
  global: (options?: {
1174
1411
  backend?: GlobalBackend;
1175
1412
  }) => TableBuilder<Shape>;
@@ -1178,17 +1415,31 @@ interface TableBuilder<Shape extends Record<string, Validator> = Record<string,
1178
1415
  unique?: boolean;
1179
1416
  }) => TableBuilder<Shape>;
1180
1417
  /**
1181
- * Opt this table OUT of secure-by-default RLS. Under a schema marked
1182
- * `.rls("required")`, every table is protected (the write path denies raw,
1183
- * non-RLS `ctx.db` access); calling `.public()` exempts this one table so a
1184
- * plain `query`/`mutation` may read/write it without an RLS policy. No effect
1185
- * when the schema does not require RLS.
1186
- */
1418
+ * Name the column holding the owning user's id, so "only the owner sees these
1419
+ * rows" is declared once here rather than restated in every shape.
1420
+ *
1421
+ * A `defineShape({ table, owner: true })` over this table derives its predicate
1422
+ * from the field: the subscriber's verified `ctx.auth.userId` must match, and an
1423
+ * anonymous subscriber is denied. Pairs naturally with `.shardBy(field)` on the
1424
+ * same column — the shard key routes the storage, `ownedBy` states who the rows
1425
+ * belong to — but the two are independent and either can be used alone.
1426
+ *
1427
+ * This is a *shape* declaration, not an RLS policy: it narrows what a shape
1428
+ * replicates. Guarding procedure reads/writes is still `rls(...)`'s job.
1429
+ */
1430
+ ownedBy: (field: keyof Shape & string) => TableBuilder<Shape>;
1431
+ /**
1432
+ * Opt this table OUT of secure-by-default RLS. Under a schema marked
1433
+ * `.rls("required")`, every table is protected (the write path denies raw,
1434
+ * non-RLS `ctx.db` access); calling `.public()` exempts this one table so a
1435
+ * plain `query`/`mutation` may read/write it without an RLS policy. No effect
1436
+ * when the schema does not require RLS.
1437
+ */
1187
1438
  public: () => TableBuilder<Shape>;
1188
1439
  /**
1189
- * Declare a rank index (sorted companion table, btree-backed) for
1190
- * `rank(row)` / `rankPage()` reads in O(log n). See {@link RankIndexDefinition}.
1191
- */
1440
+ * Declare a rank index (sorted companion table, btree-backed) for
1441
+ * `rank(row)` / `rankPage()` reads in O(log n). See {@link RankIndexDefinition}.
1442
+ */
1192
1443
  rankIndex: (name: string, options: InlineRankIndexOptions<Shape>) => TableBuilder<Shape>;
1193
1444
  /** Declare relations to other tables, loaded via `findMany({ with })`. */
1194
1445
  relations: (build: (r: RelationBuilder) => Record<string, RelationDefinition>) => TableBuilder<Shape>;
@@ -1200,23 +1451,47 @@ interface TableBuilder<Shape extends Record<string, Validator> = Record<string,
1200
1451
  /** Route storage by the named field — one DO per distinct value. */
1201
1452
  shardBy: (field: keyof Shape & string) => TableBuilder<Shape>;
1202
1453
  /**
1203
- * Turn on soft delete. Adds a nullable timestamp column (`options.field`,
1204
- * default `deletedAt`) and changes `ctx.db.&lt;table>.delete()` to **set** it
1205
- * instead of removing the row; `onDelete: "cascade"` children are recursively
1206
- * soft-deleted too. **List reads** (`findMany`/`findFirst`/`query()`/`count`/
1207
- * `aggregate`/relation loads) then hide soft-deleted rows unless they pass
1208
- * `includeDeleted: true`; by-id `get`/`patch`/`replace` and the new
1209
- * `restore()` still address the row directly. `hardDelete()` physically
1210
- * removes it (cascading as a real delete). Note: `includeDeleted` is a read
1211
- * scope, not access control — anyone who can run the read can set it; a unique
1212
- * index still rejects a new row that collides with a soft-deleted one (the row
1213
- * physically persists).
1214
- */
1454
+ * Turn on soft delete. Adds a nullable timestamp column (`options.field`,
1455
+ * default `deletedAt`) and changes `ctx.db.&lt;table>.delete()` to **set** it
1456
+ * instead of removing the row; `onDelete: "cascade"` children are recursively
1457
+ * soft-deleted too. **List reads** (`findMany`/`findFirst`/`query()`/`count`/
1458
+ * `aggregate`/relation loads) then hide soft-deleted rows unless they pass
1459
+ * `includeDeleted: true`; by-id `get`/`patch`/`replace` and the new
1460
+ * `restore()` still address the row directly. `hardDelete()` physically
1461
+ * removes it (cascading as a real delete). Note: `includeDeleted` is a read
1462
+ * scope, not access control — anyone who can run the read can set it; a unique
1463
+ * index still rejects a new row that collides with a soft-deleted one (the row
1464
+ * physically persists).
1465
+ */
1215
1466
  softDelete: (options?: {
1216
1467
  field?: string;
1217
1468
  }) => TableBuilder<Shape>;
1469
+ /**
1470
+ * Materialize this table from an external Postgres/MySQL behind Cloudflare
1471
+ * Hyperdrive (plan 077). A system-driven poll loop reads the tenant slice
1472
+ * (`query`, with params bound from `tenantBy`) and lands it in the DO's SQLite,
1473
+ * after which `defineShape` carries it to clients unchanged. Implies
1474
+ * `.externallyManaged()` (rows come from the ingest loop, not user mutations).
1475
+ *
1476
+ * Orthogonal to `.shardBy()` — combine them for per-tenant DOs. **Under
1477
+ * `.shardBy()` `tenantBy` is mandatory** (the tenant-isolation boundary); the
1478
+ * `external_source_unscoped` advisor lint fails the build when it is absent, and
1479
+ * `external_source_on_global` rejects combining `.source()` with `.global()`.
1480
+ */
1481
+ source: (definition: ExternalSourceDefinition) => TableBuilder<Shape>;
1218
1482
  /** Declare named lifecycle triggers fired inline within the write path. */
1219
1483
  triggers: (build: (t: TriggerBuilder<Shape>) => Record<string, TriggerDefinition>) => TableBuilder<Shape>;
1484
+ /**
1485
+ * Declare a table-level TTL: a DO alarm-driven sweep auto-deletes rows whose
1486
+ * expiry has passed (or soft-deletes them when the table also
1487
+ * `.softDelete()`s). `field` is an epoch-millisecond column; without
1488
+ * `options.after` its value is the absolute expiry instant, with `after` the
1489
+ * row expires `after` ms past `field` (`field + after`). Coarse, cheap,
1490
+ * table-level — for per-row schedules use `@lunora/scheduler`.
1491
+ */
1492
+ ttl: (field: keyof Shape & string, options?: {
1493
+ after?: number;
1494
+ }) => TableBuilder<Shape>;
1220
1495
  /** Declare a vector index over a single text field on this table. */
1221
1496
  vectorize: (field: keyof Shape & string, options: VectorizeOptions<Shape>) => TableBuilder<Shape>;
1222
1497
  }
@@ -1234,22 +1509,22 @@ interface VectorIndexOptions {
1234
1509
  };
1235
1510
  }
1236
1511
  /**
1237
- * Build a table definition. Returned object is both the table definition (for
1238
- * `defineSchema`) and a fluent builder for indexes + sharding metadata.
1239
- */
1512
+ * Build a table definition. Returned object is both the table definition (for
1513
+ * `defineSchema`) and a fluent builder for indexes + sharding metadata.
1514
+ */
1240
1515
  declare const defineTable: <Shape extends Record<string, Validator>>(inputShape: Shape) => TableBuilder<Shape>;
1241
1516
  /**
1242
- * Declare a standalone vector index (DSL Shape B). Pass the returned value in
1243
- * the `vectorIndexes` map of {@link defineSchema} when the source is derived
1244
- * from multiple fields or a computation rather than a single column.
1245
- */
1517
+ * Declare a standalone vector index (DSL Shape B). Pass the returned value in
1518
+ * the `vectorIndexes` map of {@link defineSchema} when the source is derived
1519
+ * from multiple fields or a computation rather than a single column.
1520
+ */
1246
1521
  declare const defineVectorIndex: (options: VectorIndexOptions) => VectorIndexDefinition;
1247
1522
  /**
1248
- * Options for the standalone `defineAggregateIndex(name, opts)` helper (DSL
1249
- * Shape B). Unlike the inline `.aggregateIndex(...)` builder, this form takes
1250
- * the owning table explicitly via `on` — handy when a single counter wants to
1251
- * live next to the schema map rather than inside a table chain.
1252
- */
1523
+ * Options for the standalone `defineAggregateIndex(name, opts)` helper (DSL
1524
+ * Shape B). Unlike the inline `.aggregateIndex(...)` builder, this form takes
1525
+ * the owning table explicitly via `on` — handy when a single counter wants to
1526
+ * live next to the schema map rather than inside a table chain.
1527
+ */
1253
1528
  interface AggregateIndexOptions {
1254
1529
  by?: ReadonlyArray<string>;
1255
1530
  field?: string;
@@ -1258,17 +1533,17 @@ interface AggregateIndexOptions {
1258
1533
  where?: Record<string, unknown>;
1259
1534
  }
1260
1535
  /**
1261
- * Declare a standalone aggregate index. Pass the returned value to
1262
- * `defineSchema(tables, vectorIndexes, aggregateIndexes)` keyed by index name —
1263
- * the schema attaches it to `tables[on].aggregateIndexes` so runtime consumers
1264
- * (DO + D1) read every index uniformly off the table definition.
1265
- */
1536
+ * Declare a standalone aggregate index. Pass the returned value to
1537
+ * `defineSchema(tables, vectorIndexes, aggregateIndexes)` keyed by index name —
1538
+ * the schema attaches it to `tables[on].aggregateIndexes` so runtime consumers
1539
+ * (DO + D1) read every index uniformly off the table definition.
1540
+ */
1266
1541
  declare const defineAggregateIndex: (name: string, options: AggregateIndexOptions) => AggregateIndexDefinition;
1267
1542
  /**
1268
- * Options for the standalone `defineRankIndex(name, opts)` helper (DSL Shape B).
1269
- * Mirrors the inline `.rankIndex(...)` builder but takes the owning table via
1270
- * `table` so it can sit next to the schema map.
1271
- */
1543
+ * Options for the standalone `defineRankIndex(name, opts)` helper (DSL Shape B).
1544
+ * Mirrors the inline `.rankIndex(...)` builder but takes the owning table via
1545
+ * `table` so it can sit next to the schema map.
1546
+ */
1272
1547
  interface RankIndexOptions {
1273
1548
  partitionBy?: ReadonlyArray<string>;
1274
1549
  sortBy: ReadonlyArray<{
@@ -1279,64 +1554,64 @@ interface RankIndexOptions {
1279
1554
  where?: Record<string, unknown>;
1280
1555
  }
1281
1556
  /**
1282
- * Declare a standalone rank index. Pass the returned value to
1283
- * `defineSchema(tables, vectorIndexes, aggregateIndexes, rankIndexes)` keyed
1284
- * by index name — the schema attaches it to `tables[on].rankIndexes`.
1285
- */
1557
+ * Declare a standalone rank index. Pass the returned value to
1558
+ * `defineSchema(tables, vectorIndexes, aggregateIndexes, rankIndexes)` keyed
1559
+ * by index name — the schema attaches it to `tables[on].rankIndexes`.
1560
+ */
1286
1561
  declare const defineRankIndex: (name: string, options: RankIndexOptions) => RankIndexDefinition;
1287
1562
  /**
1288
- * Build the application schema. The first argument is the table map; the
1289
- * optional second argument registers standalone `defineVectorIndex(...)`
1290
- * declarations (DSL Shape B) keyed by index name. The optional third argument
1291
- * registers standalone `defineAggregateIndex(...)` declarations (DSL Shape B);
1292
- * the optional fourth argument registers standalone `defineRankIndex(...)`
1293
- * declarations. Both are folded into the matching `tables[on].*Indexes` array
1294
- * so runtime backends read every index uniformly off the table definition.
1295
- */
1296
- /**
1297
- * Schema with an in-place `.extend(plugin.extension)` method. Used so apps
1298
- * can compose plugin schemas: `defineSchema({...}).extend(authPlugin.extension)`.
1299
- *
1300
- * `extend` is non-mutating — returns a fresh `ExtendableSchema` containing
1301
- * the merged tables. Extension tables are auto-namespaced by the extension
1302
- * `key` (`buckets` → `ratelimit_buckets`), so the merged type carries the
1303
- * prefixed names via {@link PrefixedTables}. Chains:
1304
- * `defineSchema(...).extend(a).extend(b)` is the typed equivalent of merging
1305
- * `a`'s prefixed tables then `b`'s.
1306
- */
1563
+ * Build the application schema. The first argument is the table map; the
1564
+ * optional second argument registers standalone `defineVectorIndex(...)`
1565
+ * declarations (DSL Shape B) keyed by index name. The optional third argument
1566
+ * registers standalone `defineAggregateIndex(...)` declarations (DSL Shape B);
1567
+ * the optional fourth argument registers standalone `defineRankIndex(...)`
1568
+ * declarations. Both are folded into the matching `tables[on].*Indexes` array
1569
+ * so runtime backends read every index uniformly off the table definition.
1570
+ */
1571
+ /**
1572
+ * Schema with an in-place `.extend(plugin.extension)` method. Used so apps
1573
+ * can compose plugin schemas: `defineSchema({...}).extend(authPlugin.extension)`.
1574
+ *
1575
+ * `extend` is non-mutating — returns a fresh `ExtendableSchema` containing
1576
+ * the merged tables. Extension tables are auto-namespaced by the extension
1577
+ * `key` (`buckets` → `ratelimit_buckets`), so the merged type carries the
1578
+ * prefixed names via {@link PrefixedTables}. Chains:
1579
+ * `defineSchema(...).extend(a).extend(b)` is the typed equivalent of merging
1580
+ * `a`'s prefixed tables then `b`'s.
1581
+ */
1307
1582
  type ExtendableSchema<T extends Record<string, TableDefinition>> = {
1308
1583
  extend: <X extends Record<string, TableDefinition>, Key extends string>(extension: SchemaExtension<X> & {
1309
1584
  readonly key: Key;
1310
1585
  }) => ExtendableSchema<PrefixedTables<X, Key> & T>;
1311
1586
  /**
1312
- * Pin every Durable Object the app reaches — shards, fan-out, subscriptions,
1313
- * the scheduler, and `ctx.containers` — to a Cloudflare data-residency
1314
- * jurisdiction (`"eu"`, `"us"`, `"fedramp"`). Codegen reads this off the
1315
- * schema and emits it into the generated worker's `createWorker({ jurisdiction })`
1316
- * (and `ctx.scheduler` / `ctx.containers`). Non-mutating: returns a fresh
1317
- * `ExtendableSchema`, so it composes with `.rls(...)` / `.extend(...)` in any order.
1318
- *
1319
- * ⚠️ **Set this once, before your first deploy — changing or removing it
1320
- * strands data.** A Durable Object name maps to a *different* ID in each
1321
- * jurisdiction, so toggling this on an existing app makes every shard, scheduler
1322
- * job, and session DO resolve to a NEW, empty DO; the previous data stays in the
1323
- * old jurisdiction's DOs and is no longer reachable. There is no in-place
1324
- * migration — you would have to export from the old jurisdiction and import
1325
- * into the new one.
1326
- *
1327
- * Note: this pins **DO-backed** state only. D1-backed state — `.global()`
1328
- * tables and `@lunora/auth` sessions alike — is governed by D1's own location
1329
- * settings, not this option.
1330
- * @see https://developers.cloudflare.com/durable-objects/reference/data-location/
1331
- */
1587
+ * Pin every Durable Object the app reaches — shards, fan-out, subscriptions,
1588
+ * the scheduler, and `ctx.containers` — to a Cloudflare data-residency
1589
+ * jurisdiction (`"eu"`, `"us"`, `"fedramp"`). Codegen reads this off the
1590
+ * schema and emits it into the generated worker's `createWorker({ jurisdiction })`
1591
+ * (and `ctx.scheduler` / `ctx.containers`). Non-mutating: returns a fresh
1592
+ * `ExtendableSchema`, so it composes with `.rls(...)` / `.extend(...)` in any order.
1593
+ *
1594
+ * ⚠️ **Set this once, before your first deploy — changing or removing it
1595
+ * strands data.** A Durable Object name maps to a *different* ID in each
1596
+ * jurisdiction, so toggling this on an existing app makes every shard, scheduler
1597
+ * job, and session DO resolve to a NEW, empty DO; the previous data stays in the
1598
+ * old jurisdiction's DOs and is no longer reachable. There is no in-place
1599
+ * migration — you would have to export from the old jurisdiction and import
1600
+ * into the new one.
1601
+ *
1602
+ * Note: this pins **DO-backed** state only. D1-backed state — `.global()`
1603
+ * tables and `@lunora/auth` sessions alike — is governed by D1's own location
1604
+ * settings, not this option.
1605
+ * @see https://developers.cloudflare.com/durable-objects/reference/data-location/
1606
+ */
1332
1607
  jurisdiction: (jurisdiction: DurableObjectJurisdiction) => ExtendableSchema<T>;
1333
1608
  /**
1334
- * Turn on secure-by-default RLS for the whole schema. Every table is then
1335
- * protected — the DO/D1 write path denies raw, non-RLS `ctx.db` access, so a
1336
- * procedure that forgets `.use(rls(...))` fails closed. Opt a table out with
1337
- * `.public()`. Non-mutating: returns a fresh `ExtendableSchema` carrying the
1338
- * mode, so `.rls("required")` composes with `.extend(...)` either order.
1339
- */
1609
+ * Turn on secure-by-default RLS for the whole schema. Every table is then
1610
+ * protected — the DO/D1 write path denies raw, non-RLS `ctx.db` access, so a
1611
+ * procedure that forgets `.use(rls(...))` fails closed. Opt a table out with
1612
+ * `.public()`. Non-mutating: returns a fresh `ExtendableSchema` carrying the
1613
+ * mode, so `.rls("required")` composes with `.extend(...)` either order.
1614
+ */
1340
1615
  rls: (mode: "required") => ExtendableSchema<T>;
1341
1616
  } & Schema<T>;
1342
1617
  declare const defineSchema: <T extends Record<string, TableDefinition>>(tables: T, vectorIndexes?: Record<string, VectorIndexDefinition>, aggregateIndexes?: Record<string, AggregateIndexDefinition>, rankIndexes?: Record<string, RankIndexDefinition>) => ExtendableSchema<T>;
@@ -1344,19 +1619,19 @@ declare const defineSchema: <T extends Record<string, TableDefinition>>(tables:
1344
1619
  declare const DEFAULT_TTL_MS = 3e4;
1345
1620
  declare const PRESENCE_BARE_TABLE = "present";
1346
1621
  /**
1347
- * The prefixed table name the extension produces at merge time. The handlers
1348
- * read/write this name directly so they always agree with the merged schema.
1349
- */
1622
+ * The prefixed table name the extension produces at merge time. The handlers
1623
+ * read/write this name directly so they always agree with the merged schema.
1624
+ */
1350
1625
  declare const PRESENCE_TABLE: "presence_present";
1351
1626
  /**
1352
- * A single present member as returned by `listPresent`.
1353
- *
1354
- * Note: the raw client-chosen `sessionId` is deliberately NOT surfaced. It is a
1355
- * connection secret — disclosing every member's `sessionId` would let any
1356
- * subscriber enumerate them and target the heartbeat / disconnect write paths.
1357
- * A "who's here" UI needs only `userId` + awareness `data`; the caller already
1358
- * knows its own session id locally (the `usePresence` hook returns it).
1359
- */
1627
+ * A single present member as returned by `listPresent`.
1628
+ *
1629
+ * Note: the raw client-chosen `sessionId` is deliberately NOT surfaced. It is a
1630
+ * connection secret — disclosing every member's `sessionId` would let any
1631
+ * subscriber enumerate them and target the heartbeat / disconnect write paths.
1632
+ * A "who's here" UI needs only `userId` + awareness `data`; the caller already
1633
+ * knows its own session id locally (the `usePresence` hook returns it).
1634
+ */
1360
1635
  interface PresenceMember {
1361
1636
  /** Opaque awareness blob (selection, cursor, name, color…). */
1362
1637
  data?: Record<string, unknown>;
@@ -1370,41 +1645,41 @@ interface PresenceMember {
1370
1645
  /** Options for {@link definePresence}. */
1371
1646
  interface DefinePresenceOptions {
1372
1647
  /**
1373
- * Grace window (ms) before a gracefully-closed session is dropped from the
1374
- * present list. When `0` (the default), `onDisconnect` hard-deletes the
1375
- * session's row the instant its socket closes. When `> 0`, the row is
1376
- * instead aged so the read-time TTL filter hides it `disconnectGraceMs`
1377
- * from now — a reconnect with the same `sessionId` within the window
1378
- * re-heartbeats and restores full presence with no visible flicker (the
1379
- * AnyCable `presence_ttl` behaviour). Clamped to `ttlMs`.
1380
- */
1648
+ * Grace window (ms) before a gracefully-closed session is dropped from the
1649
+ * present list. When `0` (the default), `onDisconnect` hard-deletes the
1650
+ * session's row the instant its socket closes. When `> 0`, the row is
1651
+ * instead aged so the read-time TTL filter hides it `disconnectGraceMs`
1652
+ * from now — a reconnect with the same `sessionId` within the window
1653
+ * re-heartbeats and restores full presence with no visible flicker (the
1654
+ * AnyCable `presence_ttl` behaviour). Clamped to `ttlMs`.
1655
+ */
1381
1656
  disconnectGraceMs?: number;
1382
1657
  /**
1383
- * How long (ms) a heartbeat keeps a member present. `listPresent` excludes
1384
- * rows whose `lastSeen` is older than `now - ttlMs`. Defaults to 30s.
1385
- */
1658
+ * How long (ms) a heartbeat keeps a member present. `listPresent` excludes
1659
+ * rows whose `lastSeen` is older than `now - ttlMs`. Defaults to 30s.
1660
+ */
1386
1661
  ttlMs?: number;
1387
1662
  }
1388
1663
  /** The registered functions a presence component ships. */
1389
1664
  interface PresenceFunctions {
1390
1665
  /**
1391
- * Connection-lifecycle hook: the instant a client's WebSocket drops, hard-
1392
- * delete its presence row so it disappears from `listPresent` with no TTL
1393
- * lag. Targets the row by the `{ roomId, sessionId }` the client passed as
1394
- * the connection `context`, and only deletes it when the disconnecting
1395
- * VERIFIED identity owns the row (so a forged context can't evict another
1396
- * member). The TTL filter + `sweep` remain the fallback for ungraceful drops
1397
- * where no `context` was recorded.
1398
- */
1666
+ * Connection-lifecycle hook: the instant a client's WebSocket drops, hard-
1667
+ * delete its presence row so it disappears from `listPresent` with no TTL
1668
+ * lag. Targets the row by the `{ roomId, sessionId }` the client passed as
1669
+ * the connection `context`, and only deletes it when the disconnecting
1670
+ * VERIFIED identity owns the row (so a forged context can't evict another
1671
+ * member). The TTL filter + `sweep` remain the fallback for ungraceful drops
1672
+ * where no `context` was recorded.
1673
+ */
1399
1674
  disconnect: RegisteredLifecycleHook;
1400
1675
  /**
1401
- * Upsert the caller's presence row for `roomId` and stamp `lastSeen = now`.
1402
- * Keyed by `(roomId, sessionId)` — re-heartbeats patch the existing row so
1403
- * subscribers receive a single-row delta, not a churn of insert/delete. A
1404
- * heartbeat may only patch a row owned by the same identity (an existing row
1405
- * held by a different `userId` is refused with `FORBIDDEN`), so a client
1406
- * can't overwrite another member's awareness data via a guessed `sessionId`.
1407
- */
1676
+ * Upsert the caller's presence row for `roomId` and stamp `lastSeen = now`.
1677
+ * Keyed by `(roomId, sessionId)` — re-heartbeats patch the existing row so
1678
+ * subscribers receive a single-row delta, not a churn of insert/delete. A
1679
+ * heartbeat may only patch a row owned by the same identity (an existing row
1680
+ * held by a different `userId` is refused with `FORBIDDEN`), so a client
1681
+ * can't overwrite another member's awareness data via a guessed `sessionId`.
1682
+ */
1408
1683
  heartbeat: RegisteredMutation<{
1409
1684
  data: ReturnType<typeof v.optional>;
1410
1685
  roomId: ReturnType<typeof v.string>;
@@ -1413,17 +1688,17 @@ interface PresenceFunctions {
1413
1688
  lastSeen: number;
1414
1689
  }>;
1415
1690
  /**
1416
- * Live query returning the non-expired members of `roomId`, newest heartbeat
1417
- * first. Subscribe to it for a reactive "who's here" list.
1418
- */
1691
+ * Live query returning the non-expired members of `roomId`, newest heartbeat
1692
+ * first. Subscribe to it for a reactive "who's here" list.
1693
+ */
1419
1694
  listPresent: RegisteredQuery<{
1420
1695
  roomId: ReturnType<typeof v.string>;
1421
1696
  }, PresenceMember[]>;
1422
1697
  /**
1423
- * Internal mutation that hard-deletes every expired row for `roomId`. Stale
1424
- * rows already vanish from `listPresent` via the read-time TTL filter; this
1425
- * only reclaims storage. Schedule it (cron / `runAfter`) if you care.
1426
- */
1698
+ * Internal mutation that hard-deletes every expired row for `roomId`. Stale
1699
+ * rows already vanish from `listPresent` via the read-time TTL filter; this
1700
+ * only reclaims storage. Schedule it (cron / `runAfter`) if you care.
1701
+ */
1427
1702
  sweep: RegisteredMutation<{
1428
1703
  roomId: ReturnType<typeof v.string>;
1429
1704
  }, {
@@ -1437,114 +1712,114 @@ type PresenceComponent = Component<{
1437
1712
  functions: PresenceFunctions;
1438
1713
  };
1439
1714
  /**
1440
- * The presence schema extension: a single `present` table, auto-namespaced to
1441
- * `presence_present` at merge time, indexed by `(roomId, sessionId)` for the
1442
- * heartbeat upsert and by `roomId` for `listPresent`.
1443
- */
1715
+ * The presence schema extension: a single `present` table, auto-namespaced to
1716
+ * `presence_present` at merge time, indexed by `(roomId, sessionId)` for the
1717
+ * heartbeat upsert and by `roomId` for `listPresent`.
1718
+ */
1444
1719
  declare const presenceExtension: SchemaExtension<{
1445
1720
  [PRESENCE_BARE_TABLE]: ReturnType<typeof defineTable>;
1446
1721
  }>;
1447
1722
  declare const definePresence: (options?: DefinePresenceOptions) => PresenceComponent;
1448
1723
  /**
1449
- * The middlewares `protectPublic` chains, in the order they run. Every field is
1450
- * optional, so a bundle can be just a rate limit, just a captcha, or any mix —
1451
- * pass the already-constructed middlewares (e.g. `rateLimit(limiter, "signup")`
1452
- * from `@lunora/ratelimit`, `verifyTurnstileMiddleware({...})` from
1453
- * `@lunora/auth`). They are accepted as values rather than imported here so
1454
- * `@lunora/server` keeps no dependency on those packages (which depend on it).
1455
- */
1724
+ * The middlewares `protectPublic` chains, in the order they run. Every field is
1725
+ * optional, so a bundle can be just a rate limit, just a captcha, or any mix —
1726
+ * pass the already-constructed middlewares (e.g. `rateLimit(limiter, "signup")`
1727
+ * from `@lunora/ratelimit`, `verifyTurnstileMiddleware({...})` from
1728
+ * `@lunora/auth`). They are accepted as values rather than imported here so
1729
+ * `@lunora/server` keeps no dependency on those packages (which depend on it).
1730
+ */
1456
1731
  interface ProtectPublicOptions<Context> {
1457
1732
  /**
1458
- * A CAPTCHA / bot check, run after the rate limit. Placed second on purpose:
1459
- * an obvious flood is cheaper to reject with the in-memory limiter than with
1460
- * a Turnstile siteverify round-trip.
1461
- */
1733
+ * A CAPTCHA / bot check, run after the rate limit. Placed second on purpose:
1734
+ * an obvious flood is cheaper to reject with the in-memory limiter than with
1735
+ * a Turnstile siteverify round-trip.
1736
+ */
1462
1737
  captcha?: Middleware<Context, Context>;
1463
1738
  /**
1464
- * A rate limit, run first. Cheapest gate, so it sheds obvious abuse before
1465
- * any network-bound check below it runs.
1466
- */
1739
+ * A rate limit, run first. Cheapest gate, so it sheds obvious abuse before
1740
+ * any network-bound check below it runs.
1741
+ */
1467
1742
  rateLimit?: Middleware<Context, Context>;
1468
1743
  /** Extra middlewares appended after `rateLimit` and `captcha`, in order. */
1469
1744
  use?: ReadonlyArray<Middleware<Context, Context>>;
1470
1745
  }
1471
1746
  /**
1472
- * Compose the recommended public-procedure protections into a single
1473
- * `.use()`-able middleware. It is thin sugar over middleware composition — no
1474
- * new enforcement engine — chaining (in order) a rate limit, a CAPTCHA check,
1475
- * and any extra middlewares so a public mutation that creates users, sends
1476
- * mail, or consumes credits is guarded in one attachment:
1477
- *
1478
- * ```ts
1479
- * export const signUp = mutation
1480
- * .use(protectPublic({
1481
- * rateLimit: rateLimit(limiter, "signup"),
1482
- * captcha: verifyTurnstileMiddleware({ secret: env.TURNSTILE_SECRET_KEY, token: (c) => c.args.captchaToken }),
1483
- * }))
1484
- * .handler(async (ctx, args) => { ... });
1485
- * ```
1486
- *
1487
- * The bundle is context-preserving — each inner middleware leaves the context
1488
- * unchanged — so it slots into any `.use()` chain without reshaping the
1489
- * procedure context. Omitted fields are skipped; an empty bundle is a
1490
- * transparent pass-through.
1491
- */
1747
+ * Compose the recommended public-procedure protections into a single
1748
+ * `.use()`-able middleware. It is thin sugar over middleware composition — no
1749
+ * new enforcement engine — chaining (in order) a rate limit, a CAPTCHA check,
1750
+ * and any extra middlewares so a public mutation that creates users, sends
1751
+ * mail, or consumes credits is guarded in one attachment:
1752
+ *
1753
+ * ```ts
1754
+ * export const signUp = mutation
1755
+ * .use(protectPublic({
1756
+ * rateLimit: rateLimit(limiter, "signup"),
1757
+ * captcha: verifyTurnstileMiddleware({ secret: env.TURNSTILE_SECRET_KEY, token: (c) => c.args.captchaToken }),
1758
+ * }))
1759
+ * .handler(async (ctx, args) => { ... });
1760
+ * ```
1761
+ *
1762
+ * The bundle is context-preserving — each inner middleware leaves the context
1763
+ * unchanged — so it slots into any `.use()` chain without reshaping the
1764
+ * procedure context. Omitted fields are skipped; an empty bundle is a
1765
+ * transparent pass-through.
1766
+ */
1492
1767
  declare const protectPublic: <Context>(options: ProtectPublicOptions<Context>) => Middleware<Context, Context>;
1493
1768
  declare const definePolicy: <Context = unknown>(input: DefinePolicyInput<Context>) => Policy<Context>;
1494
1769
  /**
1495
- * Build a project-bound, relation-aware `definePolicy` typed against the
1496
- * generated `DataModel` (`DM`) + `Relations` (`REL`) maps. Codegen emits a
1497
- * `createPolicyDsl&lt;DataModel, Relations>()` binding into `_generated/server.ts`,
1498
- * so importing `definePolicy` from the generated module constrains `table` to a
1499
- * real table name and type-checks the `when` predicate — including Prisma-style
1500
- * relation predicates (`is`/`some`/…) the `@lunora/do` pre-resolver now resolves
1501
- * on reads. The runtime is byte-for-byte the untyped {@link definePolicy}; only
1502
- * the compile-time surface narrows, so a policy authored either way is
1503
- * discovered identically by the `rls()` chain.
1504
- */
1505
- declare const createPolicyDsl: <DM, REL extends Record<keyof DM, object>>() => <T extends keyof DM, Context = unknown>(input: TypedDefinePolicyInput<DM, REL, T, Context>) => Policy<Context>;
1506
- /**
1507
- * Declare a named permission a policy can check with `ctx.auth.can(...)`. Grant
1508
- * it to a role through `defineRole`'s `permissions`, register those roles with
1509
- * the middleware via `rls(policies, { roles })`, then check it in a policy with
1510
- * `when: ({ auth }) => auth.can(permission)`. See the `./index` JSDoc for a
1511
- * worked example.
1512
- */
1770
+ * Build a project-bound, relation-aware `definePolicy` typed against the
1771
+ * generated `DataModel` (`DM`) + `Relations` (`REL`) maps. Codegen emits a
1772
+ * `createPolicyDsl&lt;DataModel, Relations>()` binding into `_generated/server.ts`,
1773
+ * so importing `definePolicy` from the generated module constrains `table` to a
1774
+ * real table name and type-checks the `when` predicate — including Prisma-style
1775
+ * relation predicates (`is`/`some`/…) the `@lunora/do` pre-resolver now resolves
1776
+ * on reads. The runtime is byte-for-byte the untyped {@link definePolicy}; only
1777
+ * the compile-time surface narrows, so a policy authored either way is
1778
+ * discovered identically by the `rls()` chain.
1779
+ */
1780
+ declare const createPolicyDsl: <DM, REL extends Record<keyof DM, object>, Identity = Record<string, unknown>>() => <T extends keyof DM, Context = unknown>(input: TypedDefinePolicyInput<DM, REL, T, Context, Identity>) => Policy<Context>;
1781
+ /**
1782
+ * Declare a named permission a policy can check with `ctx.auth.can(...)`. Grant
1783
+ * it to a role through `defineRole`'s `permissions`, register those roles with
1784
+ * the middleware via `rls(policies, { roles })`, then check it in a policy with
1785
+ * `when: ({ auth }) => auth.can(permission)`. See the `./index` JSDoc for a
1786
+ * worked example.
1787
+ */
1513
1788
  declare const definePermission: (name: string, options?: Omit<Permission, "name">) => Permission;
1514
1789
  /**
1515
- * Collect a list of policies into the structure the `rls()` middleware
1516
- * consumes. Multiple read policies on the same table OR together (any one
1517
- * matching reveals the row); multiple write policies for the same `(table,
1518
- * op)` AND together (every one must allow). The middleware keeps them in order
1519
- * and decides — see `./middleware`.
1520
- *
1521
- * Validates against an **accidentally duplicated policy** — the same
1522
- * `(table, on)` registered with the *same* decision function (a copy-paste, or
1523
- * the same policy object spread in twice). Because multiple DISTINCT policies
1524
- * per `(table, on)` are intentional, the check keys on the `when` reference too:
1525
- * only a reference-identical `when` for the same `(table, on)` is a real
1526
- * duplicate. Throws at module load so the misconfiguration surfaces immediately
1527
- * rather than as a silently double-evaluated predicate at request time.
1528
- */
1790
+ * Collect a list of policies into the structure the `rls()` middleware
1791
+ * consumes. Multiple read policies on the same table OR together (any one
1792
+ * matching reveals the row); multiple write policies for the same `(table,
1793
+ * op)` AND together (every one must allow). The middleware keeps them in order
1794
+ * and decides — see `./middleware`.
1795
+ *
1796
+ * Validates against an **accidentally duplicated policy** — the same
1797
+ * `(table, on)` registered with the *same* decision function (a copy-paste, or
1798
+ * the same policy object spread in twice). Because multiple DISTINCT policies
1799
+ * per `(table, on)` are intentional, the check keys on the `when` reference too:
1800
+ * only a reference-identical `when` for the same `(table, on)` is a real
1801
+ * duplicate. Throws at module load so the misconfiguration surfaces immediately
1802
+ * rather than as a silently double-evaluated predicate at request time.
1803
+ */
1529
1804
  declare const definePolicies: <Context = unknown>(policies: ReadonlyArray<Policy<Context>>) => ReadonlyArray<Policy<Context>>;
1530
1805
  declare const defineRole: (name: string, options?: Omit<Role, "name">) => Role;
1531
1806
  /**
1532
- * Structural mirror of `@lunora/do`'s `QueryArgs` and `CountArgs`. The
1533
- * runtime ORM in `@lunora/do`/`@lunora/d1` reads `baseWhere` /
1534
- * `restrictsCounts` straight off these option objects, so as long as the
1535
- * fields here stay name-compatible the wrapper is portable across the two
1536
- * dialects without an inter-package dependency.
1537
- */
1807
+ * Structural mirror of `@lunora/do`'s `QueryArgs` and `CountArgs`. The
1808
+ * runtime ORM in `@lunora/do`/`@lunora/d1` reads `baseWhere` /
1809
+ * `restrictsCounts` straight off these option objects, so as long as the
1810
+ * fields here stay name-compatible the wrapper is portable across the two
1811
+ * dialects without an inter-package dependency.
1812
+ */
1538
1813
  interface QueryArgs {
1539
1814
  baseWhere?: WhereInput;
1540
1815
  cursor?: null | string;
1541
1816
  limit?: number;
1542
1817
  orderBy?: ReadonlyArray<unknown>;
1543
1818
  /**
1544
- * Per-target-table read filter the RLS wrapper attaches so a `with` relation
1545
- * is policy-filtered on its own hop (see `@lunora/do`'s `QueryArgs`). Mirrors
1546
- * the top-level read: `(table) => readBase(table).baseWhere`.
1547
- */
1819
+ * Per-target-table read filter the RLS wrapper attaches so a `with` relation
1820
+ * is policy-filtered on its own hop (see `@lunora/do`'s `QueryArgs`). Mirrors
1821
+ * the top-level read: `(table) => readBase(table).baseWhere`.
1822
+ */
1548
1823
  relationBaseWhere?: (table: string) => undefined | WhereInput;
1549
1824
  restrictsCounts?: boolean;
1550
1825
  where?: WhereInput;
@@ -1613,41 +1888,54 @@ interface TableReaderLike {
1613
1888
  numItems: number;
1614
1889
  }) => Promise<QueryPage>;
1615
1890
  take: (limit: number) => Promise<Record<string, unknown>[]>;
1891
+ withGeoIndex: (indexName: string, build: (q: unknown) => unknown) => TableReaderLike;
1616
1892
  withIndex: (indexName: string, range?: (q: unknown) => unknown) => TableReaderLike;
1617
1893
  withSearchIndex: (indexName: string, search: (q: unknown) => unknown) => TableReaderLike;
1618
1894
  }
1619
1895
  /**
1620
- * Structural projection of the runtime ORM writer. The wrapper relies only
1621
- * on these fields, so it's interchangeable between `@lunora/do`'s
1622
- * `DatabaseWriterLike` and `@lunora/d1`'s `DatabaseWriterLike`.
1623
- */
1896
+ * Structural projection of the runtime ORM writer. The wrapper relies only
1897
+ * on these fields, so it's interchangeable between `@lunora/do`'s
1898
+ * `DatabaseWriterLike` and `@lunora/d1`'s `DatabaseWriterLike`.
1899
+ */
1624
1900
  interface DatabaseWriterLike {
1625
1901
  /**
1626
- * Reduce matching rows to a scalar. The RLS wrapper AND-merges the read
1627
- * `baseWhere` into `options` so the reduction only sees policy-visible rows
1628
- * (safe: an aggregate scoped to `where` never reveals a hidden row — see
1629
- * `@lunora/do`'s `RestrictableQueryOptions`). Required: the only writer ever
1630
- * wrapped is `@lunora/do`'s `createShardCtxDb`, which always implements it.
1631
- */
1902
+ * Reduce matching rows to a scalar. The RLS wrapper AND-merges the read
1903
+ * `baseWhere` into `options` so the reduction only sees policy-visible rows
1904
+ * (safe: an aggregate scoped to `where` never reveals a hidden row — see
1905
+ * `@lunora/do`'s `RestrictableQueryOptions`). Required: the only writer ever
1906
+ * wrapped is `@lunora/do`'s `createShardCtxDb`, which always implements it.
1907
+ */
1632
1908
  aggregate: (tableName: string, options: AggregateArgs) => Promise<null | number>;
1633
1909
  count: (tableName: string, whereOrArgs?: CountArgs | WhereInput) => Promise<number>;
1634
1910
  delete: (id: string, expectedTable?: string, options?: {
1635
1911
  hard?: boolean;
1636
1912
  }) => Promise<void>;
1913
+ /** Uncapped, chunked erase of a whole table. The RLS wrapper gates each row like a single delete. */
1914
+ deleteAll?: (tableName: string, options?: {
1915
+ chunkSize?: number;
1916
+ hard?: boolean;
1917
+ }) => Promise<{
1918
+ deleted: number;
1919
+ }>;
1637
1920
  deleteMany: (ids: ReadonlyArray<string>, options?: {
1638
1921
  limit?: number;
1639
1922
  }, expectedTable?: string) => Promise<{
1640
1923
  deleted: number;
1641
1924
  }>;
1925
+ deleteWhere?: (tableName: string, where: WhereInput, options?: {
1926
+ limit?: number;
1927
+ }) => Promise<{
1928
+ deleted: number;
1929
+ }>;
1642
1930
  findFirst: (tableName: string, args?: QueryArgs) => Promise<Record<string, unknown> | null>;
1643
1931
  findFirstOrThrow: (tableName: string, args?: QueryArgs) => Promise<Record<string, unknown>>;
1644
1932
  findMany: (tableName: string, args?: QueryArgs) => Promise<QueryPage>;
1645
1933
  get: (id: string, expectedTable?: string) => Promise<Record<string, unknown> | null>;
1646
1934
  /**
1647
- * Group + reduce. Same `baseWhere` injection as `aggregate`: the per-group
1648
- * reduction is scoped to policy-visible rows, so a group count tallies only
1649
- * rows the caller may read. Required for the same reason as `aggregate`.
1650
- */
1935
+ * Group + reduce. Same `baseWhere` injection as `aggregate`: the per-group
1936
+ * reduction is scoped to policy-visible rows, so a group count tallies only
1937
+ * rows the caller may read. Required for the same reason as `aggregate`.
1938
+ */
1651
1939
  groupBy: (tableName: string, options: GroupByArgs) => Promise<ReadonlyArray<{
1652
1940
  key: Record<string, unknown>;
1653
1941
  value: null | number;
@@ -1655,19 +1943,20 @@ interface DatabaseWriterLike {
1655
1943
  insert: (tableName: string, document: Record<string, unknown>) => Promise<string>;
1656
1944
  insertMany: (tableName: string, documents: ReadonlyArray<Record<string, unknown>>, options?: {
1657
1945
  limit?: number;
1658
- }) => Promise<string[]>;
1946
+ skipDuplicates?: boolean;
1947
+ }) => Promise<(string | null)[]>;
1659
1948
  insertManyUnsafe: (tableName: string, documents: ReadonlyArray<Record<string, unknown>>, options?: {
1660
1949
  allowExplicitId?: boolean;
1661
1950
  limit?: number;
1662
1951
  }) => Promise<string[]>;
1663
1952
  /**
1664
- * Optional table-aware lookup. The underlying writer (e.g. `@lunora/do`)
1665
- * already knows the owning table of an id internally, so it can return
1666
- * `{ row, tableName }` in a single round-trip. When present, the RLS wrapper
1667
- * uses it to collapse the per-call membership-probe fan-out (1 `get` + N
1668
- * `findFirst` across every policy table) down to one lookup. Writers that
1669
- * don't implement it fall back to the probe path.
1670
- */
1953
+ * Optional table-aware lookup. The underlying writer (e.g. `@lunora/do`)
1954
+ * already knows the owning table of an id internally, so it can return
1955
+ * `{ row, tableName }` in a single round-trip. When present, the RLS wrapper
1956
+ * uses it to collapse the per-call membership-probe fan-out (1 `get` + N
1957
+ * `findFirst` across every policy table) down to one lookup. Writers that
1958
+ * don't implement it fall back to the probe path.
1959
+ */
1671
1960
  lookupById?: (id: string, expectedTable?: string) => Promise<null | {
1672
1961
  row: Record<string, unknown>;
1673
1962
  tableName: string;
@@ -1678,14 +1967,24 @@ interface DatabaseWriterLike {
1678
1967
  patch: Record<string, unknown>;
1679
1968
  }>, options?: {
1680
1969
  limit?: number;
1681
- }, expectedTable?: string) => Promise<void>;
1970
+ }, expectedTable?: string) => Promise<{
1971
+ patched: number;
1972
+ }>;
1973
+ patchWhere?: (tableName: string, args: {
1974
+ patch: Record<string, unknown>;
1975
+ where: WhereInput;
1976
+ }, options?: {
1977
+ limit?: number;
1978
+ }) => Promise<{
1979
+ patched: number;
1980
+ }>;
1682
1981
  query: (tableName: string) => TableReaderLike;
1683
1982
  /**
1684
- * Rank a row within its partition. A position is a count-of-rows-before, so
1685
- * — exactly like `count()` — it can't be trusted in an RLS-restricted
1686
- * reader: the wrapper fails it closed with `COUNT_RLS_UNSUPPORTED`. Required
1687
- * for the same reason as `aggregate`.
1688
- */
1983
+ * Rank a row within its partition. A position is a count-of-rows-before, so
1984
+ * — exactly like `count()` — it can't be trusted in an RLS-restricted
1985
+ * reader: the wrapper fails it closed with `COUNT_RLS_UNSUPPORTED`. Required
1986
+ * for the same reason as `aggregate`.
1987
+ */
1689
1988
  rank: (tableName: string, indexName: string, options: RankArgs) => Promise<null | {
1690
1989
  position: number;
1691
1990
  total: number;
@@ -1696,24 +1995,36 @@ interface DatabaseWriterLike {
1696
1995
  total: number;
1697
1996
  }>;
1698
1997
  /**
1699
- * Sorted pagination over a rank companion. The companion stores only the
1700
- * partition + sort keys + id, so an arbitrary read `baseWhere` can't be
1701
- * enforced against it (and re-filtering the fetched rows would break page
1702
- * sizing). RLS therefore fails it closed rather than leak hidden rows.
1703
- * Required for the same reason as `aggregate`.
1704
- */
1998
+ * Sorted pagination over a rank companion. The companion stores only the
1999
+ * partition + sort keys + id, so an arbitrary read `baseWhere` can't be
2000
+ * enforced against it (and re-filtering the fetched rows would break page
2001
+ * sizing). RLS therefore fails it closed rather than leak hidden rows.
2002
+ * Required for the same reason as `aggregate`.
2003
+ */
1705
2004
  rankPage: (tableName: string, indexName: string, options?: RankPageArgs) => Promise<QueryPage>;
1706
2005
  replace: (id: string, document: Record<string, unknown>, expectedTable?: string) => Promise<void>;
1707
2006
  restore?: (id: string, expectedTable?: string) => Promise<void>;
2007
+ /**
2008
+ * Whole-shard erase. The RLS wrapper deliberately **fails this closed** rather
2009
+ * than wrapping it — see the wrapper's `wipeShard`.
2010
+ */
2011
+ wipeShard?: (options?: {
2012
+ chunkSize?: number;
2013
+ exclude?: ReadonlyArray<string>;
2014
+ tables?: ReadonlyArray<string>;
2015
+ }) => Promise<{
2016
+ deleted: number;
2017
+ tables: Record<string, number>;
2018
+ }>;
1708
2019
  }
1709
2020
  /**
1710
- * What a procedure's `ctx.db` must structurally satisfy for the middleware
1711
- * to wrap it. We deliberately mirror `@lunora/do`'s `DatabaseWriterLike`
1712
- * rather than `@lunora/server`'s nominal `DatabaseWriter`/`DatabaseReader`:
1713
- * the runtime adapter that flows in is the `DatabaseWriterLike`-shaped one,
1714
- * and structural matching keeps this module free of an `@lunora/do`-typed
1715
- * `ctx`.
1716
- */
2021
+ * What a procedure's `ctx.db` must structurally satisfy for the middleware
2022
+ * to wrap it. We deliberately mirror `@lunora/do`'s `DatabaseWriterLike`
2023
+ * rather than `@lunora/server`'s nominal `DatabaseWriter`/`DatabaseReader`:
2024
+ * the runtime adapter that flows in is the `DatabaseWriterLike`-shaped one,
2025
+ * and structural matching keeps this module free of an `@lunora/do`-typed
2026
+ * `ctx`.
2027
+ */
1717
2028
  type RlsDatabase = DatabaseWriterLike;
1718
2029
  /** Roles list source on the context. Tolerant of older auth states. */
1719
2030
  type AuthLike = {
@@ -1728,21 +2039,183 @@ interface RlsContextIn {
1728
2039
  }
1729
2040
  declare const rls: <Context extends RlsContextIn = RlsContextIn>(policies: ReadonlyArray<Policy<Context>>, options?: RlsOptions) => Middleware<Context, Context>;
1730
2041
  /**
1731
- * Operations a storage rule can gate. `read` covers `download` / `getMetadata`
1732
- * / `getSignedUrl` / `getUrl`; `write` covers `store` / `generateUploadUrl`;
1733
- * `delete` is `delete`; `list` is a prefix listing (governed via the file
1734
- * browser / admin path, not `ctx.storage` which has no `list`).
1735
- */
2042
+ * A predicate matching **no** rows the deny decision.
2043
+ *
2044
+ * `{ OR: [] }` is a disjunction over zero branches, which the where-compiler
2045
+ * folds to a constant false. Use it (never a bare `{}`) for the unauthorized
2046
+ * branch of a shape or policy predicate:
2047
+ *
2048
+ * ```ts
2049
+ * where: (ctx, { userId }) => (ctx.auth.userId === userId ? { userId } : deny())
2050
+ * ```
2051
+ *
2052
+ * A fresh object per call, so a caller can safely spread or extend the result.
2053
+ */
2054
+ declare const deny: () => WhereInput;
2055
+ /**
2056
+ * A predicate matching **every** row — no restriction beyond whatever it is
2057
+ * AND-composed with. The honest spelling of "this branch adds no filter", where a
2058
+ * bare `{}` reads like an oversight.
2059
+ */
2060
+ declare const allowAll: () => WhereInput;
2061
+ /**
2062
+ * `true` when `where` is the vacuously-false deny predicate — i.e. an `OR` over
2063
+ * zero branches, carrying no other constraint. Used by the runtime to short-circuit
2064
+ * a denied read instead of compiling and running SQL that provably matches nothing.
2065
+ */
2066
+ declare const isDeny: (where: WhereInput) => boolean;
2067
+ /**
2068
+ * Normalize a boolean-or-predicate decision to a {@link WhereInput}.
2069
+ *
2070
+ * The authoring surfaces accept `true`/`false` as sugar — they read far better
2071
+ * than the sentinel objects in an ownership check — and every consumer downstream
2072
+ * wants a plain predicate. `undefined` means "no opinion" and maps to
2073
+ * {@link allowAll}, matching the RLS `PolicyDecision` contract.
2074
+ */
2075
+ declare const toWhereInput: (decision: WhereInput | boolean | undefined) => WhereInput;
2076
+ /**
2077
+ * One `rls()` tag's read policies for a table, paired with the role→permission
2078
+ * grants of that SAME middleware. Keeping the role map per-group is what lets a
2079
+ * policy's `auth.can(...)` resolve against its own middleware's roles — never a
2080
+ * permission registered on a different `rls()` step.
2081
+ */
2082
+ interface ScopedReadPolicies {
2083
+ readonly policies: ReadonlyArray<Policy>;
2084
+ readonly rolePermissions: ReadonlyMap<string, ReadonlySet<string>>;
2085
+ }
2086
+ /** Table-indexed read-policy groups, each scoped to the roles of the rls() middleware that declared it. */
2087
+ interface RlsReadRegistry {
2088
+ readonly byTable: ReadonlyMap<string, ReadonlyArray<ScopedReadPolicies>>;
2089
+ }
2090
+ /** The trusted, server-resolved facts a shape's RLS evaluation runs under. */
2091
+ interface ShapeReadWhereRequest {
2092
+ /** The shape ctx (the procedure context a policy `when` reads as `ctx`). */
2093
+ readonly ctx: unknown;
2094
+ /** Resolved identity claims (the socket's verified identity), or `null` when anonymous. */
2095
+ readonly identity: Record<string, unknown> | null;
2096
+ /** `true` when the schema is `.rls("required")` — gates the fail-closed branch. */
2097
+ readonly rlsRequired: boolean;
2098
+ /** Role labels the request carries (drives `auth.can(...)`). */
2099
+ readonly roles: ReadonlyArray<string>;
2100
+ /** The shape's own predicate (`where(ctx, args)`). */
2101
+ readonly shapeWhere: WhereInput;
2102
+ /** Logical table the shape replicates. */
2103
+ readonly table: string;
2104
+ /** `true` when the table is `.public()` (exempt from `.rls("required")` denial). */
2105
+ readonly tablePublic: boolean;
2106
+ /** Verified user id, or `null` when anonymous. */
2107
+ readonly userId: null | string;
2108
+ }
2109
+ /**
2110
+ * Build the read-policy registry from the registered functions (pass
2111
+ * `Object.values(LUNORA_FUNCTIONS)`). Only `on: "read"` policies are collected,
2112
+ * grouped per `rls()` middleware so each group keeps its own role→permission map
2113
+ * (a `(table, when)` pair is de-duplicated within a tag). A tag reused across
2114
+ * several procedures (a shared `const guard = rls(...)`) is folded once. This
2115
+ * mirrors the request-time `rls()` path exactly: a policy's `auth.can(...)`
2116
+ * resolves against the roles of the middleware that declared it, never a union.
2117
+ */
2118
+ declare const buildRlsReadRegistry: (functions: Iterable<unknown>) => RlsReadRegistry;
2119
+ /**
2120
+ * Compute the effective `where` a shape replicates: the table's RLS read
2121
+ * base-where AND the shape's own predicate. Returns the shape predicate
2122
+ * unchanged for a table with no read policy (a `.public()` or non-RLS table),
2123
+ * and the FALSE sentinel (replicate nothing) when a `.rls("required")` schema
2124
+ * exposes a protected, policy-less table.
2125
+ */
2126
+ declare const composeShapeReadWhere: (registry: RlsReadRegistry, request: ShapeReadWhereRequest) => WhereInput;
2127
+ /**
2128
+ * A shape declaration. `where` receives the trusted procedure context and the
2129
+ * validated client args and returns the same {@link WhereInput} shape the RLS
2130
+ * DSL uses, so the DO can AND-merge it with the table's read base-where via the
2131
+ * existing where-compiler (zero second predicate implementation).
2132
+ */
2133
+ interface ShapeDefinition<Args extends ValidatorMap = ValidatorMap, Context = QueryCtx> {
2134
+ /**
2135
+ * Validator for the client-supplied shape parameters. Validated on the DO
2136
+ * before `where` runs, so a malformed `args` envelope is rejected at the
2137
+ * subscription boundary rather than silently widening the partition. Omit
2138
+ * for a parameterless shape.
2139
+ */
2140
+ readonly args?: Args;
2141
+ /**
2142
+ * Project the replicated rows to these columns (the system columns `_id` and
2143
+ * `_creationTime` are always included). Omit to replicate every column. An
2144
+ * empty array is rejected — it would replicate no data, which is never the
2145
+ * intent.
2146
+ */
2147
+ readonly columns?: ReadonlyArray<string>;
2148
+ /**
2149
+ * Restrict this shape to rows the **subscriber owns**, without writing the
2150
+ * predicate by hand.
2151
+ *
2152
+ * `true` uses the owning column declared by `.ownedBy(field)` on the table;
2153
+ * `"field"` names the owning column here instead (for a table that has no
2154
+ * `.ownedBy()`, or a shape that scopes by a different column).
2155
+ *
2156
+ * The derived predicate is `{ [field]: ctx.auth.userId }`, AND-composed with
2157
+ * the shape's own `where` when both are present, and an anonymous subscriber is denied
2158
+ * outright. Because the value comes from the socket's verified identity rather
2159
+ * than from `args`, a client cannot ask for another user's partition at all —
2160
+ * strictly safer than accepting a `userId` arg and comparing it.
2161
+ *
2162
+ * This is the primitive for the "every shape repeats the same ownership check"
2163
+ * pattern: with it, an owner-scoped shape needs no `where` at all.
2164
+ */
2165
+ readonly owner?: string | true;
2166
+ /** Logical table this shape replicates a partition of. */
2167
+ readonly table: string;
2168
+ /**
2169
+ * Predicate selecting the rows this shape replicates. AND-composed with the
2170
+ * table's RLS read base-where on the DO. Runs server-side with a trusted
2171
+ * `ctx` (identity/auth the client can't forge) and the validated client
2172
+ * `args`; returns a {@link WhereInput} using the same operator set as the
2173
+ * SQL compiler (`eq`/`in`/`lt`/… + `AND`/`OR`/`NOT`).
2174
+ *
2175
+ * `true`/`false` are accepted as sugar for "no further restriction" / "deny" —
2176
+ * `false` compiles to the vacuously-false predicate, so a denial branch can
2177
+ * never be mistyped as the everything-matches `{}`. Optional when `owner` is
2178
+ * set; otherwise required.
2179
+ */
2180
+ readonly where?: (context: Context, args: InferValidatorMap<Args>) => WhereInput | boolean;
2181
+ }
2182
+ /** A {@link ShapeDefinition} plus the codegen discovery marker and a dispatch-shaped `compileWhere`. */
2183
+ interface RegisteredShape<Args extends ValidatorMap = ValidatorMap, Context = QueryCtx> extends ShapeDefinition<Args, Context> {
2184
+ readonly __lunoraShape: true;
2185
+ /**
2186
+ * Validate `rawArgs`, then evaluate the shape's predicate under the trusted
2187
+ * `ctx` and return its {@link WhereInput}. Used by the generated DO's
2188
+ * `resolveShape` override: `ctx` is erased to `unknown` at this dispatch
2189
+ * boundary (the DO builds it from the socket's verified identity and hands it
2190
+ * back as the concrete {@link QueryContext} the predicate expects), exactly like
2191
+ * `RegisteredLunoraFunction.handler` erases its context.
2192
+ *
2193
+ * `options.ownerField` carries the table's `.ownedBy(field)` column, which only
2194
+ * the caller (the DO, holding the schema) can look up — it is what resolves an
2195
+ * `owner: true` shape. Omitted for an `owner: "field"` or plain `where` shape.
2196
+ */
2197
+ readonly compileWhere: (context: unknown, rawArgs: Record<string, unknown>, options?: {
2198
+ ownerField?: string;
2199
+ }) => WhereInput;
2200
+ }
2201
+ /** Declare a replication shape. See the module docs for runtime semantics. */
2202
+ declare const defineShape: <Args extends ValidatorMap = ValidatorMap, Context = QueryCtx>(definition: ShapeDefinition<Args, Context>) => RegisteredShape<Args, Context>;
2203
+ /**
2204
+ * Operations a storage rule can gate. `read` covers `download` / `getMetadata`
2205
+ * / `getSignedUrl` / `getUrl`; `write` covers `store` / `generateUploadUrl`;
2206
+ * `delete` is `delete`; `list` is a prefix listing (governed via the file
2207
+ * browser / admin path, not `ctx.storage` which has no `list`).
2208
+ */
1736
2209
  type StorageOperation = "delete" | "list" | "read" | "write";
1737
2210
  /** A rule's decision. `true` allows, `false` denies, `undefined` opts this rule out. */
1738
2211
  type StorageRuleDecision = boolean | undefined;
1739
2212
  /**
1740
- * Context handed to a storage rule. `auth` mirrors RLS's `PolicyContext.auth`
1741
- * (the per-request userId / roles / identity and the `can(permission)` helper),
1742
- * so a rule reads `({ auth, key }) => key.startsWith(`user/${auth.userId}/`)`.
1743
- * `key` is the object key the operation targets (for `list`, the listing
1744
- * prefix). `ctx` is the full procedure context the middleware closed over.
1745
- */
2213
+ * Context handed to a storage rule. `auth` mirrors RLS's `PolicyContext.auth`
2214
+ * (the per-request userId / roles / identity and the `can(permission)` helper),
2215
+ * so a rule reads `({ auth, key }) => key.startsWith(`user/${auth.userId}/`)`.
2216
+ * `key` is the object key the operation targets (for `list`, the listing
2217
+ * prefix). `ctx` is the full procedure context the middleware closed over.
2218
+ */
1746
2219
  interface StorageRuleContext<Context = unknown> {
1747
2220
  readonly auth: {
1748
2221
  readonly can: (permission: Permission | string) => boolean;
@@ -1757,11 +2230,11 @@ interface StorageRuleContext<Context = unknown> {
1757
2230
  /** A registered storage rule as stored in the rule table. */
1758
2231
  interface StorageRule<Context = unknown> {
1759
2232
  /**
1760
- * Logical bucket the rule governs — matched against the accessor's bucket
1761
- * (`ctx.storage.bucketName`, or the bucket selected via `ctx.storage.bucket(name)`).
1762
- * A rule only applies to operations on its own bucket. The unnamed bucket is
1763
- * `"default"`. Also surfaced in the studio's access-rules view.
1764
- */
2233
+ * Logical bucket the rule governs — matched against the accessor's bucket
2234
+ * (`ctx.storage.bucketName`, or the bucket selected via `ctx.storage.bucket(name)`).
2235
+ * A rule only applies to operations on its own bucket. The unnamed bucket is
2236
+ * `"default"`. Also surfaced in the studio's access-rules view.
2237
+ */
1765
2238
  readonly bucket: string;
1766
2239
  readonly on: StorageOperation;
1767
2240
  /** Optional key-prefix scope; the rule only governs keys under it. Absent ⇒ the whole bucket. */
@@ -1776,27 +2249,27 @@ interface DefineStorageRuleInput<Context = unknown> {
1776
2249
  when: (context: StorageRuleContext<Context>) => StorageRuleDecision;
1777
2250
  }
1778
2251
  /**
1779
- * Options for the `storageRules(rules, options)` middleware. `roles` registers
1780
- * the role→permission grants that back `ctx.auth.can(...)`, exactly as RLS's
1781
- * `RlsOptions.roles` does — fail-closed for unlisted roles.
1782
- */
2252
+ * Options for the `storageRules(rules, options)` middleware. `roles` registers
2253
+ * the role→permission grants that back `ctx.auth.can(...)`, exactly as RLS's
2254
+ * `RlsOptions.roles` does — fail-closed for unlisted roles.
2255
+ */
1783
2256
  interface StorageRulesOptions {
1784
2257
  readonly roles?: ReadonlyArray<Role>;
1785
2258
  }
1786
2259
  declare const defineStorageRule: <Context = unknown>(input: DefineStorageRuleInput<Context>) => StorageRule<Context>;
1787
2260
  /**
1788
- * Collect a list of storage rules into the structure the `storageRules()`
1789
- * middleware consumes. Multiple rules for the same `(bucket, on)` OR together —
1790
- * any one allowing grants the operation (each rule grants a slice of the
1791
- * keyspace).
1792
- *
1793
- * Validates against an **accidentally duplicated rule** — the same
1794
- * `(bucket, on, prefix)` registered with the *same* decision function (a
1795
- * copy-paste, or the same rule object spread in twice). Because multiple
1796
- * DISTINCT rules per `(bucket, on)` are intentional, the check keys on the
1797
- * `when` reference too. Throws at module load so the misconfiguration surfaces
1798
- * immediately rather than as a silently double-evaluated predicate.
1799
- */
2261
+ * Collect a list of storage rules into the structure the `storageRules()`
2262
+ * middleware consumes. Multiple rules for the same `(bucket, on)` OR together —
2263
+ * any one allowing grants the operation (each rule grants a slice of the
2264
+ * keyspace).
2265
+ *
2266
+ * Validates against an **accidentally duplicated rule** — the same
2267
+ * `(bucket, on, prefix)` registered with the *same* decision function (a
2268
+ * copy-paste, or the same rule object spread in twice). Because multiple
2269
+ * DISTINCT rules per `(bucket, on)` are intentional, the check keys on the
2270
+ * `when` reference too. Throws at module load so the misconfiguration surfaces
2271
+ * immediately rather than as a silently double-evaluated predicate.
2272
+ */
1800
2273
  declare const defineStorageRules: <Context = unknown>(rules: ReadonlyArray<StorageRule<Context>>) => ReadonlyArray<StorageRule<Context>>;
1801
2274
  /** The minimal `ctx.auth` shape the middleware reads — a structural subset that the full AuthState satisfies. Tolerant of older auth states (mirrors RLS's `AuthLike`). */
1802
2275
  type StorageAuthLike = {
@@ -1810,4 +2283,4 @@ interface StorageContextIn {
1810
2283
  }
1811
2284
  declare const storageRules: <Context extends StorageContextIn = StorageContextIn>(rules: ReadonlyArray<StorageRule<Context>>, options?: StorageRulesOptions) => Middleware<Context, Context>;
1812
2285
  declare const VERSION = "0.0.0";
1813
- export { type ActionBuilder, type ActionCtx, type AggregateIndexDefinition, type AggregateIndexOptions, type AggregateOp, type ArgsValidator, type Component, type ComponentFunctions, type CreateOptions, type DataModelInit, type DefineComponentOptions, type DefinePluginOptions, type DefinePolicyInput, type DefinePresenceOptions, type DefineStorageRuleInput, type DurableObjectJurisdiction, type EmptyArgs, type EnvAccessor, type EnvKeyFailure, type EnvShape, type ExtendableSchema, type FacadeEntry, type FacadeWriterLike, type FunctionKind, type HttpActionCtx, type HttpActionHandler, type HttpMethod, type HttpRoute, type HttpRouteBuilder, type HttpRouteFactory, type HttpRouteHandlerOptions, type HttpStreamHandlerOptions, type InferArgs, type InferEnv, type InlineAggregateIndexOptions, type InlineRankIndexOptions, type InternalActionBuilder, type InternalMutationBuilder, type InternalQueryBuilder, type LifecycleEvent, type LifecycleHandler, type LunoraBuilders, LunoraEnvError, LunoraError, type LunoraErrorCode, type LunoraHttpApp, type LunoraHttpEnv, type LunoraRouteHandler, type ManyRelation, type MaskColumns, type MaskContext, type MaskFn, type MaskOptions, type MaskPolicies, type MaskStrategy, type Middleware, type MiddlewareNext, type MigrationDefinition, type MigrationDocument, type MigrationTransform, type MutationBuilder, type MutationCtx, type OnDeleteAction, type OneRelation, type OrmLike, DEFAULT_TTL_MS as PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, type Permission, type Plugin, type Policy, type PrefixedTables, type PresenceComponent, type PresenceFunctions, type PresenceMember, type ProtectPublicOptions, type QueryBuilder, type QueryCtx, type RankIndexDefinition, type RankIndexOptions, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMigration, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationBuilder, type RelationDefinition, type RlsOptions, type Role, type Schema, type SchemaExtension, type StorageOperation, type StorageRule, type StorageRuleContext, type StorageRuleDecision, type StorageRulesOptions, type TableBuilder, type TableDefinition, type TerminalKind, type TriggerBuilder, type TriggerDefinition, type TypedDefinePolicyInput, VERSION, type VectorEmbedder, type VectorIndexDefinition, type VectorIndexOptions, type VectorMetric, type VectorizeOptions, type WhereInput, asBucketStorage, bindOrm, bindTableFacade, composePluginMiddleware, createPolicyDsl, defineAggregateIndex, defineComponent, defineEnv, defineMigration, definePermission, definePlugin, definePolicies, definePolicy, definePresence, defineRankIndex, defineRole, defineSchema, defineSchemaExtension, defineStorageRule, defineStorageRules, defineTable, defineVectorIndex, httpAction, httpRoute, httpRouter, initLunora, installPlugins, mask, mergeSchemaExtension, onConnect, onDisconnect, presenceExtension, protectPublic, redactSecrets, rls, serveStorageObject, storageRules };
2286
+ export { type ActionBuilder, type ActionCtx, type AggregateIndexDefinition, type AggregateIndexOptions, type AggregateOp, type ArgsValidator, type Component, type ComponentFunctions, type CreateOptions, type DataModelInit, type DefineComponentOptions, type DefineIdentityOptions, type DefinePluginOptions, type DefinePolicyInput, type DefinePresenceOptions, type DefineStorageRuleInput, type DurableObjectJurisdiction, type EmptyArgs, type EnvAccessor, type EnvKeyFailure, type EnvShape, type ExposeConfig, type ExtendableSchema, type FacadeEntry, type FacadeWriterLike, type FunctionKind, type HttpActionCtx, type HttpActionHandler, type HttpMethod, type HttpRoute, type HttpRouteBuilder, type HttpRouteFactory, type HttpRouteHandlerOptions, type HttpStreamHandlerOptions, type IdentityContract, type IdentityRejectMode, type IdentityValidation, type InferArgs, type InferEnv, type InferIdentity, type InlineAggregateIndexOptions, type InlineRankIndexOptions, type InternalActionBuilder, type InternalMutationBuilder, type InternalQueryBuilder, type LifecycleEvent, type LifecycleHandler, type LunoraBuilders, LunoraEnvError, LunoraError, type LunoraHttpApp, type LunoraHttpEnv, type LunoraRouteHandler, type ManyRelation, type MaskColumns, type MaskContext, type MaskFn, type MaskOptions, type MaskPolicies, type MaskStrategy, type Middleware, type MiddlewareNext, type MigrationDefinition, type MigrationDocument, type MigrationTransform, type MutationBuilder, type MutationCtx, type MutatorDefinition, type OnDeleteAction, type OneRelation, type OrmLike, DEFAULT_TTL_MS as PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, type Permission, type Plugin, type Policy, type PrefixedTables, type PresenceComponent, type PresenceFunctions, type PresenceMember, type ProtectPublicOptions, type QueryBuilder, type QueryCtx, type RankIndexDefinition, type RankIndexOptions, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMigration, type RegisteredMutation, type RegisteredMutator, type RegisteredQuery, type RegisteredShape, type RegisteredStream, type RelationBuilder, type RelationDefinition, type RlsOptions, type RlsReadRegistry, type Role, type Schema, type SchemaExtension, type ShapeDefinition, type ShapeReadWhereRequest, type StorageOperation, type StorageRule, type StorageRuleContext, type StorageRuleDecision, type StorageRulesOptions, type TableBuilder, type TableDefinition, type TerminalKind, type TriggerBuilder, type TriggerDefinition, type TypedDefinePolicyInput, VERSION, type VectorEmbedder, type VectorIndexDefinition, type VectorIndexOptions, type VectorMetric, type VectorizeOptions, type WhereInput, allowAll, asBucketStorage, bindOrm, bindTableFacade, buildRlsReadRegistry, composePluginMiddleware, composeShapeReadWhere, createPolicyDsl, createSecrets, defineAggregateIndex, defineComponent, defineEnv, defineIdentity, defineMigration, defineMutator, definePermission, definePlugin, definePolicies, definePolicy, definePresence, defineRankIndex, defineRole, defineSchema, defineSchemaExtension, defineShape, defineStorageRule, defineStorageRules, defineTable, defineVectorIndex, deny, httpAction, httpRoute, httpRouter, initLunora, installPlugins, isDeny, isSafeHeaderValue, mask, mergeSchemaExtension, onConnect, onDisconnect, presenceExtension, protectPublic, redactSecrets, rls, serveStorageObject, storageRules, toWhereInput };