@lunora/server 1.0.0-alpha.26 → 1.0.0-alpha.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,39 +1,39 @@
1
1
  import { Validator, Infer, ValidatorMap, InferValidatorMap, v } from '@lunora/values';
2
2
  export { type ColumnValidator, type Id, type Infer, ValidationError, type Validator, type ValidatorKind, v } from '@lunora/values';
3
3
  import { ArgsValidator, InferArgs, RegisteredAction, 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 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";
4
+ export { type AnyApi, type AuthState, type CachePurge, type DatabaseReader, type DatabaseWriter, type FunctionVisibility, type IndexDefinition, type IndexRangeBuilder, type LifecycleEventKind, type LogFields, type LunoraLogMethod, 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";
5
5
  import { LunoraError as LunoraError$1, LunoraErrorCode } from '@lunora/errors';
6
6
  export type { LunoraErrorCode } from '@lunora/errors';
7
7
  import { Context, Hono } from 'hono';
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-Cxl6ndhm.js";
9
- export type { d as PolicyContext, e as PolicyDecision, f as PolicyDecisionOf, P as PolicyOperation } from "./packem_shared/types.d-Cxl6ndhm.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";
10
10
  export { type CronJob, type CronJobsBuilder, type CronScheduleKind, type DailySchedule, type IntervalSchedule, type MonthlySchedule, type WeeklySchedule, cronJobs } from '@lunora/scheduler';
11
11
  import "./data-model.js";
12
12
  /**
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
- */
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
+ */
27
27
  declare const asBucketStorage: (raw: unknown) => unknown;
28
28
  /** Builder discriminator. Codegen reads this kind. */
29
29
  type TerminalKind = FunctionKind;
30
30
  /** Initial (empty) accumulated args for a fresh builder. */
31
31
  type EmptyArgs = Record<never, never>;
32
32
  /**
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
- */
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
+ */
37
37
  interface MiddlewareNext<ContextIn> {
38
38
  (): Promise<ContextIn>;
39
39
  <Extension extends Record<string, unknown>>(options: {
@@ -41,10 +41,10 @@ interface MiddlewareNext<ContextIn> {
41
41
  }): Promise<ContextIn & Extension>;
42
42
  }
43
43
  /**
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
- */
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
+ */
48
48
  type Middleware<ContextIn, ContextOut> = (options: {
49
49
  ctx: ContextIn;
50
50
  next: MiddlewareNext<ContextIn>;
@@ -52,14 +52,14 @@ type Middleware<ContextIn, ContextOut> = (options: {
52
52
  /** Options accepted by `initLunora.dataModel&lt;DM>().create(...)`. Reserved for transformer/error-formatter wiring. */
53
53
  type CreateOptions = Record<never, never>;
54
54
  /**
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
- */
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
+ */
63
63
  interface QueryBuilder<Context, Args extends ArgsValidator, Output = undefined> {
64
64
  readonly __lunoraProcedure: "query";
65
65
  input: <A extends ArgsValidator>(validators: A) => QueryBuilder<Context, A & Args, Output>;
@@ -72,13 +72,13 @@ interface QueryBuilder<Context, Args extends ArgsValidator, Output = undefined>
72
72
  ctx: Context;
73
73
  }) => Output | Promise<Output>) => RegisteredQuery<Args, Output>;
74
74
  /**
75
- * Terminal: declare this procedure as a streaming query. The handler is an
76
- * async generator (or any function returning an `AsyncIterable&lt;R>`) that
77
- * yields one chunk per server-pushed frame. The third `signal` argument is
78
- * tripped when the client cancels — break out of the loop or check
79
- * `signal.aborted` between yields. `.output()` does not apply: per-chunk
80
- * validation is opt-in via the handler itself.
81
- */
75
+ * Terminal: declare this procedure as a streaming query. The handler is an
76
+ * async generator (or any function returning an `AsyncIterable&lt;R>`) that
77
+ * yields one chunk per server-pushed frame. The third `signal` argument is
78
+ * tripped when the client cancels — break out of the loop or check
79
+ * `signal.aborted` between yields. `.output()` does not apply: per-chunk
80
+ * validation is opt-in via the handler itself.
81
+ */
82
82
  stream: <R>(handler: (options: {
83
83
  args: InferArgs<Args>;
84
84
  ctx: Context;
@@ -86,11 +86,11 @@ interface QueryBuilder<Context, Args extends ArgsValidator, Output = undefined>
86
86
  }) => AsyncGenerator<R, void, void> | AsyncIterable<R>) => RegisteredStream<Args, R>;
87
87
  use: <ContextOut>(middleware: Middleware<Context, ContextOut>) => QueryBuilder<ContextOut, Args, Output>;
88
88
  /**
89
- * Mark this query as paid. The origin worker answers an unpaid client RPC
90
- * with HTTP 402, verifies + settles the x402 payment, then dispatches. `price`
91
- * is USD (a number of dollars or a `"0.01"`/`"$0.01"` string); the network,
92
- * recipient, and facilitator come from the worker-level x402 charge config.
93
- */
89
+ * Mark this query as paid. The origin worker answers an unpaid client RPC
90
+ * with HTTP 402, verifies + settles the x402 payment, then dispatches. `price`
91
+ * is USD (a number of dollars or a `"0.01"`/`"$0.01"` string); the network,
92
+ * recipient, and facilitator come from the worker-level x402 charge config.
93
+ */
94
94
  x402: (config: X402ProcedureConfig) => QueryBuilder<Context, Args, Output>;
95
95
  }
96
96
  interface MutationBuilder<Context, Args extends ArgsValidator, Output = undefined> {
@@ -106,11 +106,11 @@ interface MutationBuilder<Context, Args extends ArgsValidator, Output = undefine
106
106
  output: <V extends Validator>(validator: V) => MutationBuilder<Context, Args, Infer<V>>;
107
107
  use: <ContextOut>(middleware: Middleware<Context, ContextOut>) => MutationBuilder<ContextOut, Args, Output>;
108
108
  /**
109
- * Mark this mutation as paid. The origin worker answers an unpaid client RPC
110
- * with HTTP 402, verifies + settles the x402 payment, then dispatches. `price`
111
- * is USD (a number of dollars or a `"0.01"`/`"$0.01"` string); the network,
112
- * recipient, and facilitator come from the worker-level x402 charge config.
113
- */
109
+ * Mark this mutation as paid. The origin worker answers an unpaid client RPC
110
+ * with HTTP 402, verifies + settles the x402 payment, then dispatches. `price`
111
+ * is USD (a number of dollars or a `"0.01"`/`"$0.01"` string); the network,
112
+ * recipient, and facilitator come from the worker-level x402 charge config.
113
+ */
114
114
  x402: (config: X402ProcedureConfig) => MutationBuilder<Context, Args, Output>;
115
115
  }
116
116
  interface ActionBuilder<Context, Args extends ArgsValidator, Output = undefined> {
@@ -126,19 +126,19 @@ interface ActionBuilder<Context, Args extends ArgsValidator, Output = undefined>
126
126
  output: <V extends Validator>(validator: V) => ActionBuilder<Context, Args, Infer<V>>;
127
127
  use: <ContextOut>(middleware: Middleware<Context, ContextOut>) => ActionBuilder<ContextOut, Args, Output>;
128
128
  /**
129
- * Mark this action as paid. The origin worker answers an unpaid client RPC
130
- * with HTTP 402, verifies + settles the x402 payment, then dispatches. `price`
131
- * is USD (a number of dollars or a `"0.01"`/`"$0.01"` string); the network,
132
- * recipient, and facilitator come from the worker-level x402 charge config.
133
- */
129
+ * Mark this action as paid. The origin worker answers an unpaid client RPC
130
+ * with HTTP 402, verifies + settles the x402 payment, then dispatches. `price`
131
+ * is USD (a number of dollars or a `"0.01"`/`"$0.01"` string); the network,
132
+ * recipient, and facilitator come from the worker-level x402 charge config.
133
+ */
134
134
  x402: (config: X402ProcedureConfig) => ActionBuilder<Context, Args, Output>;
135
135
  }
136
136
  /**
137
- * Internal builder variants. Identical to their public counterparts but carry
138
- * the `__lunoraVisibility: "internal"` brand codegen keys off to route the
139
- * registration into the `internal` object (and keep it off `api`). `input`/`use`
140
- * return the internal builder type so the brand survives the whole chain.
141
- */
137
+ * Internal builder variants. Identical to their public counterparts but carry
138
+ * the `__lunoraVisibility: "internal"` brand codegen keys off to route the
139
+ * registration into the `internal` object (and keep it off `api`). `input`/`use`
140
+ * return the internal builder type so the brand survives the whole chain.
141
+ */
142
142
  interface InternalQueryBuilder<Context, Args extends ArgsValidator, Output = undefined> {
143
143
  readonly __lunoraProcedure: "query";
144
144
  readonly __lunoraVisibility: "internal";
@@ -202,38 +202,38 @@ interface DataModelInit<DataModel> {
202
202
  create: (options?: CreateOptions) => LunoraBuilders;
203
203
  }
204
204
  /**
205
- * Entry point for the procedure builder. `dataModel&lt;DM>()` binds the generated
206
- * `DataModel` (phantom for now), and `.create()` yields the public root builders
207
- * plus their `internal*` counterparts.
208
- */
205
+ * Entry point for the procedure builder. `dataModel&lt;DM>()` binds the generated
206
+ * `DataModel` (phantom for now), and `.create()` yields the public root builders
207
+ * plus their `internal*` counterparts.
208
+ */
209
209
  declare const initLunora: {
210
210
  dataModel: <DataModel>() => DataModelInit<DataModel>;
211
211
  };
212
212
  /**
213
- * Build the `ctx.secrets` reader from the worker `env`. `get(name)` resolves
214
- * `env[name].get()` — the `secrets_store_secrets[]` binding of that name. An
215
- * absent or non-Secrets-Store binding throws a directed error pointing at the
216
- * wrangler config; the lookup is lazy, so an unused secret never resolves.
217
- */
213
+ * Build the `ctx.secrets` reader from the worker `env`. `get(name)` resolves
214
+ * `env[name].get()` — the `secrets_store_secrets[]` binding of that name. An
215
+ * absent or non-Secrets-Store binding throws a directed error pointing at the
216
+ * wrangler config; the lookup is lazy, so an unused secret never resolves.
217
+ */
218
218
  declare const createSecrets: (env: Record<string, unknown>) => Secrets;
219
219
  /**
220
- * Redact secrets from a free-form message. Masks, in order: any quoted value
221
- * whose contents look like a credential (so a value surfaced as `received string
222
- * "sk_live_…"` is masked even though the surrounding text is not a token); a
223
- * `scheme://user:password@host` URL credential (the password segment); any
224
- * known-prefix credential token wherever it appears, at any length; any value
225
- * following a secret-named key in `KEY=value` / `KEY: value` form; and any
226
- * remaining bare high-entropy ≥24-char token run anywhere in the message.
227
- *
228
- * This is BEST-EFFORT defense-in-depth, NOT a guarantee: a short, prefix-less
229
- * secret under a non-secret-named key (and embedded credentials in shapes not
230
- * enumerated here) can still slip through. Treat it as a backstop — prefer
231
- * structured logging that never serializes raw env/secret fields in the first
232
- * place over relying on post-hoc scrubbing of untrusted data.
233
- *
234
- * Exported because it is independently useful — call it before logging anything
235
- * derived from `env`, request bodies, or thrown errors.
236
- */
220
+ * Redact secrets from a free-form message. Masks, in order: any quoted value
221
+ * whose contents look like a credential (so a value surfaced as `received string
222
+ * "sk_live_…"` is masked even though the surrounding text is not a token); a
223
+ * `scheme://user:password@host` URL credential (the password segment); any
224
+ * known-prefix credential token wherever it appears, at any length; any value
225
+ * following a secret-named key in `KEY=value` / `KEY: value` form; and any
226
+ * remaining bare high-entropy ≥24-char token run anywhere in the message.
227
+ *
228
+ * This is BEST-EFFORT defense-in-depth, NOT a guarantee: a short, prefix-less
229
+ * secret under a non-secret-named key (and embedded credentials in shapes not
230
+ * enumerated here) can still slip through. Treat it as a backstop — prefer
231
+ * structured logging that never serializes raw env/secret fields in the first
232
+ * place over relying on post-hoc scrubbing of untrusted data.
233
+ *
234
+ * Exported because it is independently useful — call it before logging anything
235
+ * derived from `env`, request bodies, or thrown errors.
236
+ */
237
237
  declare const redactSecrets: (message: string) => string;
238
238
  /** One key's validation failure, secrets already redacted out of `message`. */
239
239
  interface EnvKeyFailure {
@@ -243,12 +243,12 @@ interface EnvKeyFailure {
243
243
  message: string;
244
244
  }
245
245
  /**
246
- * Thrown when one or more env keys are missing or fail validation. Carries the
247
- * structured list of `failures` (each with the offending `key`) so callers can
248
- * react programmatically; `message` is the joined, secret-redacted summary.
249
- *
250
- * Named export only (no default) per the repo export convention.
251
- */
246
+ * Thrown when one or more env keys are missing or fail validation. Carries the
247
+ * structured list of `failures` (each with the offending `key`) so callers can
248
+ * react programmatically; `message` is the joined, secret-redacted summary.
249
+ *
250
+ * Named export only (no default) per the repo export convention.
251
+ */
252
252
  declare class LunoraEnvError extends LunoraError$1 {
253
253
  readonly failures: ReadonlyArray<EnvKeyFailure>;
254
254
  constructor(failures: ReadonlyArray<EnvKeyFailure>);
@@ -256,18 +256,18 @@ declare class LunoraEnvError extends LunoraError$1 {
256
256
  /** A record of `v.*` validators describing the expected env shape. */
257
257
  type EnvShape = Record<string, Validator>;
258
258
  /**
259
- * The typed output of {@link defineEnv}. Optional validators (`v.optional(...)`)
260
- * become optional keys; everything else is required. Mirrors how `InferArgs`
261
- * derives an args object from a validator map.
262
- */
263
- 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]> };
264
- /**
265
- * The accessor returned by {@link defineEnv}. A typed view over an `env` object
266
- * plus a `.parse(env)` escape hatch that validates every key eagerly.
267
- *
268
- * Call the accessor with the worker's `env` to get the typed, lazily-validated
269
- * proxy: `const config = defineEnv({ … }); const { PORT } = config(env);`.
270
- */
259
+ * The typed output of {@link defineEnv}. Optional validators (`v.optional(...)`)
260
+ * become optional keys; everything else is required. Mirrors how `InferArgs`
261
+ * derives an args object from a validator map.
262
+ */
263
+ 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]>; };
264
+ /**
265
+ * The accessor returned by {@link defineEnv}. A typed view over an `env` object
266
+ * plus a `.parse(env)` escape hatch that validates every key eagerly.
267
+ *
268
+ * Call the accessor with the worker's `env` to get the typed, lazily-validated
269
+ * proxy: `const config = defineEnv({ … }); const { PORT } = config(env);`.
270
+ */
271
271
  interface EnvAccessor<S extends EnvShape> {
272
272
  /** Validate every key eagerly and return the typed, plain (non-proxy) object. Use for fail-fast-at-boot. */
273
273
  parse: (env: unknown) => InferEnv<S>;
@@ -275,40 +275,40 @@ interface EnvAccessor<S extends EnvShape> {
275
275
  (env: unknown): InferEnv<S>;
276
276
  }
277
277
  /**
278
- * Define a typed, validated accessor over a Worker's `env`. Pass a record of
279
- * `v.*` validators; receive an accessor that validates lazily per key (cached
280
- * per `env` identity) and infers its output type from the validators.
281
- *
282
- * ```ts
283
- * import { defineEnv, v } from "@lunora/server";
284
- *
285
- * const config = defineEnv({
286
- * STRIPE_KEY: v.string(),
287
- * PORT: v.optional(v.number()),
288
- * });
289
- *
290
- * export default {
291
- * fetch(request, env) {
292
- * const { STRIPE_KEY, PORT } = config(env); // STRIPE_KEY: string, PORT?: number
293
- * // …
294
- * },
295
- * };
296
- * ```
297
- *
298
- * Throws {@link LunoraEnvError} (secrets redacted) when a key is missing or
299
- * invalid — lazily on first access of that key, or eagerly via `config.parse(env)`.
300
- */
278
+ * Define a typed, validated accessor over a Worker's `env`. Pass a record of
279
+ * `v.*` validators; receive an accessor that validates lazily per key (cached
280
+ * per `env` identity) and infers its output type from the validators.
281
+ *
282
+ * ```ts
283
+ * import { defineEnv, v } from "@lunora/server";
284
+ *
285
+ * const config = defineEnv({
286
+ * STRIPE_KEY: v.string(),
287
+ * PORT: v.optional(v.number()),
288
+ * });
289
+ *
290
+ * export default {
291
+ * fetch(request, env) {
292
+ * const { STRIPE_KEY, PORT } = config(env); // STRIPE_KEY: string, PORT?: number
293
+ * // …
294
+ * },
295
+ * };
296
+ * ```
297
+ *
298
+ * Throws {@link LunoraEnvError} (secrets redacted) when a key is missing or
299
+ * invalid — lazily on first access of that key, or eagerly via `config.parse(env)`.
300
+ */
301
301
  declare const defineEnv: <S extends EnvShape>(shape: S) => EnvAccessor<S>;
302
302
  declare class LunoraError extends LunoraError$1 {
303
303
  constructor(code: LunoraErrorCode, message?: string, data?: unknown);
304
304
  }
305
305
  /**
306
- * Minimal structural writer the facade binds over. Declared with **method**
307
- * syntax (not arrow properties) so a more-specifically-typed writer — both
308
- * `@lunora/do`'s `DatabaseWriterLike` and the RLS middleware's wrapped writer —
309
- * stays assignable under bivariant parameter checking. That is the whole reason
310
- * the shared helper can serve both callers, hence the rule exemption.
311
- */
306
+ * Minimal structural writer the facade binds over. Declared with **method**
307
+ * syntax (not arrow properties) so a more-specifically-typed writer — both
308
+ * `@lunora/do`'s `DatabaseWriterLike` and the RLS middleware's wrapped writer —
309
+ * stays assignable under bivariant parameter checking. That is the whole reason
310
+ * the shared helper can serve both callers, hence the rule exemption.
311
+ */
312
312
  interface FacadeWriterLike {
313
313
  aggregate(tableName: string, options: unknown): Promise<unknown>;
314
314
  count(tableName: string, where?: unknown): Promise<number>;
@@ -389,11 +389,11 @@ interface FacadeEntry {
389
389
  hardDelete: (id: string) => Promise<void>;
390
390
  insert: (document: Record<string, unknown>, options?: FacadeInsertOptions) => Promise<null | string>;
391
391
  /**
392
- * Insert many documents into this table in one call. With
393
- * `{ skipDuplicates: true }`, UNIQUE breaches resolve to `null` for that row
394
- * instead of failing the batch. The typed facade narrows the return to
395
- * `Id&lt;T>[]` when skipDuplicates is not requested.
396
- */
392
+ * Insert many documents into this table in one call. With
393
+ * `{ skipDuplicates: true }`, UNIQUE breaches resolve to `null` for that row
394
+ * instead of failing the batch. The typed facade narrows the return to
395
+ * `Id&lt;T>[]` when skipDuplicates is not requested.
396
+ */
397
397
  insertMany: (documents: ReadonlyArray<Record<string, unknown>>, options?: {
398
398
  limit?: number;
399
399
  skipDuplicates?: boolean;
@@ -430,10 +430,10 @@ interface FacadeEntry {
430
430
  /** Options accepted by the per-table `insert` accessor. */
431
431
  interface FacadeInsertOptions {
432
432
  /**
433
- * When `true`, a UNIQUE-constraint breach is swallowed: the insert becomes a
434
- * silent no-op and resolves to `null` instead of throwing a `CONFLICT`. Any
435
- * other error still propagates. Mirrors better-drizzle's `create({ skipDuplicates })`.
436
- */
433
+ * When `true`, a UNIQUE-constraint breach is swallowed: the insert becomes a
434
+ * silent no-op and resolves to `null` instead of throwing a `CONFLICT`. Any
435
+ * other error still propagates. Mirrors better-drizzle's `create({ skipDuplicates })`.
436
+ */
437
437
  skipDuplicates?: boolean;
438
438
  }
439
439
  /** The conflict target for `upsert`/`upsertMany`: one field name or a tuple of them. */
@@ -461,17 +461,17 @@ interface UpsertManyArgs {
461
461
  target: UpsertTarget;
462
462
  }
463
463
  /**
464
- * Bind a structural writer to one table, producing its `ctx.db` table accessor.
465
- *
466
- * The by-id accessors (`get`/`delete`/`patch`/`replace`) forward the bound
467
- * `tableName` as `expectedTable` so the underlying writer scopes its id lookup
468
- * to this table. Without it, a branded `Id&lt;"posts">` carrying another table's
469
- * id would resolve cross-table (the writer probes every table by id), letting
470
- * `ctx.db.posts.get(foreignId)` read — or `.delete`/`.patch`/`.replace`
471
- * mutate — a row in an unrelated table (IDOR). Writers that ignore the second
472
- * argument keep their previous global behaviour; the scoping is opt-in via this
473
- * forwarded name.
474
- */
464
+ * Bind a structural writer to one table, producing its `ctx.db` table accessor.
465
+ *
466
+ * The by-id accessors (`get`/`delete`/`patch`/`replace`) forward the bound
467
+ * `tableName` as `expectedTable` so the underlying writer scopes its id lookup
468
+ * to this table. Without it, a branded `Id&lt;"posts">` carrying another table's
469
+ * id would resolve cross-table (the writer probes every table by id), letting
470
+ * `ctx.db.posts.get(foreignId)` read — or `.delete`/`.patch`/`.replace`
471
+ * mutate — a row in an unrelated table (IDOR). Writers that ignore the second
472
+ * argument keep their previous global behaviour; the scoping is opt-in via this
473
+ * forwarded name.
474
+ */
475
475
  declare const bindTableFacade: (writer: FacadeWriterLike, tableName: string) => FacadeEntry;
476
476
  /** The kitcn-style `ctx.orm` namespace over a per-table facade map. */
477
477
  interface OrmLike {
@@ -492,21 +492,21 @@ declare const bindOrm: (facade: Record<string, FacadeEntry>) => OrmLike;
492
492
  /** HTTP verbs the typed {@link httpRoute} builder can bind to. */
493
493
  type HttpMethod = "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT";
494
494
  /**
495
- * Context handed to an HTTP action handler. A narrower view of {@link ActionContext}:
496
- * HTTP actions run in the worker (the "action runtime"), separate from the
497
- * transactional store, so there is no direct `db` / `vectors` / `scheduler` /
498
- * `storage` surface — reach the data layer through `runQuery` / `runMutation` /
499
- * `runAction`, which forward to the owning shard.
500
- */
495
+ * Context handed to an HTTP action handler. A narrower view of {@link ActionContext}:
496
+ * HTTP actions run in the worker (the "action runtime"), separate from the
497
+ * transactional store, so there is no direct `db` / `vectors` / `scheduler` /
498
+ * `storage` surface — reach the data layer through `runQuery` / `runMutation` /
499
+ * `runAction`, which forward to the owning shard.
500
+ */
501
501
  type HttpActionCtx = Pick<ActionCtx, "auth" | "cache" | "fetch" | "runAction" | "runMutation" | "runQuery">;
502
502
  /** A raw handler wrapped by {@link httpAction}. Receives the raw request, returns the raw response. */
503
503
  type HttpActionHandler = (context: HttpActionCtx, request: Request) => Promise<Response> | Response;
504
504
  /**
505
- * The hono {@link https://hono.dev | Hono} environment used by {@link httpRouter}.
506
- * The runtime injects the per-request {@link HttpActionCtx} on the private
507
- * `__lunoraCtx` binding; the router's lifting middleware promotes it to
508
- * `c.var.lunora` so handlers can read it as a typed variable.
509
- */
505
+ * The hono {@link https://hono.dev | Hono} environment used by {@link httpRouter}.
506
+ * The runtime injects the per-request {@link HttpActionCtx} on the private
507
+ * `__lunoraCtx` binding; the router's lifting middleware promotes it to
508
+ * `c.var.lunora` so handlers can read it as a typed variable.
509
+ */
510
510
  interface LunoraHttpEnv {
511
511
  Bindings: Record<string, unknown> & {
512
512
  __lunoraCtx?: HttpActionCtx;
@@ -520,31 +520,31 @@ type LunoraHttpApp = Hono<LunoraHttpEnv>;
520
520
  /** A compiled route handler: a hono handler that resolves to a raw {@link Response}. */
521
521
  type LunoraRouteHandler = (c: Context<LunoraHttpEnv>) => Promise<Response>;
522
522
  /**
523
- * Wrap a `(ctx, request) => Response` handler as a hono handler. The raw escape
524
- * hatch — mount it with `app.all(path, httpAction(fn))`. `ctx` is the
525
- * runtime-injected {@link HttpActionCtx} lifted into `c.var.lunora` by
526
- * {@link httpRouter}; `request` is the underlying `c.req.raw`.
527
- */
523
+ * Wrap a `(ctx, request) => Response` handler as a hono handler. The raw escape
524
+ * hatch — mount it with `app.all(path, httpAction(fn))`. `ctx` is the
525
+ * runtime-injected {@link HttpActionCtx} lifted into `c.var.lunora` by
526
+ * {@link httpRouter}; `request` is the underlying `c.req.raw`.
527
+ */
528
528
  declare const httpAction: (handler: HttpActionHandler) => LunoraRouteHandler;
529
529
  /**
530
- * Create the hono app for HTTP actions. Pre-wired with a middleware that lifts
531
- * the runtime-injected `c.env.__lunoraCtx` into `c.var.lunora`, so both
532
- * {@link httpAction} and the typed {@link httpRoute} builder can read the action
533
- * context. The full hono surface is available — plugins, path params, `.route`:
534
- *
535
- * ```ts
536
- * const app = httpRouter();
537
- * app.use("*", cors());
538
- * app.post("/webhook", httpAction(onWebhook));
539
- * app.get("/users/:id", getUser);
540
- * export default createWorker({ httpRouter: app, ... });
541
- * ```
542
- *
543
- * The lifting middleware throws if the context is absent. `createWorker` injects
544
- * it on every request the router sees, so this only trips when the app is run
545
- * outside the runtime — a misconfiguration we surface loudly rather than let
546
- * `c.var.lunora` be silently `undefined` despite its non-optional type.
547
- */
530
+ * Create the hono app for HTTP actions. Pre-wired with a middleware that lifts
531
+ * the runtime-injected `c.env.__lunoraCtx` into `c.var.lunora`, so both
532
+ * {@link httpAction} and the typed {@link httpRoute} builder can read the action
533
+ * context. The full hono surface is available — plugins, path params, `.route`:
534
+ *
535
+ * ```ts
536
+ * const app = httpRouter();
537
+ * app.use("*", cors());
538
+ * app.post("/webhook", httpAction(onWebhook));
539
+ * app.get("/users/:id", getUser);
540
+ * export default createWorker({ httpRouter: app, ... });
541
+ * ```
542
+ *
543
+ * The lifting middleware throws if the context is absent. `createWorker` injects
544
+ * it on every request the router sees, so this only trips when the app is run
545
+ * outside the runtime — a misconfiguration we surface loudly rather than let
546
+ * `c.var.lunora` be silently `undefined` despite its non-optional type.
547
+ */
548
548
  declare const httpRouter: () => LunoraHttpApp;
549
549
  /** The `{ ctx, searchParams, body, params }` a typed route handler receives. */
550
550
  interface HttpRouteHandlerOptions<SearchParams extends ArgsValidator, Body extends ArgsValidator, Params extends ArgsValidator> {
@@ -554,12 +554,12 @@ interface HttpRouteHandlerOptions<SearchParams extends ArgsValidator, Body exten
554
554
  searchParams: InferArgs<SearchParams>;
555
555
  }
556
556
  /**
557
- * The `{ ctx, searchParams, params, request, signal }` a streaming HTTP
558
- * handler receives. There is no parsed `body` — streams are typically GET, and
559
- * the raw `request` is exposed if a handler needs to read the body itself.
560
- * `signal` is tripped when the client disconnects.
561
- * @experimental Part of the HTTP-SSE stream surface; reconnect/POST-body design questions are still open.
562
- */
557
+ * The `{ ctx, searchParams, params, request, signal }` a streaming HTTP
558
+ * handler receives. There is no parsed `body` — streams are typically GET, and
559
+ * the raw `request` is exposed if a handler needs to read the body itself.
560
+ * `signal` is tripped when the client disconnects.
561
+ * @experimental Part of the HTTP-SSE stream surface; reconnect/POST-body design questions are still open.
562
+ */
563
563
  interface HttpStreamHandlerOptions<SearchParams extends ArgsValidator, Params extends ArgsValidator> {
564
564
  ctx: HttpActionCtx;
565
565
  params: InferArgs<Params>;
@@ -568,50 +568,50 @@ interface HttpStreamHandlerOptions<SearchParams extends ArgsValidator, Params ex
568
568
  signal: AbortSignal;
569
569
  }
570
570
  /**
571
- * A typed REST route under construction. `.searchParams()` / `.body()` /
572
- * `.params()` accumulate validator maps (later calls merge, a colliding key
573
- * wins) that decode the URL query, JSON body, and hono path params into the
574
- * handler's typed `searchParams` / `body` / `params`. Like the procedure
575
- * builder, `.output(validator)` defaults to the `undefined` sentinel — while
576
- * unset the handler is generic over its own return; once set the handler must
577
- * return that type and the result is parsed through the validator before
578
- * serialization. `[Output] extends [undefined]` is tuple-wrapped so a union
579
- * `Output` doesn't distribute and the test is for the exact sentinel.
580
- *
581
- * The terminal `.handler()` yields a {@link LunoraRouteHandler} — mount it
582
- * directly with `app.get(path, route)`.
583
- */
571
+ * A typed REST route under construction. `.searchParams()` / `.body()` /
572
+ * `.params()` accumulate validator maps (later calls merge, a colliding key
573
+ * wins) that decode the URL query, JSON body, and hono path params into the
574
+ * handler's typed `searchParams` / `body` / `params`. Like the procedure
575
+ * builder, `.output(validator)` defaults to the `undefined` sentinel — while
576
+ * unset the handler is generic over its own return; once set the handler must
577
+ * return that type and the result is parsed through the validator before
578
+ * serialization. `[Output] extends [undefined]` is tuple-wrapped so a union
579
+ * `Output` doesn't distribute and the test is for the exact sentinel.
580
+ *
581
+ * The terminal `.handler()` yields a {@link LunoraRouteHandler} — mount it
582
+ * directly with `app.get(path, route)`.
583
+ */
584
584
  interface HttpRouteBuilder<SearchParams extends ArgsValidator, Body extends ArgsValidator, Params extends ArgsValidator, Output = undefined> {
585
585
  body: <B extends ArgsValidator>(validators: B) => HttpRouteBuilder<SearchParams, B & Body, Params, Output>;
586
586
  /**
587
- * Attach a `Cache-Control` header to the response. Only meaningful when
588
- * Workers Cache is enabled in `wrangler.jsonc` (`"cache": { "enabled": true }`).
589
- */
587
+ * Attach a `Cache-Control` header to the response. Only meaningful when
588
+ * Workers Cache is enabled in `wrangler.jsonc` (`"cache": { "enabled": true }`).
589
+ */
590
590
  cacheControl: (value: string) => HttpRouteBuilder<SearchParams, Body, Params, Output>;
591
591
  /**
592
- * Attach a `Cache-Tag` header to the response for tag-based purging via
593
- * `ctx.cache.purge({ tags: [...] })`.
594
- */
592
+ * Attach a `Cache-Tag` header to the response for tag-based purging via
593
+ * `ctx.cache.purge({ tags: [...] })`.
594
+ */
595
595
  cacheTag: (value: string) => HttpRouteBuilder<SearchParams, Body, Params, Output>;
596
596
  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;
597
597
  output: <V extends Validator>(validator: V) => HttpRouteBuilder<SearchParams, Body, Params, Infer<V>>;
598
598
  params: <P extends ArgsValidator>(validators: P) => HttpRouteBuilder<SearchParams, Body, P & Params, Output>;
599
599
  searchParams: <S extends ArgsValidator>(validators: S) => HttpRouteBuilder<S & SearchParams, Body, Params, Output>;
600
600
  /**
601
- * Terminal: declare this route as a streaming Server-Sent Events endpoint.
602
- * The handler is an async generator (or any function returning an
603
- * `AsyncIterable&lt;R>`) that yields one chunk per SSE `data:` frame; on
604
- * iterator completion the route writes a final `event: complete` frame; on
605
- * throw, an `event: error` frame is written with `{code, message}` before
606
- * the stream closes. The chunks are JSON-encoded; `R` is inferred from the
607
- * handler's yielded type.
608
- * @experimental Reconnect/POST-body/wire-fidelity design questions are still open, so the shape may change.
609
- */
601
+ * Terminal: declare this route as a streaming Server-Sent Events endpoint.
602
+ * The handler is an async generator (or any function returning an
603
+ * `AsyncIterable&lt;R>`) that yields one chunk per SSE `data:` frame; on
604
+ * iterator completion the route writes a final `event: complete` frame; on
605
+ * throw, an `event: error` frame is written with `{code, message}` before
606
+ * the stream closes. The chunks are JSON-encoded; `R` is inferred from the
607
+ * handler's yielded type.
608
+ * @experimental Reconnect/POST-body/wire-fidelity design questions are still open, so the shape may change.
609
+ */
610
610
  stream: <R>(handler: (options: HttpStreamHandlerOptions<SearchParams, Params>) => AsyncGenerator<R, void, void> | AsyncIterable<R>) => LunoraRouteHandler;
611
611
  /**
612
- * Attach a `Vary` header to the response so Cloudflare stores separate
613
- * cached variants per distinct value of the listed request headers.
614
- */
612
+ * Attach a `Vary` header to the response so Cloudflare stores separate
613
+ * cached variants per distinct value of the listed request headers.
614
+ */
615
615
  vary: (value: string) => HttpRouteBuilder<SearchParams, Body, Params, Output>;
616
616
  }
617
617
  /** Opens a fresh {@link HttpRouteBuilder}. The `path` documents intent; hono owns the actual routing at mount. */
@@ -627,33 +627,33 @@ interface HttpRoute {
627
627
  put: HttpRouteFactory;
628
628
  }
629
629
  /**
630
- * Typed REST route builder. Compiles down to a {@link LunoraRouteHandler}, so a
631
- * typed route and a hand-written {@link httpAction} are interchangeable when
632
- * mounted on {@link httpRouter}:
633
- *
634
- * ```ts
635
- * export const listTodos = httpRoute
636
- * .get("/api/todos")
637
- * .searchParams({ limit: v.number(), q: v.optional(v.string()) })
638
- * .output(v.array(v.object({ id: v.string(), text: v.string() })))
639
- * .handler(async ({ ctx, searchParams }) => ctx.runQuery(api.todos.list, searchParams));
640
- *
641
- * export const getTodo = httpRoute
642
- * .get("/api/todos/:id")
643
- * .params({ id: v.string() })
644
- * .handler(async ({ ctx, params }) => ctx.runQuery(api.todos.get, params));
645
- *
646
- * const app = httpRouter();
647
- * app.get("/api/todos", listTodos);
648
- * app.get("/api/todos/:id", getTodo);
649
- * ```
650
- */
630
+ * Typed REST route builder. Compiles down to a {@link LunoraRouteHandler}, so a
631
+ * typed route and a hand-written {@link httpAction} are interchangeable when
632
+ * mounted on {@link httpRouter}:
633
+ *
634
+ * ```ts
635
+ * export const listTodos = httpRoute
636
+ * .get("/api/todos")
637
+ * .searchParams({ limit: v.number(), q: v.optional(v.string()) })
638
+ * .output(v.array(v.object({ id: v.string(), text: v.string() })))
639
+ * .handler(async ({ ctx, searchParams }) => ctx.runQuery(api.todos.list, searchParams));
640
+ *
641
+ * export const getTodo = httpRoute
642
+ * .get("/api/todos/:id")
643
+ * .params({ id: v.string() })
644
+ * .handler(async ({ ctx, params }) => ctx.runQuery(api.todos.get, params));
645
+ *
646
+ * const app = httpRouter();
647
+ * app.get("/api/todos", listTodos);
648
+ * app.get("/api/todos/:id", getTodo);
649
+ * ```
650
+ */
651
651
  declare const httpRoute: HttpRoute;
652
652
  /**
653
- * Structural view of an R2 object body, as returned by `@lunora/storage`'s
654
- * `download()`. Re-declared here (not imported) so `@lunora/server` takes no
655
- * runtime dependency on `@lunora/storage`; the real binding satisfies the shape.
656
- */
653
+ * Structural view of an R2 object body, as returned by `@lunora/storage`'s
654
+ * `download()`. Re-declared here (not imported) so `@lunora/server` takes no
655
+ * runtime dependency on `@lunora/storage`; the real binding satisfies the shape.
656
+ */
657
657
  interface StorageObjectBody {
658
658
  /** The object body stream (`null` for a zero-byte object). */
659
659
  body: ReadableStream | null;
@@ -684,46 +684,46 @@ interface ContextWithStorage {
684
684
  storage: StorageDownloader;
685
685
  }
686
686
  /**
687
- * True when `value` is safe to use as an HTTP header field-value: no CR, LF, or
688
- * NUL. Guards against response-header injection / `Headers`-construction throws
689
- * when reflecting attacker-influenced object metadata (e.g. a stored
690
- * `Content-Type`). Exported (see the `export {}` at the file end) so an `httpAction`
691
- * handler can guard a request-derived header value before writing it — the fix the
692
- * `http_action_response_header_injection` advisor lint points to.
693
- */
687
+ * True when `value` is safe to use as an HTTP header field-value: no CR, LF, or
688
+ * NUL. Guards against response-header injection / `Headers`-construction throws
689
+ * when reflecting attacker-influenced object metadata (e.g. a stored
690
+ * `Content-Type`). Exported (see the `export {}` at the file end) so an `httpAction`
691
+ * handler can guard a request-derived header value before writing it — the fix the
692
+ * `http_action_response_header_injection` advisor lint points to.
693
+ */
694
694
  declare const isSafeHeaderValue: (value: string) => boolean;
695
695
  /**
696
- * Stream a stored object as an HTTP {@link Response} from an `httpAction`
697
- * handler, with correct `Content-Type`, `ETag`, and `Accept-Ranges: bytes`.
698
- * Honors a single-range `Range` request → **206 Partial Content** with
699
- * `Content-Range` + `Content-Length`; otherwise **200**. A missing object is a
700
- * **404**; an out-of-bounds range is a **416** with a `Content-Range` of
701
- * `bytes` star-slash-size.
702
- *
703
- * A range request re-issues the `download()` with the resolved `{ offset, length }`
704
- * window so R2 streams only those bytes back to the Worker — the slice is never
705
- * buffered in the isolate. The first `download()` is used only for the object's
706
- * size + metadata (its body is left unread and cancelled). For very large
707
- * objects a signed URL (`ctx.storage.getSignedUrl`) is still cheaper since the
708
- * client then ranges against R2/CDN directly with no Worker hop.
709
- */
696
+ * Stream a stored object as an HTTP {@link Response} from an `httpAction`
697
+ * handler, with correct `Content-Type`, `ETag`, and `Accept-Ranges: bytes`.
698
+ * Honors a single-range `Range` request → **206 Partial Content** with
699
+ * `Content-Range` + `Content-Length`; otherwise **200**. A missing object is a
700
+ * **404**; an out-of-bounds range is a **416** with a `Content-Range` of
701
+ * `bytes` star-slash-size.
702
+ *
703
+ * A range request re-issues the `download()` with the resolved `{ offset, length }`
704
+ * window so R2 streams only those bytes back to the Worker — the slice is never
705
+ * buffered in the isolate. The first `download()` is used only for the object's
706
+ * size + metadata (its body is left unread and cancelled). For very large
707
+ * objects a signed URL (`ctx.storage.getSignedUrl`) is still cheaper since the
708
+ * client then ranges against R2/CDN directly with no Worker hop.
709
+ */
710
710
  declare const serveStorageObject: (context: ContextWithStorage, key: string, request: Request) => Promise<Response>;
711
711
  /**
712
- * What the worker does with a resolver's identity when it fails contract
713
- * validation (a forged / malformed claim set arriving from an untrusted token).
714
- * `"anonymous"` (default, safe) treats the request as anonymous, so the bad
715
- * identity never reaches a policy as a valid identity (`ctx.auth.userId`
716
- * becomes `undefined`). `"reject"` fails the request closed (a `401`) — use
717
- * when a malformed credential should be a hard error, not a silent downgrade.
718
- */
712
+ * What the worker does with a resolver's identity when it fails contract
713
+ * validation (a forged / malformed claim set arriving from an untrusted token).
714
+ * `"anonymous"` (default, safe) treats the request as anonymous, so the bad
715
+ * identity never reaches a policy as a valid identity (`ctx.auth.userId`
716
+ * becomes `undefined`). `"reject"` fails the request closed (a `401`) — use
717
+ * when a malformed credential should be a hard error, not a silent downgrade.
718
+ */
719
719
  type IdentityRejectMode = "anonymous" | "reject";
720
720
  /** Options for {@link defineIdentity}. */
721
721
  interface DefineIdentityOptions {
722
722
  /**
723
- * How to handle a resolver identity that violates the contract at the trust
724
- * boundary. Defaults to `"anonymous"` (a forged claim set is downgraded to
725
- * anonymous rather than flowing in as an unchecked cast).
726
- */
723
+ * How to handle a resolver identity that violates the contract at the trust
724
+ * boundary. Defaults to `"anonymous"` (a forged claim set is downgraded to
725
+ * anonymous rather than flowing in as an unchecked cast).
726
+ */
727
727
  readonly onInvalid?: IdentityRejectMode;
728
728
  }
729
729
  /** Result of validating a candidate identity against the contract. */
@@ -734,21 +734,21 @@ type IdentityValidation = {
734
734
  ok: false;
735
735
  };
736
736
  /**
737
- * A declared identity claim contract. Carries the codegen discovery brand, the
738
- * declared claim validators, the reject policy, and a runtime `validate`. The
739
- * `TClaims` type parameter is the inferred claim shape (always extending
740
- * `{ userId: string }`); it is phantom (no runtime field) and exists so
741
- * `@lunora/codegen` and {@link InferIdentity} can recover the type.
742
- */
737
+ * A declared identity claim contract. Carries the codegen discovery brand, the
738
+ * declared claim validators, the reject policy, and a runtime `validate`. The
739
+ * `TClaims` type parameter is the inferred claim shape (always extending
740
+ * `{ userId: string }`); it is phantom (no runtime field) and exists so
741
+ * `@lunora/codegen` and {@link InferIdentity} can recover the type.
742
+ */
743
743
  interface IdentityContract<TClaims extends {
744
744
  userId: string;
745
745
  } = {
746
746
  userId: string;
747
747
  }> {
748
748
  /**
749
- * Phantom carrier for the inferred claim type. Never populated at runtime
750
- * (`undefined`); present only so the type flows to codegen / {@link InferIdentity}.
751
- */
749
+ * Phantom carrier for the inferred claim type. Never populated at runtime
750
+ * (`undefined`); present only so the type flows to codegen / {@link InferIdentity}.
751
+ */
752
752
  readonly __claimType?: TClaims;
753
753
  readonly __lunoraIdentity: true;
754
754
  /** The declared claim validators (a `@lunora/values` validator map). */
@@ -756,23 +756,23 @@ interface IdentityContract<TClaims extends {
756
756
  /** Reject policy applied at the trust boundary. See {@link IdentityRejectMode}. */
757
757
  readonly onInvalid: IdentityRejectMode;
758
758
  /**
759
- * Validate a resolver's returned identity against the declared claims. On
760
- * success the caller keeps the original identity untouched (so undeclared
761
- * claims are forwarded verbatim, preserving today's behaviour); on failure
762
- * the worker applies the `onInvalid` policy.
763
- */
759
+ * Validate a resolver's returned identity against the declared claims. On
760
+ * success the caller keeps the original identity untouched (so undeclared
761
+ * claims are forwarded verbatim, preserving today's behaviour); on failure
762
+ * the worker applies the `onInvalid` policy.
763
+ */
764
764
  validate: (identity: Record<string, unknown>) => IdentityValidation;
765
765
  }
766
766
  /** Recover the declared claim type from a {@link defineIdentity} contract. */
767
767
  type InferIdentity<T> = T extends IdentityContract<infer TClaims> ? TClaims : never;
768
768
  /**
769
- * Declare the identity claim contract. `claims` is a `@lunora/values` validator
770
- * map whose inferred type must extend `{ userId: string }` — if it does not
771
- * (e.g. `userId` is missing or not a required string), the argument type
772
- * collapses to `never` and the call fails to typecheck.
773
- * @example
774
- * export const identity = defineIdentity({ userId: v.string(), tenantId: v.optional(v.string()), scopes: v.optional(v.array(v.string())) });
775
- */
769
+ * Declare the identity claim contract. `claims` is a `@lunora/values` validator
770
+ * map whose inferred type must extend `{ userId: string }` — if it does not
771
+ * (e.g. `userId` is missing or not a required string), the argument type
772
+ * collapses to `never` and the call fails to typecheck.
773
+ * @example
774
+ * export const identity = defineIdentity({ userId: v.string(), tenantId: v.optional(v.string()), scopes: v.optional(v.array(v.string())) });
775
+ */
776
776
  declare const defineIdentity: <A extends ValidatorMap>(claims: InferValidatorMap<A> extends {
777
777
  userId: string;
778
778
  } ? A : never, options?: DefineIdentityOptions) => IdentityContract<InferValidatorMap<A> & {
@@ -785,14 +785,14 @@ declare const onConnect: (handler: LifecycleHandler) => RegisteredLifecycleHook;
785
785
  /** Register a hook that fires once when a client's WebSocket disconnects. */
786
786
  declare const onDisconnect: (handler: LifecycleHandler) => RegisteredLifecycleHook;
787
787
  /**
788
- * Context handed to a {@link MaskFn} (and to {@link MaskOptions.bypass}). The
789
- * `auth` shape mirrors RLS's `PolicyContext.auth` one-for-one — same identity
790
- * resolver, same `can(...)` permission check — so an author can branch a mask
791
- * on the caller's role/permission. `row` is the full pre-mask row the column
792
- * belongs to; `column` is the column currently being masked. Both are absent
793
- * when the context is used for the procedure-wide `bypass` check (no specific
794
- * cell is in play yet).
795
- */
788
+ * Context handed to a {@link MaskFn} (and to {@link MaskOptions.bypass}). The
789
+ * `auth` shape mirrors RLS's `PolicyContext.auth` one-for-one — same identity
790
+ * resolver, same `can(...)` permission check — so an author can branch a mask
791
+ * on the caller's role/permission. `row` is the full pre-mask row the column
792
+ * belongs to; `column` is the column currently being masked. Both are absent
793
+ * when the context is used for the procedure-wide `bypass` check (no specific
794
+ * cell is in play yet).
795
+ */
796
796
  interface MaskContext<Context = unknown> {
797
797
  readonly auth: {
798
798
  /** `true` when any of the request's `roles` grants `permission` (see {@link MaskOptions.roles}). Fails closed for unregistered roles. */
@@ -808,50 +808,50 @@ interface MaskContext<Context = unknown> {
808
808
  readonly row?: Record<string, unknown>;
809
809
  }
810
810
  /**
811
- * A custom masking function. Receives the raw cell value and the
812
- * {@link MaskContext}, returns the value to surface. Use it for partial masks
813
- * (`maskMiddle(phone)`), role-aware reveals (`ctx.auth.can(...) ? value : null`),
814
- * or format-preserving tokens. A function that **throws** fails closed — the
815
- * cell is redacted to `null`, never leaked raw.
816
- */
811
+ * A custom masking function. Receives the raw cell value and the
812
+ * {@link MaskContext}, returns the value to surface. Use it for partial masks
813
+ * (`maskMiddle(phone)`), role-aware reveals (`ctx.auth.can(...) ? value : null`),
814
+ * or format-preserving tokens. A function that **throws** fails closed — the
815
+ * cell is redacted to `null`, never leaked raw.
816
+ */
817
817
  type MaskFn<Context = unknown> = (value: unknown, context: MaskContext<Context>) => unknown;
818
818
  /**
819
- * How a column is masked:
820
- *
821
- * - `"redact"` — drop the value to `null`. The simplest, safest strategy, and
822
- * the right choice for any value that must actually be kept secret.
823
- * - `"hash"` — replace with a stable token (unsalted 32-bit FNV-1a hex) so the
824
- * same input always yields the same token (joinable/groupable client-side).
825
- * **This is NOT a confidentiality control.** It is a non-cryptographic,
826
- * unsalted, deterministic, narrow (~2^32) digest: low-entropy values (emails,
827
- * phone numbers, SSNs) are brute-force-recoverable by the very caller you are
828
- * masking from, and identical values always produce identical tokens across
829
- * rows/columns/tenants (enabling correlation). Use `"hash"` ONLY when you want a
830
- * stable pseudonym for grouping/joining and leaking the value is acceptable —
831
- * never to hide sensitive PII. For PII that must stay hidden, use `"redact"`.
832
- * - a {@link MaskFn} — author-defined transform (partial mask, role-aware reveal).
833
- */
819
+ * How a column is masked:
820
+ *
821
+ * - `"redact"` — drop the value to `null`. The simplest, safest strategy, and
822
+ * the right choice for any value that must actually be kept secret.
823
+ * - `"hash"` — replace with a stable token (unsalted 32-bit FNV-1a hex) so the
824
+ * same input always yields the same token (joinable/groupable client-side).
825
+ * **This is NOT a confidentiality control.** It is a non-cryptographic,
826
+ * unsalted, deterministic, narrow (~2^32) digest: low-entropy values (emails,
827
+ * phone numbers, SSNs) are brute-force-recoverable by the very caller you are
828
+ * masking from, and identical values always produce identical tokens across
829
+ * rows/columns/tenants (enabling correlation). Use `"hash"` ONLY when you want a
830
+ * stable pseudonym for grouping/joining and leaking the value is acceptable —
831
+ * never to hide sensitive PII. For PII that must stay hidden, use `"redact"`.
832
+ * - a {@link MaskFn} — author-defined transform (partial mask, role-aware reveal).
833
+ */
834
834
  type MaskStrategy<Context = unknown> = "hash" | "redact" | MaskFn<Context>;
835
835
  /** Per-column strategy map for one table: `{ email: "redact", phone: maskMiddle }`. */
836
836
  type MaskColumns<Context = unknown> = Record<string, MaskStrategy<Context>>;
837
837
  /**
838
- * The mask declaration passed to `mask(...)`: a table → column → strategy map.
839
- * Deliberately a plain object literal so the codegen feeder can statically read
840
- * which columns a procedure masks (powering the `mask_uncovered_pii_column`
841
- * advisor lint), exactly as the RLS feeder reads policy tables.
842
- */
838
+ * The mask declaration passed to `mask(...)`: a table → column → strategy map.
839
+ * Deliberately a plain object literal so the codegen feeder can statically read
840
+ * which columns a procedure masks (powering the `mask_uncovered_pii_column`
841
+ * advisor lint), exactly as the RLS feeder reads policy tables.
842
+ */
843
843
  type MaskPolicies<Context = unknown> = Record<string, MaskColumns<Context>>;
844
844
  /**
845
- * Options for `mask(policies, options)`.
846
- *
847
- * - `roles` registers the role→permission grants that back `ctx.auth.can(...)`
848
- * inside a {@link MaskFn} — identical to `rls(policies, { roles })`. A role
849
- * not listed grants no permissions (fails closed for unknown roles).
850
- * - `bypass` is a procedure-wide escape hatch: when it returns `true` the whole
851
- * mask is skipped (the caller sees raw values). Use it for a privileged
852
- * viewer — `bypass: ({ auth }) => auth.can("pii:view")`. Prefer this over
853
- * branching every column when an entire class of caller should see clear data.
854
- */
845
+ * Options for `mask(policies, options)`.
846
+ *
847
+ * - `roles` registers the role→permission grants that back `ctx.auth.can(...)`
848
+ * inside a {@link MaskFn} — identical to `rls(policies, { roles })`. A role
849
+ * not listed grants no permissions (fails closed for unknown roles).
850
+ * - `bypass` is a procedure-wide escape hatch: when it returns `true` the whole
851
+ * mask is skipped (the caller sees raw values). Use it for a privileged
852
+ * viewer — `bypass: ({ auth }) => auth.can("pii:view")`. Prefer this over
853
+ * branching every column when an entire class of caller should see clear data.
854
+ */
855
855
  interface MaskOptions<Context = unknown> {
856
856
  readonly bypass?: (context: MaskContext<Context>) => boolean;
857
857
  readonly roles?: ReadonlyArray<Role>;
@@ -897,11 +897,11 @@ interface TableReaderLike$1 {
897
897
  withSearchIndex: (indexName: string, search: (q: unknown) => unknown) => TableReaderLike$1;
898
898
  }
899
899
  /**
900
- * Structural projection of the runtime ORM writer — the same subset
901
- * `../rls/middleware` mirrors, so the wrapper is interchangeable between
902
- * `@lunora/do`'s and `@lunora/d1`'s `DatabaseWriterLike` without an
903
- * inter-package dependency. `rankBefore` is optional (the D1 twin omits it).
904
- */
900
+ * Structural projection of the runtime ORM writer — the same subset
901
+ * `../rls/middleware` mirrors, so the wrapper is interchangeable between
902
+ * `@lunora/do`'s and `@lunora/d1`'s `DatabaseWriterLike` without an
903
+ * inter-package dependency. `rankBefore` is optional (the D1 twin omits it).
904
+ */
905
905
  interface MaskDatabase {
906
906
  aggregate: (tableName: string, options: AggregateArgs$1) => Promise<null | number>;
907
907
  count: (tableName: string, whereOrArgs?: unknown) => Promise<number>;
@@ -973,22 +973,22 @@ interface MaskContextIn {
973
973
  db: MaskDatabase;
974
974
  }
975
975
  /**
976
- * Procedure-builder middleware. Apply per-request via `.use(mask(policies))`.
977
- * Closes over the policy map at builder-construction time; resolves identity +
978
- * the `bypass` decision per call against the live ctx.
979
- *
980
- * IMPORTANT: a mask is in scope only for procedures whose builder chain
981
- * includes this middleware — opt-in, never global (the same invariant as RLS).
982
- */
976
+ * Procedure-builder middleware. Apply per-request via `.use(mask(policies))`.
977
+ * Closes over the policy map at builder-construction time; resolves identity +
978
+ * the `bypass` decision per call against the live ctx.
979
+ *
980
+ * IMPORTANT: a mask is in scope only for procedures whose builder chain
981
+ * includes this middleware — opt-in, never global (the same invariant as RLS).
982
+ */
983
983
  declare const mask: <Context extends MaskContextIn = MaskContextIn>(policies: MaskPolicies<Context>, options?: MaskOptions<Context>) => Middleware<Context, Context>;
984
984
  /** A document handed to a migration transform: the stored row including `_id`/`_creationTime`. */
985
985
  type MigrationDocument = Record<string, unknown>;
986
986
  /**
987
- * Transform applied to one document. Return a new document to rewrite the row,
988
- * or `undefined` to leave it untouched (skipped, not counted as changed). The
989
- * runner always preserves the original `_id` and `_creationTime`, so the
990
- * returned document neither needs to nor should change row identity.
991
- */
987
+ * Transform applied to one document. Return a new document to rewrite the row,
988
+ * or `undefined` to leave it untouched (skipped, not counted as changed). The
989
+ * runner always preserves the original `_id` and `_creationTime`, so the
990
+ * returned document neither needs to nor should change row identity.
991
+ */
992
992
  type MigrationTransform = (document: MigrationDocument) => MigrationDocument | undefined | void;
993
993
  interface MigrationDefinition {
994
994
  /** Rows fetched and rewritten per batch. Defaults to the runner's batch size when omitted. */
@@ -1009,132 +1009,132 @@ interface RegisteredMigration extends MigrationDefinition {
1009
1009
  /** Declare an online data migration. See the module docs for runtime semantics. */
1010
1010
  declare const defineMigration: (definition: MigrationDefinition) => RegisteredMigration;
1011
1011
  /**
1012
- * A mutator declaration. `server` is authoritative; `client` is the optimistic
1013
- * twin (optional — omit it to let the optimistic write fall through to the
1014
- * server round-trip with no local preview). Both receive the same validated
1015
- * `args`.
1016
- */
1012
+ * A mutator declaration. `server` is authoritative; `client` is the optimistic
1013
+ * twin (optional — omit it to let the optimistic write fall through to the
1014
+ * server round-trip with no local preview). Both receive the same validated
1015
+ * `args`.
1016
+ */
1017
1017
  interface MutatorDefinition<Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown> {
1018
1018
  /**
1019
- * Validator for the mutator's arguments. Validated on the DO before `server`
1020
- * runs and (when present) on the client before `client` runs, so both impls
1021
- * see the same parsed shape. Omit for a parameterless mutator.
1022
- */
1019
+ * Validator for the mutator's arguments. Validated on the DO before `server`
1020
+ * runs and (when present) on the client before `client` runs, so both impls
1021
+ * see the same parsed shape. Omit for a parameterless mutator.
1022
+ */
1023
1023
  readonly args?: Args;
1024
1024
  /**
1025
- * Optimistic client implementation. Runs in a TanStack DB transaction
1026
- * against the local collections; its writes are applied immediately and
1027
- * automatically rolled back / rebased as the authoritative result syncs
1028
- * back. Pure and side-effect-free beyond the local store. Omit to skip the
1029
- * local preview.
1030
- */
1025
+ * Optimistic client implementation. Runs in a TanStack DB transaction
1026
+ * against the local collections; its writes are applied immediately and
1027
+ * automatically rolled back / rebased as the authoritative result syncs
1028
+ * back. Pure and side-effect-free beyond the local store. Omit to skip the
1029
+ * local preview.
1030
+ */
1031
1031
  readonly client?: (tx: ClientTx, args: InferValidatorMap<Args>) => Promise<void> | void;
1032
1032
  /**
1033
- * Authoritative server implementation. Runs inside the shard DO with a full
1034
- * {@link MutationContext} (`ctx.db` writer); its writes append to `__cdc_log`
1035
- * and poke back to subscribers. This is the source of truth — the client
1036
- * impl is only a prediction of it.
1037
- */
1033
+ * Authoritative server implementation. Runs inside the shard DO with a full
1034
+ * {@link MutationContext} (`ctx.db` writer); its writes append to `__cdc_log`
1035
+ * and poke back to subscribers. This is the source of truth — the client
1036
+ * impl is only a prediction of it.
1037
+ */
1038
1038
  readonly server: (context: ServerContext, args: InferValidatorMap<Args>) => Promise<R> | R;
1039
1039
  }
1040
1040
  /**
1041
- * A {@link MutatorDefinition} plus the codegen discovery marker and a
1042
- * dispatch-shaped `handler` (validates `args`, then runs `server`) so the DO
1043
- * invokes a mutator exactly like a registered procedure.
1044
- */
1041
+ * A {@link MutatorDefinition} plus the codegen discovery marker and a
1042
+ * dispatch-shaped `handler` (validates `args`, then runs `server`) so the DO
1043
+ * invokes a mutator exactly like a registered procedure.
1044
+ */
1045
1045
  interface RegisteredMutator<Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown> extends MutatorDefinition<Args, ServerContext, ClientTx, R> {
1046
1046
  readonly __lunoraMutator: true;
1047
1047
  /** Validate `rawArgs`, then run the authoritative `server` impl. Used by the DO push path. */
1048
1048
  readonly handler: (context: ServerContext, rawArgs: Record<string, unknown>) => Promise<R>;
1049
1049
  /**
1050
- * Marks the dispatch kind so codegen can register the mutator in the same
1051
- * `LUNORA_FUNCTIONS` table queries/mutations use — the DO's `handleRpc`
1052
- * reads `kind === "mutation"` to wrap the authoritative `server` impl in the
1053
- * shard's BEGIN/COMMIT span (all-or-nothing writes), exactly like an
1054
- * ordinary `mutation`.
1055
- */
1050
+ * Marks the dispatch kind so codegen can register the mutator in the same
1051
+ * `LUNORA_FUNCTIONS` table queries/mutations use — the DO's `handleRpc`
1052
+ * reads `kind === "mutation"` to wrap the authoritative `server` impl in the
1053
+ * shard's BEGIN/COMMIT span (all-or-nothing writes), exactly like an
1054
+ * ordinary `mutation`.
1055
+ */
1056
1056
  readonly kind: "mutation";
1057
1057
  }
1058
1058
  /** Declare a custom mutator. See the module docs for runtime semantics. */
1059
1059
  declare const defineMutator: <Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown>(definition: MutatorDefinition<Args, ServerContext, ClientTx, R>) => RegisteredMutator<Args, ServerContext, ClientTx, R>;
1060
1060
  /**
1061
- * The prefixed tables a single plugin `P` contributes, or an empty map when it
1062
- * ships no schema extension. Mirrors {@link PrefixedTables} at the plugin level
1063
- * so {@link InstalledTables} can fold a tuple of plugins.
1064
- */
1061
+ * The prefixed tables a single plugin `P` contributes, or an empty map when it
1062
+ * ships no schema extension. Mirrors {@link PrefixedTables} at the plugin level
1063
+ * so {@link InstalledTables} can fold a tuple of plugins.
1064
+ */
1065
1065
  type ExtensionTablesOf<P> = P extends {
1066
1066
  readonly extension: SchemaExtension<infer X> & {
1067
1067
  readonly key: infer K;
1068
1068
  };
1069
1069
  } ? K extends string ? PrefixedTables<X, K> : Record<never, never> : Record<never, never>;
1070
1070
  /**
1071
- * Fold a tuple of plugins onto a base table map `T`, accumulating each plugin's
1072
- * auto-prefixed extension tables left-to-right — the type-level mirror of
1073
- * {@link installPlugins} applying `mergeSchemaExtension` for each plugin in turn.
1074
- */
1071
+ * Fold a tuple of plugins onto a base table map `T`, accumulating each plugin's
1072
+ * auto-prefixed extension tables left-to-right — the type-level mirror of
1073
+ * {@link installPlugins} applying `mergeSchemaExtension` for each plugin in turn.
1074
+ */
1075
1075
  type InstalledTables<T extends Record<string, TableDefinition>, Plugins extends ReadonlyArray<unknown>> = Plugins extends readonly [infer Head, ...infer Rest] ? InstalledTables<ExtensionTablesOf<Head> & T, Rest> : T;
1076
1076
  /**
1077
- * Union every plugin's `ContextOut` in a tuple — the type-level mirror of the
1078
- * `ctx.api.&lt;key>` additions {@link composePluginMiddleware} accumulates as each
1079
- * plugin middleware runs. Independent of the incoming context, which the builder
1080
- * infers at the `.use(...)` site.
1081
- */
1077
+ * Union every plugin's `ContextOut` in a tuple — the type-level mirror of the
1078
+ * `ctx.api.&lt;key>` additions {@link composePluginMiddleware} accumulates as each
1079
+ * plugin middleware runs. Independent of the incoming context, which the builder
1080
+ * infers at the `.use(...)` site.
1081
+ */
1082
1082
  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;
1083
1083
  /**
1084
- * Schema fragment a plugin contributes. Same shape as the `tables` map
1085
- * passed to `defineSchema`. Optional `vectorIndexes` mirror the top-level
1086
- * `defineSchema` argument so a plugin can ship vector decls alongside its
1087
- * tables.
1088
- */
1084
+ * Schema fragment a plugin contributes. Same shape as the `tables` map
1085
+ * passed to `defineSchema`. Optional `vectorIndexes` mirror the top-level
1086
+ * `defineSchema` argument so a plugin can ship vector decls alongside its
1087
+ * tables.
1088
+ */
1089
1089
  interface SchemaExtension<T extends Record<string, TableDefinition> = Record<string, TableDefinition>> {
1090
1090
  /** Stable key identifying the plugin that owns this extension. */
1091
1091
  readonly key: string;
1092
1092
  /**
1093
- * Extension tables, keyed by **bare** name (e.g. `buckets`). At merge time
1094
- * each is auto-prefixed with `key` (`ratelimit_buckets`) so it can't
1095
- * collide with an app table; do **not** namespace manually.
1096
- */
1093
+ * Extension tables, keyed by **bare** name (e.g. `buckets`). At merge time
1094
+ * each is auto-prefixed with `key` (`ratelimit_buckets`) so it can't
1095
+ * collide with an app table; do **not** namespace manually.
1096
+ */
1097
1097
  readonly tables: T;
1098
1098
  /**
1099
- * Optional standalone vector indexes the plugin ships, keyed by index
1100
- * name. Merged into the host schema's `vectorIndexes`; a key collision
1101
- * with the base schema is a hard error (same policy as tables).
1102
- */
1099
+ * Optional standalone vector indexes the plugin ships, keyed by index
1100
+ * name. Merged into the host schema's `vectorIndexes`; a key collision
1101
+ * with the base schema is a hard error (same policy as tables).
1102
+ */
1103
1103
  readonly vectorIndexes?: Record<string, VectorIndexDefinition>;
1104
1104
  }
1105
1105
  /**
1106
- * Build a {@link SchemaExtension}. The `key` is a runtime tag (used for
1107
- * error messages on collision) and a type-level brand.
1108
- */
1106
+ * Build a {@link SchemaExtension}. The `key` is a runtime tag (used for
1107
+ * error messages on collision) and a type-level brand.
1108
+ */
1109
1109
  declare const defineSchemaExtension: <T extends Record<string, TableDefinition>>(key: string, options: {
1110
1110
  tables: T;
1111
1111
  vectorIndexes?: Record<string, VectorIndexDefinition>;
1112
1112
  }) => SchemaExtension<T>;
1113
1113
  /**
1114
- * A plugin packages an optional schema extension and optional middleware.
1115
- * Both are independently usable: an app can install only the schema (e.g.
1116
- * for plugins that ship background workers but no per-request behavior)
1117
- * or only the middleware (plugins that augment ctx without persistent
1118
- * state).
1119
- */
1114
+ * A plugin packages an optional schema extension and optional middleware.
1115
+ * Both are independently usable: an app can install only the schema (e.g.
1116
+ * for plugins that ship background workers but no per-request behavior)
1117
+ * or only the middleware (plugins that augment ctx without persistent
1118
+ * state).
1119
+ */
1120
1120
  interface Plugin<TExtension extends Record<string, TableDefinition> = Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn> {
1121
1121
  /**
1122
- * Optional schema extension. Apps install via
1123
- * `defineSchema(...).extend(plugin.extension)`.
1124
- */
1122
+ * Optional schema extension. Apps install via
1123
+ * `defineSchema(...).extend(plugin.extension)`.
1124
+ */
1125
1125
  readonly extension?: SchemaExtension<TExtension>;
1126
1126
  /** Stable key identifying the plugin. Matches `extension.key` when set. */
1127
1127
  readonly key: string;
1128
1128
  /**
1129
- * Optional middleware. Users attach with `c.query.use(plugin.middleware)`.
1130
- * The middleware can extend `ctx`; convention is to attach helpers under
1131
- * `ctx.api.&lt;key>`, e.g.
1132
- *
1133
- * ```ts
1134
- * middleware: ({ ctx, next }) =>
1135
- * next({ ctx: { api: { ...ctx.api, ratelimit: api } } })
1136
- * ```
1137
- */
1129
+ * Optional middleware. Users attach with `c.query.use(plugin.middleware)`.
1130
+ * The middleware can extend `ctx`; convention is to attach helpers under
1131
+ * `ctx.api.&lt;key>`, e.g.
1132
+ *
1133
+ * ```ts
1134
+ * middleware: ({ ctx, next }) =>
1135
+ * next({ ctx: { api: { ...ctx.api, ratelimit: api } } })
1136
+ * ```
1137
+ */
1138
1138
  readonly middleware?: Middleware<TContextIn, TContextOut>;
1139
1139
  }
1140
1140
  /** Options to {@link definePlugin}. */
@@ -1143,13 +1143,13 @@ interface DefinePluginOptions<TExtension extends Record<string, TableDefinition>
1143
1143
  middleware?: Middleware<TContextIn, TContextOut>;
1144
1144
  }
1145
1145
  /**
1146
- * Call signatures for {@link definePlugin}. When `extension` is supplied the
1147
- * returned plugin's `extension` is typed as PRESENT (not `?`), so the
1148
- * canonical install pattern `defineSchema(...).extend(plugin.extension)`
1149
- * typechecks without a non-null assertion — the shape every scaffold template
1150
- * ships. The bare-options signature keeps `extension` optional for plugins
1151
- * that carry only middleware.
1152
- */
1146
+ * Call signatures for {@link definePlugin}. When `extension` is supplied the
1147
+ * returned plugin's `extension` is typed as PRESENT (not `?`), so the
1148
+ * canonical install pattern `defineSchema(...).extend(plugin.extension)`
1149
+ * typechecks without a non-null assertion — the shape every scaffold template
1150
+ * ships. The bare-options signature keeps `extension` optional for plugins
1151
+ * that carry only middleware.
1152
+ */
1153
1153
  interface DefinePluginFunction {
1154
1154
  <TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut> & {
1155
1155
  extension: SchemaExtension<TExtension>;
@@ -1159,35 +1159,35 @@ interface DefinePluginFunction {
1159
1159
  <TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut>): Plugin<TExtension, TContextIn, TContextOut>;
1160
1160
  }
1161
1161
  /**
1162
- * Package a schema extension + middleware as a reusable plugin. Either
1163
- * field is optional — `definePlugin("foo", {})` is valid but degenerate.
1164
- */
1162
+ * Package a schema extension + middleware as a reusable plugin. Either
1163
+ * field is optional — `definePlugin("foo", {})` is valid but degenerate.
1164
+ */
1165
1165
  declare const definePlugin: DefinePluginFunction;
1166
1166
  /**
1167
- * Bundle of registered functions a {@link Component} ships. Keys are the
1168
- * function's local name (e.g. `check`, `reset`); the registered function
1169
- * value carries its own kind / args / handler.
1170
- *
1171
- * Users re-export from their own lunora module so codegen picks them up:
1172
- *
1173
- * ```ts
1174
- * // lunora/ratelimit.ts
1175
- * import { ratelimit } from "@vendor/ratelimit-component";
1176
- * export const { check, reset } = ratelimit.functions;
1177
- * // Emits as `ratelimit:check` / `ratelimit:reset` in the generated `api`.
1178
- * ```
1179
- *
1180
- * Codegen follows the re-export back to the bundled `query/mutation/action`
1181
- * call (property access or destructuring both work), so the functions land in
1182
- * the generated `api` under the re-exporting file's namespace.
1183
- */
1167
+ * Bundle of registered functions a {@link Component} ships. Keys are the
1168
+ * function's local name (e.g. `check`, `reset`); the registered function
1169
+ * value carries its own kind / args / handler.
1170
+ *
1171
+ * Users re-export from their own lunora module so codegen picks them up:
1172
+ *
1173
+ * ```ts
1174
+ * // lunora/ratelimit.ts
1175
+ * import { ratelimit } from "@vendor/ratelimit-component";
1176
+ * export const { check, reset } = ratelimit.functions;
1177
+ * // Emits as `ratelimit:check` / `ratelimit:reset` in the generated `api`.
1178
+ * ```
1179
+ *
1180
+ * Codegen follows the re-export back to the bundled `query/mutation/action`
1181
+ * call (property access or destructuring both work), so the functions land in
1182
+ * the generated `api` under the re-exporting file's namespace.
1183
+ */
1184
1184
  type ComponentFunctions = Readonly<Record<string, RegisteredFunction<any, any, FunctionKind>>>;
1185
1185
  /**
1186
- * Component = {@link Plugin} with a bundle of registered functions. The
1187
- * extension + middleware + functions are independent: a component can ship
1188
- * functions without a schema (e.g. a stateless utility), or a schema
1189
- * without functions (e.g. shared table definitions), and any combination.
1190
- */
1186
+ * Component = {@link Plugin} with a bundle of registered functions. The
1187
+ * extension + middleware + functions are independent: a component can ship
1188
+ * functions without a schema (e.g. a stateless utility), or a schema
1189
+ * without functions (e.g. shared table definitions), and any combination.
1190
+ */
1191
1191
  interface Component<TExtension extends Record<string, TableDefinition> = Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn, F extends ComponentFunctions = ComponentFunctions> extends Plugin<TExtension, TContextIn, TContextOut> {
1192
1192
  readonly functions: F;
1193
1193
  }
@@ -1196,81 +1196,81 @@ interface DefineComponentOptions<TExtension extends Record<string, TableDefiniti
1196
1196
  functions?: F;
1197
1197
  }
1198
1198
  /**
1199
- * Convenience wrapper around {@link definePlugin} that also bundles a set
1200
- * of registered functions. The resulting `component.functions` object is a
1201
- * record of `name → registered query/mutation/action`; consumers
1202
- * re-export entries so codegen discovers them as user functions:
1203
- *
1204
- * ```ts
1205
- * export const ratelimit = defineComponent("ratelimit", {
1206
- * // Bare `buckets` merges in as `ratelimit_buckets`.
1207
- * extension: defineSchemaExtension("ratelimit", { tables: { buckets } }),
1208
- * middleware: ({ ctx, next }) => next({ ctx: { ...ctx, ratelimit: api(ctx) } }),
1209
- * functions: {
1210
- * check: query.input({ key: v.string() }).query(async ({ ctx, args }) => ...),
1211
- * reset: mutation.input({ key: v.string() }).mutation(async ({ ctx, args }) => ...),
1212
- * },
1213
- * });
1214
- * ```
1215
- *
1216
- * Re-exporting an entry (by property access or destructuring) is enough for
1217
- * codegen to discover it in the host app's namespace — the discovery resolver
1218
- * chases the re-export back to the bundled registration call.
1219
- */
1199
+ * Convenience wrapper around {@link definePlugin} that also bundles a set
1200
+ * of registered functions. The resulting `component.functions` object is a
1201
+ * record of `name → registered query/mutation/action`; consumers
1202
+ * re-export entries so codegen discovers them as user functions:
1203
+ *
1204
+ * ```ts
1205
+ * export const ratelimit = defineComponent("ratelimit", {
1206
+ * // Bare `buckets` merges in as `ratelimit_buckets`.
1207
+ * extension: defineSchemaExtension("ratelimit", { tables: { buckets } }),
1208
+ * middleware: ({ ctx, next }) => next({ ctx: { ...ctx, ratelimit: api(ctx) } }),
1209
+ * functions: {
1210
+ * check: query.input({ key: v.string() }).query(async ({ ctx, args }) => ...),
1211
+ * reset: mutation.input({ key: v.string() }).mutation(async ({ ctx, args }) => ...),
1212
+ * },
1213
+ * });
1214
+ * ```
1215
+ *
1216
+ * Re-exporting an entry (by property access or destructuring) is enough for
1217
+ * codegen to discover it in the host app's namespace — the discovery resolver
1218
+ * chases the re-export back to the bundled registration call.
1219
+ */
1220
1220
  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>;
1221
1221
  /**
1222
- * Map every key `K` of an extension's table map `X` to its auto-prefixed name
1223
- * `${Key}_${K}`. Mirrors the runtime prefixing in {@link mergeSchemaExtension}
1224
- * so the typed `.extend(...)` chain reflects the real merged table names.
1225
- */
1226
- type PrefixedTables<X extends Record<string, TableDefinition>, Key extends string> = { [K in keyof X as K extends string ? `${Key}_${K}` : K]: X[K] };
1227
- /**
1228
- * Merge a {@link SchemaExtension} into an existing schema. Returns a new
1229
- * schema object — never mutates the input.
1230
- *
1231
- * Extension tables are auto-namespaced: each bare table name is prefixed with
1232
- * the extension `key` (`buckets` → `ratelimit_buckets`), Convex-Components
1233
- * style, and every intra-extension reference (relation targets, aggregate /
1234
- * rank index `on`, standalone vector index `table`) is rewritten to match.
1235
- * References to base/app tables are left untouched.
1236
- *
1237
- * Because each extension lives in its own `key` namespace, app↔component
1238
- * collisions are impossible. The only remaining hard error is two extensions
1239
- * sharing the same `key` and producing the same prefixed table (or vector
1240
- * index) name — silent shadow would let one plugin hijack another's data.
1241
- */
1222
+ * Map every key `K` of an extension's table map `X` to its auto-prefixed name
1223
+ * `${Key}_${K}`. Mirrors the runtime prefixing in {@link mergeSchemaExtension}
1224
+ * so the typed `.extend(...)` chain reflects the real merged table names.
1225
+ */
1226
+ type PrefixedTables<X extends Record<string, TableDefinition>, Key extends string> = { [K in keyof X as K extends string ? `${Key}_${K}` : K]: X[K]; };
1227
+ /**
1228
+ * Merge a {@link SchemaExtension} into an existing schema. Returns a new
1229
+ * schema object — never mutates the input.
1230
+ *
1231
+ * Extension tables are auto-namespaced: each bare table name is prefixed with
1232
+ * the extension `key` (`buckets` → `ratelimit_buckets`), Convex-Components
1233
+ * style, and every intra-extension reference (relation targets, aggregate /
1234
+ * rank index `on`, standalone vector index `table`) is rewritten to match.
1235
+ * References to base/app tables are left untouched.
1236
+ *
1237
+ * Because each extension lives in its own `key` namespace, app↔component
1238
+ * collisions are impossible. The only remaining hard error is two extensions
1239
+ * sharing the same `key` and producing the same prefixed table (or vector
1240
+ * index) name — silent shadow would let one plugin hijack another's data.
1241
+ */
1242
1242
  declare const mergeSchemaExtension: <T extends Record<string, TableDefinition>, X extends Record<string, TableDefinition>, Key extends string = string>(base: Schema<T>, extension: SchemaExtension<X> & {
1243
1243
  readonly key: Key;
1244
1244
  }) => Schema<PrefixedTables<X, Key> & T>;
1245
1245
  /**
1246
- * Install several plugins' schema extensions in one call — the one-shot
1247
- * counterpart to chaining `defineSchema(...).extend(a).extend(b)`. Plugins
1248
- * without an `extension` (middleware-only) are skipped; tables from those that
1249
- * do are auto-prefixed and reference-rewritten exactly as
1250
- * {@link mergeSchemaExtension} does for a single `.extend(...)`.
1251
- *
1252
- * ```ts
1253
- * const schema = installPlugins(defineSchema({ todos }), [ratelimit, audit]);
1254
- * // → todos + ratelimit_* + audit_*
1255
- * ```
1256
- *
1257
- * Pair it with {@link composePluginMiddleware} to attach every plugin's
1258
- * middleware in a single `.use(...)`, so installing N plugins is two calls
1259
- * rather than N `.extend(...)` + N `.use(...)`.
1260
- */
1246
+ * Install several plugins' schema extensions in one call — the one-shot
1247
+ * counterpart to chaining `defineSchema(...).extend(a).extend(b)`. Plugins
1248
+ * without an `extension` (middleware-only) are skipped; tables from those that
1249
+ * do are auto-prefixed and reference-rewritten exactly as
1250
+ * {@link mergeSchemaExtension} does for a single `.extend(...)`.
1251
+ *
1252
+ * ```ts
1253
+ * const schema = installPlugins(defineSchema({ todos }), [ratelimit, audit]);
1254
+ * // → todos + ratelimit_* + audit_*
1255
+ * ```
1256
+ *
1257
+ * Pair it with {@link composePluginMiddleware} to attach every plugin's
1258
+ * middleware in a single `.use(...)`, so installing N plugins is two calls
1259
+ * rather than N `.extend(...)` + N `.use(...)`.
1260
+ */
1261
1261
  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>>;
1262
1262
  /**
1263
- * Compose every plugin's middleware into a single middleware you attach with one
1264
- * `.use(...)`. Plugins without middleware (schema-only) are skipped; the rest run
1265
- * in array order, each seeing the context the previous one widened, so the final
1266
- * `next({ ctx })` the builder receives carries every plugin's `ctx.api.&lt;key>`
1267
- * additions. Equivalent to `.use(a.middleware).use(b.middleware)…` but as one
1268
- * value, the middleware sibling of {@link installPlugins}.
1269
- *
1270
- * `ContextIn` is left free so the builder infers it from the context at the
1271
- * `.use(...)` site; the result type widens it by the union of the plugins'
1272
- * outputs.
1273
- */
1263
+ * Compose every plugin's middleware into a single middleware you attach with one
1264
+ * `.use(...)`. Plugins without middleware (schema-only) are skipped; the rest run
1265
+ * in array order, each seeing the context the previous one widened, so the final
1266
+ * `next({ ctx })` the builder receives carries every plugin's `ctx.api.&lt;key>`
1267
+ * additions. Equivalent to `.use(a.middleware).use(b.middleware)…` but as one
1268
+ * value, the middleware sibling of {@link installPlugins}.
1269
+ *
1270
+ * `ContextIn` is left free so the builder infers it from the context at the
1271
+ * `.use(...)` site; the result type widens it by the union of the plugins'
1272
+ * outputs.
1273
+ */
1274
1274
  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>;
1275
1275
  /** Options for `.vectorize(field, opts)` (DSL Shape A). */
1276
1276
  interface VectorizeOptions<Shape extends Record<string, Validator> = Record<string, Validator>> {
@@ -1307,10 +1307,10 @@ interface RelationBuilder {
1307
1307
  }) => OneRelation<Target>;
1308
1308
  }
1309
1309
  /**
1310
- * Options for the inline `.aggregateIndex(name, opts)` builder. `op` defaults to
1311
- * `count` so `aggregateIndex("byUser", { by: ["userId"] })` is a single-line
1312
- * `COUNT(*) GROUP BY userId` accelerator.
1313
- */
1310
+ * Options for the inline `.aggregateIndex(name, opts)` builder. `op` defaults to
1311
+ * `count` so `aggregateIndex("byUser", { by: ["userId"] })` is a single-line
1312
+ * `COUNT(*) GROUP BY userId` accelerator.
1313
+ */
1314
1314
  interface InlineAggregateIndexOptions<Shape extends Record<string, Validator> = Record<string, Validator>> {
1315
1315
  /** Group keys; counter rows are one per distinct tuple. Omitted = single-row aggregate over the whole table. */
1316
1316
  by?: ReadonlyArray<keyof Shape & string>;
@@ -1322,11 +1322,11 @@ interface InlineAggregateIndexOptions<Shape extends Record<string, Validator> =
1322
1322
  where?: Record<string, unknown>;
1323
1323
  }
1324
1324
  /**
1325
- * Options for the inline `.rankIndex(name, opts)` builder. `sortBy` is required;
1326
- * accepts either an array of `{ field, direction }` keys, or the shorthand
1327
- * `["field"]` (asc) / `{ field: "desc" }` map entries. `partitionBy` scopes the
1328
- * rank — omitted ⇒ one global rank over the whole table.
1329
- */
1325
+ * Options for the inline `.rankIndex(name, opts)` builder. `sortBy` is required;
1326
+ * accepts either an array of `{ field, direction }` keys, or the shorthand
1327
+ * `["field"]` (asc) / `{ field: "desc" }` map entries. `partitionBy` scopes the
1328
+ * rank — omitted ⇒ one global rank over the whole table.
1329
+ */
1330
1330
  interface InlineRankIndexOptions<Shape extends Record<string, Validator> = Record<string, Validator>> {
1331
1331
  /** Columns that scope each ranking; omitted ⇒ one global rank. */
1332
1332
  partitionBy?: ReadonlyArray<keyof Shape & string>;
@@ -1342,19 +1342,19 @@ interface TableBuilder<Shape extends Record<string, Validator> = Record<string,
1342
1342
  /** Declare an aggregate (counter/sum/…) maintained by triggers for O(1) reads. */
1343
1343
  aggregateIndex: (name: string, options?: InlineAggregateIndexOptions<Shape>) => TableBuilder<Shape>;
1344
1344
  /**
1345
- * Mark this table as written outside Lunora's discoverable insert path —
1346
- * by an adapter, a migration, or framework middleware (e.g. `@lunora/auth`'s
1347
- * better-auth tables, `@lunora/ratelimit`'s store). Advisor insert-path lints
1348
- * (`table_without_insert`) then skip it instead of flagging the absent
1349
- * `ctx.db.insert(...)`.
1350
- */
1345
+ * Mark this table as written outside Lunora's discoverable insert path —
1346
+ * by an adapter, a migration, or framework middleware (e.g. `@lunora/auth`'s
1347
+ * better-auth tables, `@lunora/ratelimit`'s store). Advisor insert-path lints
1348
+ * (`table_without_insert`) then skip it instead of flagging the absent
1349
+ * `ctx.db.insert(...)`.
1350
+ */
1351
1351
  externallyManaged: () => TableBuilder<Shape>;
1352
1352
  /**
1353
- * Mark this table as global (cross-shard). Backed by **D1** by default;
1354
- * pass `{ backend: "hyperdrive" }` to store it in a Postgres/MySQL database
1355
- * via Cloudflare Hyperdrive (PlanetScale, Neon, …) instead. Either way the
1356
- * table stays reactive — live queries re-run on write.
1357
- */
1353
+ * Mark this table as global (cross-shard). Backed by **D1** by default;
1354
+ * pass `{ backend: "hyperdrive" }` to store it in a Postgres/MySQL database
1355
+ * via Cloudflare Hyperdrive (PlanetScale, Neon, …) instead. Either way the
1356
+ * table stays reactive — live queries re-run on write.
1357
+ */
1358
1358
  global: (options?: {
1359
1359
  backend?: GlobalBackend;
1360
1360
  }) => TableBuilder<Shape>;
@@ -1363,17 +1363,17 @@ interface TableBuilder<Shape extends Record<string, Validator> = Record<string,
1363
1363
  unique?: boolean;
1364
1364
  }) => TableBuilder<Shape>;
1365
1365
  /**
1366
- * Opt this table OUT of secure-by-default RLS. Under a schema marked
1367
- * `.rls("required")`, every table is protected (the write path denies raw,
1368
- * non-RLS `ctx.db` access); calling `.public()` exempts this one table so a
1369
- * plain `query`/`mutation` may read/write it without an RLS policy. No effect
1370
- * when the schema does not require RLS.
1371
- */
1366
+ * Opt this table OUT of secure-by-default RLS. Under a schema marked
1367
+ * `.rls("required")`, every table is protected (the write path denies raw,
1368
+ * non-RLS `ctx.db` access); calling `.public()` exempts this one table so a
1369
+ * plain `query`/`mutation` may read/write it without an RLS policy. No effect
1370
+ * when the schema does not require RLS.
1371
+ */
1372
1372
  public: () => TableBuilder<Shape>;
1373
1373
  /**
1374
- * Declare a rank index (sorted companion table, btree-backed) for
1375
- * `rank(row)` / `rankPage()` reads in O(log n). See {@link RankIndexDefinition}.
1376
- */
1374
+ * Declare a rank index (sorted companion table, btree-backed) for
1375
+ * `rank(row)` / `rankPage()` reads in O(log n). See {@link RankIndexDefinition}.
1376
+ */
1377
1377
  rankIndex: (name: string, options: InlineRankIndexOptions<Shape>) => TableBuilder<Shape>;
1378
1378
  /** Declare relations to other tables, loaded via `findMany({ with })`. */
1379
1379
  relations: (build: (r: RelationBuilder) => Record<string, RelationDefinition>) => TableBuilder<Shape>;
@@ -1385,33 +1385,33 @@ interface TableBuilder<Shape extends Record<string, Validator> = Record<string,
1385
1385
  /** Route storage by the named field — one DO per distinct value. */
1386
1386
  shardBy: (field: keyof Shape & string) => TableBuilder<Shape>;
1387
1387
  /**
1388
- * Turn on soft delete. Adds a nullable timestamp column (`options.field`,
1389
- * default `deletedAt`) and changes `ctx.db.&lt;table>.delete()` to **set** it
1390
- * instead of removing the row; `onDelete: "cascade"` children are recursively
1391
- * soft-deleted too. **List reads** (`findMany`/`findFirst`/`query()`/`count`/
1392
- * `aggregate`/relation loads) then hide soft-deleted rows unless they pass
1393
- * `includeDeleted: true`; by-id `get`/`patch`/`replace` and the new
1394
- * `restore()` still address the row directly. `hardDelete()` physically
1395
- * removes it (cascading as a real delete). Note: `includeDeleted` is a read
1396
- * scope, not access control — anyone who can run the read can set it; a unique
1397
- * index still rejects a new row that collides with a soft-deleted one (the row
1398
- * physically persists).
1399
- */
1388
+ * Turn on soft delete. Adds a nullable timestamp column (`options.field`,
1389
+ * default `deletedAt`) and changes `ctx.db.&lt;table>.delete()` to **set** it
1390
+ * instead of removing the row; `onDelete: "cascade"` children are recursively
1391
+ * soft-deleted too. **List reads** (`findMany`/`findFirst`/`query()`/`count`/
1392
+ * `aggregate`/relation loads) then hide soft-deleted rows unless they pass
1393
+ * `includeDeleted: true`; by-id `get`/`patch`/`replace` and the new
1394
+ * `restore()` still address the row directly. `hardDelete()` physically
1395
+ * removes it (cascading as a real delete). Note: `includeDeleted` is a read
1396
+ * scope, not access control — anyone who can run the read can set it; a unique
1397
+ * index still rejects a new row that collides with a soft-deleted one (the row
1398
+ * physically persists).
1399
+ */
1400
1400
  softDelete: (options?: {
1401
1401
  field?: string;
1402
1402
  }) => TableBuilder<Shape>;
1403
1403
  /**
1404
- * Materialize this table from an external Postgres/MySQL behind Cloudflare
1405
- * Hyperdrive (plan 077). A system-driven poll loop reads the tenant slice
1406
- * (`query`, with params bound from `tenantBy`) and lands it in the DO's SQLite,
1407
- * after which `defineShape` carries it to clients unchanged. Implies
1408
- * `.externallyManaged()` (rows come from the ingest loop, not user mutations).
1409
- *
1410
- * Orthogonal to `.shardBy()` — combine them for per-tenant DOs. **Under
1411
- * `.shardBy()` `tenantBy` is mandatory** (the tenant-isolation boundary); the
1412
- * `external_source_unscoped` advisor lint fails the build when it is absent, and
1413
- * `external_source_on_global` rejects combining `.source()` with `.global()`.
1414
- */
1404
+ * Materialize this table from an external Postgres/MySQL behind Cloudflare
1405
+ * Hyperdrive (plan 077). A system-driven poll loop reads the tenant slice
1406
+ * (`query`, with params bound from `tenantBy`) and lands it in the DO's SQLite,
1407
+ * after which `defineShape` carries it to clients unchanged. Implies
1408
+ * `.externallyManaged()` (rows come from the ingest loop, not user mutations).
1409
+ *
1410
+ * Orthogonal to `.shardBy()` — combine them for per-tenant DOs. **Under
1411
+ * `.shardBy()` `tenantBy` is mandatory** (the tenant-isolation boundary); the
1412
+ * `external_source_unscoped` advisor lint fails the build when it is absent, and
1413
+ * `external_source_on_global` rejects combining `.source()` with `.global()`.
1414
+ */
1415
1415
  source: (definition: ExternalSourceDefinition) => TableBuilder<Shape>;
1416
1416
  /** Declare named lifecycle triggers fired inline within the write path. */
1417
1417
  triggers: (build: (t: TriggerBuilder<Shape>) => Record<string, TriggerDefinition>) => TableBuilder<Shape>;
@@ -1432,22 +1432,22 @@ interface VectorIndexOptions {
1432
1432
  };
1433
1433
  }
1434
1434
  /**
1435
- * Build a table definition. Returned object is both the table definition (for
1436
- * `defineSchema`) and a fluent builder for indexes + sharding metadata.
1437
- */
1435
+ * Build a table definition. Returned object is both the table definition (for
1436
+ * `defineSchema`) and a fluent builder for indexes + sharding metadata.
1437
+ */
1438
1438
  declare const defineTable: <Shape extends Record<string, Validator>>(inputShape: Shape) => TableBuilder<Shape>;
1439
1439
  /**
1440
- * Declare a standalone vector index (DSL Shape B). Pass the returned value in
1441
- * the `vectorIndexes` map of {@link defineSchema} when the source is derived
1442
- * from multiple fields or a computation rather than a single column.
1443
- */
1440
+ * Declare a standalone vector index (DSL Shape B). Pass the returned value in
1441
+ * the `vectorIndexes` map of {@link defineSchema} when the source is derived
1442
+ * from multiple fields or a computation rather than a single column.
1443
+ */
1444
1444
  declare const defineVectorIndex: (options: VectorIndexOptions) => VectorIndexDefinition;
1445
1445
  /**
1446
- * Options for the standalone `defineAggregateIndex(name, opts)` helper (DSL
1447
- * Shape B). Unlike the inline `.aggregateIndex(...)` builder, this form takes
1448
- * the owning table explicitly via `on` — handy when a single counter wants to
1449
- * live next to the schema map rather than inside a table chain.
1450
- */
1446
+ * Options for the standalone `defineAggregateIndex(name, opts)` helper (DSL
1447
+ * Shape B). Unlike the inline `.aggregateIndex(...)` builder, this form takes
1448
+ * the owning table explicitly via `on` — handy when a single counter wants to
1449
+ * live next to the schema map rather than inside a table chain.
1450
+ */
1451
1451
  interface AggregateIndexOptions {
1452
1452
  by?: ReadonlyArray<string>;
1453
1453
  field?: string;
@@ -1456,17 +1456,17 @@ interface AggregateIndexOptions {
1456
1456
  where?: Record<string, unknown>;
1457
1457
  }
1458
1458
  /**
1459
- * Declare a standalone aggregate index. Pass the returned value to
1460
- * `defineSchema(tables, vectorIndexes, aggregateIndexes)` keyed by index name —
1461
- * the schema attaches it to `tables[on].aggregateIndexes` so runtime consumers
1462
- * (DO + D1) read every index uniformly off the table definition.
1463
- */
1459
+ * Declare a standalone aggregate index. Pass the returned value to
1460
+ * `defineSchema(tables, vectorIndexes, aggregateIndexes)` keyed by index name —
1461
+ * the schema attaches it to `tables[on].aggregateIndexes` so runtime consumers
1462
+ * (DO + D1) read every index uniformly off the table definition.
1463
+ */
1464
1464
  declare const defineAggregateIndex: (name: string, options: AggregateIndexOptions) => AggregateIndexDefinition;
1465
1465
  /**
1466
- * Options for the standalone `defineRankIndex(name, opts)` helper (DSL Shape B).
1467
- * Mirrors the inline `.rankIndex(...)` builder but takes the owning table via
1468
- * `table` so it can sit next to the schema map.
1469
- */
1466
+ * Options for the standalone `defineRankIndex(name, opts)` helper (DSL Shape B).
1467
+ * Mirrors the inline `.rankIndex(...)` builder but takes the owning table via
1468
+ * `table` so it can sit next to the schema map.
1469
+ */
1470
1470
  interface RankIndexOptions {
1471
1471
  partitionBy?: ReadonlyArray<string>;
1472
1472
  sortBy: ReadonlyArray<{
@@ -1477,64 +1477,64 @@ interface RankIndexOptions {
1477
1477
  where?: Record<string, unknown>;
1478
1478
  }
1479
1479
  /**
1480
- * Declare a standalone rank index. Pass the returned value to
1481
- * `defineSchema(tables, vectorIndexes, aggregateIndexes, rankIndexes)` keyed
1482
- * by index name — the schema attaches it to `tables[on].rankIndexes`.
1483
- */
1480
+ * Declare a standalone rank index. Pass the returned value to
1481
+ * `defineSchema(tables, vectorIndexes, aggregateIndexes, rankIndexes)` keyed
1482
+ * by index name — the schema attaches it to `tables[on].rankIndexes`.
1483
+ */
1484
1484
  declare const defineRankIndex: (name: string, options: RankIndexOptions) => RankIndexDefinition;
1485
1485
  /**
1486
- * Build the application schema. The first argument is the table map; the
1487
- * optional second argument registers standalone `defineVectorIndex(...)`
1488
- * declarations (DSL Shape B) keyed by index name. The optional third argument
1489
- * registers standalone `defineAggregateIndex(...)` declarations (DSL Shape B);
1490
- * the optional fourth argument registers standalone `defineRankIndex(...)`
1491
- * declarations. Both are folded into the matching `tables[on].*Indexes` array
1492
- * so runtime backends read every index uniformly off the table definition.
1493
- */
1494
- /**
1495
- * Schema with an in-place `.extend(plugin.extension)` method. Used so apps
1496
- * can compose plugin schemas: `defineSchema({...}).extend(authPlugin.extension)`.
1497
- *
1498
- * `extend` is non-mutating — returns a fresh `ExtendableSchema` containing
1499
- * the merged tables. Extension tables are auto-namespaced by the extension
1500
- * `key` (`buckets` → `ratelimit_buckets`), so the merged type carries the
1501
- * prefixed names via {@link PrefixedTables}. Chains:
1502
- * `defineSchema(...).extend(a).extend(b)` is the typed equivalent of merging
1503
- * `a`'s prefixed tables then `b`'s.
1504
- */
1486
+ * Build the application schema. The first argument is the table map; the
1487
+ * optional second argument registers standalone `defineVectorIndex(...)`
1488
+ * declarations (DSL Shape B) keyed by index name. The optional third argument
1489
+ * registers standalone `defineAggregateIndex(...)` declarations (DSL Shape B);
1490
+ * the optional fourth argument registers standalone `defineRankIndex(...)`
1491
+ * declarations. Both are folded into the matching `tables[on].*Indexes` array
1492
+ * so runtime backends read every index uniformly off the table definition.
1493
+ */
1494
+ /**
1495
+ * Schema with an in-place `.extend(plugin.extension)` method. Used so apps
1496
+ * can compose plugin schemas: `defineSchema({...}).extend(authPlugin.extension)`.
1497
+ *
1498
+ * `extend` is non-mutating — returns a fresh `ExtendableSchema` containing
1499
+ * the merged tables. Extension tables are auto-namespaced by the extension
1500
+ * `key` (`buckets` → `ratelimit_buckets`), so the merged type carries the
1501
+ * prefixed names via {@link PrefixedTables}. Chains:
1502
+ * `defineSchema(...).extend(a).extend(b)` is the typed equivalent of merging
1503
+ * `a`'s prefixed tables then `b`'s.
1504
+ */
1505
1505
  type ExtendableSchema<T extends Record<string, TableDefinition>> = {
1506
1506
  extend: <X extends Record<string, TableDefinition>, Key extends string>(extension: SchemaExtension<X> & {
1507
1507
  readonly key: Key;
1508
1508
  }) => ExtendableSchema<PrefixedTables<X, Key> & T>;
1509
1509
  /**
1510
- * Pin every Durable Object the app reaches — shards, fan-out, subscriptions,
1511
- * the scheduler, and `ctx.containers` — to a Cloudflare data-residency
1512
- * jurisdiction (`"eu"`, `"us"`, `"fedramp"`). Codegen reads this off the
1513
- * schema and emits it into the generated worker's `createWorker({ jurisdiction })`
1514
- * (and `ctx.scheduler` / `ctx.containers`). Non-mutating: returns a fresh
1515
- * `ExtendableSchema`, so it composes with `.rls(...)` / `.extend(...)` in any order.
1516
- *
1517
- * ⚠️ **Set this once, before your first deploy — changing or removing it
1518
- * strands data.** A Durable Object name maps to a *different* ID in each
1519
- * jurisdiction, so toggling this on an existing app makes every shard, scheduler
1520
- * job, and session DO resolve to a NEW, empty DO; the previous data stays in the
1521
- * old jurisdiction's DOs and is no longer reachable. There is no in-place
1522
- * migration — you would have to export from the old jurisdiction and import
1523
- * into the new one.
1524
- *
1525
- * Note: this pins **DO-backed** state only. D1-backed state — `.global()`
1526
- * tables and `@lunora/auth` sessions alike — is governed by D1's own location
1527
- * settings, not this option.
1528
- * @see https://developers.cloudflare.com/durable-objects/reference/data-location/
1529
- */
1510
+ * Pin every Durable Object the app reaches — shards, fan-out, subscriptions,
1511
+ * the scheduler, and `ctx.containers` — to a Cloudflare data-residency
1512
+ * jurisdiction (`"eu"`, `"us"`, `"fedramp"`). Codegen reads this off the
1513
+ * schema and emits it into the generated worker's `createWorker({ jurisdiction })`
1514
+ * (and `ctx.scheduler` / `ctx.containers`). Non-mutating: returns a fresh
1515
+ * `ExtendableSchema`, so it composes with `.rls(...)` / `.extend(...)` in any order.
1516
+ *
1517
+ * ⚠️ **Set this once, before your first deploy — changing or removing it
1518
+ * strands data.** A Durable Object name maps to a *different* ID in each
1519
+ * jurisdiction, so toggling this on an existing app makes every shard, scheduler
1520
+ * job, and session DO resolve to a NEW, empty DO; the previous data stays in the
1521
+ * old jurisdiction's DOs and is no longer reachable. There is no in-place
1522
+ * migration — you would have to export from the old jurisdiction and import
1523
+ * into the new one.
1524
+ *
1525
+ * Note: this pins **DO-backed** state only. D1-backed state — `.global()`
1526
+ * tables and `@lunora/auth` sessions alike — is governed by D1's own location
1527
+ * settings, not this option.
1528
+ * @see https://developers.cloudflare.com/durable-objects/reference/data-location/
1529
+ */
1530
1530
  jurisdiction: (jurisdiction: DurableObjectJurisdiction) => ExtendableSchema<T>;
1531
1531
  /**
1532
- * Turn on secure-by-default RLS for the whole schema. Every table is then
1533
- * protected — the DO/D1 write path denies raw, non-RLS `ctx.db` access, so a
1534
- * procedure that forgets `.use(rls(...))` fails closed. Opt a table out with
1535
- * `.public()`. Non-mutating: returns a fresh `ExtendableSchema` carrying the
1536
- * mode, so `.rls("required")` composes with `.extend(...)` either order.
1537
- */
1532
+ * Turn on secure-by-default RLS for the whole schema. Every table is then
1533
+ * protected — the DO/D1 write path denies raw, non-RLS `ctx.db` access, so a
1534
+ * procedure that forgets `.use(rls(...))` fails closed. Opt a table out with
1535
+ * `.public()`. Non-mutating: returns a fresh `ExtendableSchema` carrying the
1536
+ * mode, so `.rls("required")` composes with `.extend(...)` either order.
1537
+ */
1538
1538
  rls: (mode: "required") => ExtendableSchema<T>;
1539
1539
  } & Schema<T>;
1540
1540
  declare const defineSchema: <T extends Record<string, TableDefinition>>(tables: T, vectorIndexes?: Record<string, VectorIndexDefinition>, aggregateIndexes?: Record<string, AggregateIndexDefinition>, rankIndexes?: Record<string, RankIndexDefinition>) => ExtendableSchema<T>;
@@ -1542,19 +1542,19 @@ declare const defineSchema: <T extends Record<string, TableDefinition>>(tables:
1542
1542
  declare const DEFAULT_TTL_MS = 3e4;
1543
1543
  declare const PRESENCE_BARE_TABLE = "present";
1544
1544
  /**
1545
- * The prefixed table name the extension produces at merge time. The handlers
1546
- * read/write this name directly so they always agree with the merged schema.
1547
- */
1545
+ * The prefixed table name the extension produces at merge time. The handlers
1546
+ * read/write this name directly so they always agree with the merged schema.
1547
+ */
1548
1548
  declare const PRESENCE_TABLE: "presence_present";
1549
1549
  /**
1550
- * A single present member as returned by `listPresent`.
1551
- *
1552
- * Note: the raw client-chosen `sessionId` is deliberately NOT surfaced. It is a
1553
- * connection secret — disclosing every member's `sessionId` would let any
1554
- * subscriber enumerate them and target the heartbeat / disconnect write paths.
1555
- * A "who's here" UI needs only `userId` + awareness `data`; the caller already
1556
- * knows its own session id locally (the `usePresence` hook returns it).
1557
- */
1550
+ * A single present member as returned by `listPresent`.
1551
+ *
1552
+ * Note: the raw client-chosen `sessionId` is deliberately NOT surfaced. It is a
1553
+ * connection secret — disclosing every member's `sessionId` would let any
1554
+ * subscriber enumerate them and target the heartbeat / disconnect write paths.
1555
+ * A "who's here" UI needs only `userId` + awareness `data`; the caller already
1556
+ * knows its own session id locally (the `usePresence` hook returns it).
1557
+ */
1558
1558
  interface PresenceMember {
1559
1559
  /** Opaque awareness blob (selection, cursor, name, color…). */
1560
1560
  data?: Record<string, unknown>;
@@ -1568,41 +1568,41 @@ interface PresenceMember {
1568
1568
  /** Options for {@link definePresence}. */
1569
1569
  interface DefinePresenceOptions {
1570
1570
  /**
1571
- * Grace window (ms) before a gracefully-closed session is dropped from the
1572
- * present list. When `0` (the default), `onDisconnect` hard-deletes the
1573
- * session's row the instant its socket closes. When `> 0`, the row is
1574
- * instead aged so the read-time TTL filter hides it `disconnectGraceMs`
1575
- * from now — a reconnect with the same `sessionId` within the window
1576
- * re-heartbeats and restores full presence with no visible flicker (the
1577
- * AnyCable `presence_ttl` behaviour). Clamped to `ttlMs`.
1578
- */
1571
+ * Grace window (ms) before a gracefully-closed session is dropped from the
1572
+ * present list. When `0` (the default), `onDisconnect` hard-deletes the
1573
+ * session's row the instant its socket closes. When `> 0`, the row is
1574
+ * instead aged so the read-time TTL filter hides it `disconnectGraceMs`
1575
+ * from now — a reconnect with the same `sessionId` within the window
1576
+ * re-heartbeats and restores full presence with no visible flicker (the
1577
+ * AnyCable `presence_ttl` behaviour). Clamped to `ttlMs`.
1578
+ */
1579
1579
  disconnectGraceMs?: number;
1580
1580
  /**
1581
- * How long (ms) a heartbeat keeps a member present. `listPresent` excludes
1582
- * rows whose `lastSeen` is older than `now - ttlMs`. Defaults to 30s.
1583
- */
1581
+ * How long (ms) a heartbeat keeps a member present. `listPresent` excludes
1582
+ * rows whose `lastSeen` is older than `now - ttlMs`. Defaults to 30s.
1583
+ */
1584
1584
  ttlMs?: number;
1585
1585
  }
1586
1586
  /** The registered functions a presence component ships. */
1587
1587
  interface PresenceFunctions {
1588
1588
  /**
1589
- * Connection-lifecycle hook: the instant a client's WebSocket drops, hard-
1590
- * delete its presence row so it disappears from `listPresent` with no TTL
1591
- * lag. Targets the row by the `{ roomId, sessionId }` the client passed as
1592
- * the connection `context`, and only deletes it when the disconnecting
1593
- * VERIFIED identity owns the row (so a forged context can't evict another
1594
- * member). The TTL filter + `sweep` remain the fallback for ungraceful drops
1595
- * where no `context` was recorded.
1596
- */
1589
+ * Connection-lifecycle hook: the instant a client's WebSocket drops, hard-
1590
+ * delete its presence row so it disappears from `listPresent` with no TTL
1591
+ * lag. Targets the row by the `{ roomId, sessionId }` the client passed as
1592
+ * the connection `context`, and only deletes it when the disconnecting
1593
+ * VERIFIED identity owns the row (so a forged context can't evict another
1594
+ * member). The TTL filter + `sweep` remain the fallback for ungraceful drops
1595
+ * where no `context` was recorded.
1596
+ */
1597
1597
  disconnect: RegisteredLifecycleHook;
1598
1598
  /**
1599
- * Upsert the caller's presence row for `roomId` and stamp `lastSeen = now`.
1600
- * Keyed by `(roomId, sessionId)` — re-heartbeats patch the existing row so
1601
- * subscribers receive a single-row delta, not a churn of insert/delete. A
1602
- * heartbeat may only patch a row owned by the same identity (an existing row
1603
- * held by a different `userId` is refused with `FORBIDDEN`), so a client
1604
- * can't overwrite another member's awareness data via a guessed `sessionId`.
1605
- */
1599
+ * Upsert the caller's presence row for `roomId` and stamp `lastSeen = now`.
1600
+ * Keyed by `(roomId, sessionId)` — re-heartbeats patch the existing row so
1601
+ * subscribers receive a single-row delta, not a churn of insert/delete. A
1602
+ * heartbeat may only patch a row owned by the same identity (an existing row
1603
+ * held by a different `userId` is refused with `FORBIDDEN`), so a client
1604
+ * can't overwrite another member's awareness data via a guessed `sessionId`.
1605
+ */
1606
1606
  heartbeat: RegisteredMutation<{
1607
1607
  data: ReturnType<typeof v.optional>;
1608
1608
  roomId: ReturnType<typeof v.string>;
@@ -1611,17 +1611,17 @@ interface PresenceFunctions {
1611
1611
  lastSeen: number;
1612
1612
  }>;
1613
1613
  /**
1614
- * Live query returning the non-expired members of `roomId`, newest heartbeat
1615
- * first. Subscribe to it for a reactive "who's here" list.
1616
- */
1614
+ * Live query returning the non-expired members of `roomId`, newest heartbeat
1615
+ * first. Subscribe to it for a reactive "who's here" list.
1616
+ */
1617
1617
  listPresent: RegisteredQuery<{
1618
1618
  roomId: ReturnType<typeof v.string>;
1619
1619
  }, PresenceMember[]>;
1620
1620
  /**
1621
- * Internal mutation that hard-deletes every expired row for `roomId`. Stale
1622
- * rows already vanish from `listPresent` via the read-time TTL filter; this
1623
- * only reclaims storage. Schedule it (cron / `runAfter`) if you care.
1624
- */
1621
+ * Internal mutation that hard-deletes every expired row for `roomId`. Stale
1622
+ * rows already vanish from `listPresent` via the read-time TTL filter; this
1623
+ * only reclaims storage. Schedule it (cron / `runAfter`) if you care.
1624
+ */
1625
1625
  sweep: RegisteredMutation<{
1626
1626
  roomId: ReturnType<typeof v.string>;
1627
1627
  }, {
@@ -1635,114 +1635,114 @@ type PresenceComponent = Component<{
1635
1635
  functions: PresenceFunctions;
1636
1636
  };
1637
1637
  /**
1638
- * The presence schema extension: a single `present` table, auto-namespaced to
1639
- * `presence_present` at merge time, indexed by `(roomId, sessionId)` for the
1640
- * heartbeat upsert and by `roomId` for `listPresent`.
1641
- */
1638
+ * The presence schema extension: a single `present` table, auto-namespaced to
1639
+ * `presence_present` at merge time, indexed by `(roomId, sessionId)` for the
1640
+ * heartbeat upsert and by `roomId` for `listPresent`.
1641
+ */
1642
1642
  declare const presenceExtension: SchemaExtension<{
1643
1643
  [PRESENCE_BARE_TABLE]: ReturnType<typeof defineTable>;
1644
1644
  }>;
1645
1645
  declare const definePresence: (options?: DefinePresenceOptions) => PresenceComponent;
1646
1646
  /**
1647
- * The middlewares `protectPublic` chains, in the order they run. Every field is
1648
- * optional, so a bundle can be just a rate limit, just a captcha, or any mix —
1649
- * pass the already-constructed middlewares (e.g. `rateLimit(limiter, "signup")`
1650
- * from `@lunora/ratelimit`, `verifyTurnstileMiddleware({...})` from
1651
- * `@lunora/auth`). They are accepted as values rather than imported here so
1652
- * `@lunora/server` keeps no dependency on those packages (which depend on it).
1653
- */
1647
+ * The middlewares `protectPublic` chains, in the order they run. Every field is
1648
+ * optional, so a bundle can be just a rate limit, just a captcha, or any mix —
1649
+ * pass the already-constructed middlewares (e.g. `rateLimit(limiter, "signup")`
1650
+ * from `@lunora/ratelimit`, `verifyTurnstileMiddleware({...})` from
1651
+ * `@lunora/auth`). They are accepted as values rather than imported here so
1652
+ * `@lunora/server` keeps no dependency on those packages (which depend on it).
1653
+ */
1654
1654
  interface ProtectPublicOptions<Context> {
1655
1655
  /**
1656
- * A CAPTCHA / bot check, run after the rate limit. Placed second on purpose:
1657
- * an obvious flood is cheaper to reject with the in-memory limiter than with
1658
- * a Turnstile siteverify round-trip.
1659
- */
1656
+ * A CAPTCHA / bot check, run after the rate limit. Placed second on purpose:
1657
+ * an obvious flood is cheaper to reject with the in-memory limiter than with
1658
+ * a Turnstile siteverify round-trip.
1659
+ */
1660
1660
  captcha?: Middleware<Context, Context>;
1661
1661
  /**
1662
- * A rate limit, run first. Cheapest gate, so it sheds obvious abuse before
1663
- * any network-bound check below it runs.
1664
- */
1662
+ * A rate limit, run first. Cheapest gate, so it sheds obvious abuse before
1663
+ * any network-bound check below it runs.
1664
+ */
1665
1665
  rateLimit?: Middleware<Context, Context>;
1666
1666
  /** Extra middlewares appended after `rateLimit` and `captcha`, in order. */
1667
1667
  use?: ReadonlyArray<Middleware<Context, Context>>;
1668
1668
  }
1669
1669
  /**
1670
- * Compose the recommended public-procedure protections into a single
1671
- * `.use()`-able middleware. It is thin sugar over middleware composition — no
1672
- * new enforcement engine — chaining (in order) a rate limit, a CAPTCHA check,
1673
- * and any extra middlewares so a public mutation that creates users, sends
1674
- * mail, or consumes credits is guarded in one attachment:
1675
- *
1676
- * ```ts
1677
- * export const signUp = mutation
1678
- * .use(protectPublic({
1679
- * rateLimit: rateLimit(limiter, "signup"),
1680
- * captcha: verifyTurnstileMiddleware({ secret: env.TURNSTILE_SECRET_KEY, token: (c) => c.args.captchaToken }),
1681
- * }))
1682
- * .handler(async (ctx, args) => { ... });
1683
- * ```
1684
- *
1685
- * The bundle is context-preserving — each inner middleware leaves the context
1686
- * unchanged — so it slots into any `.use()` chain without reshaping the
1687
- * procedure context. Omitted fields are skipped; an empty bundle is a
1688
- * transparent pass-through.
1689
- */
1670
+ * Compose the recommended public-procedure protections into a single
1671
+ * `.use()`-able middleware. It is thin sugar over middleware composition — no
1672
+ * new enforcement engine — chaining (in order) a rate limit, a CAPTCHA check,
1673
+ * and any extra middlewares so a public mutation that creates users, sends
1674
+ * mail, or consumes credits is guarded in one attachment:
1675
+ *
1676
+ * ```ts
1677
+ * export const signUp = mutation
1678
+ * .use(protectPublic({
1679
+ * rateLimit: rateLimit(limiter, "signup"),
1680
+ * captcha: verifyTurnstileMiddleware({ secret: env.TURNSTILE_SECRET_KEY, token: (c) => c.args.captchaToken }),
1681
+ * }))
1682
+ * .handler(async (ctx, args) => { ... });
1683
+ * ```
1684
+ *
1685
+ * The bundle is context-preserving — each inner middleware leaves the context
1686
+ * unchanged — so it slots into any `.use()` chain without reshaping the
1687
+ * procedure context. Omitted fields are skipped; an empty bundle is a
1688
+ * transparent pass-through.
1689
+ */
1690
1690
  declare const protectPublic: <Context>(options: ProtectPublicOptions<Context>) => Middleware<Context, Context>;
1691
1691
  declare const definePolicy: <Context = unknown>(input: DefinePolicyInput<Context>) => Policy<Context>;
1692
1692
  /**
1693
- * Build a project-bound, relation-aware `definePolicy` typed against the
1694
- * generated `DataModel` (`DM`) + `Relations` (`REL`) maps. Codegen emits a
1695
- * `createPolicyDsl&lt;DataModel, Relations>()` binding into `_generated/server.ts`,
1696
- * so importing `definePolicy` from the generated module constrains `table` to a
1697
- * real table name and type-checks the `when` predicate — including Prisma-style
1698
- * relation predicates (`is`/`some`/…) the `@lunora/do` pre-resolver now resolves
1699
- * on reads. The runtime is byte-for-byte the untyped {@link definePolicy}; only
1700
- * the compile-time surface narrows, so a policy authored either way is
1701
- * discovered identically by the `rls()` chain.
1702
- */
1693
+ * Build a project-bound, relation-aware `definePolicy` typed against the
1694
+ * generated `DataModel` (`DM`) + `Relations` (`REL`) maps. Codegen emits a
1695
+ * `createPolicyDsl&lt;DataModel, Relations>()` binding into `_generated/server.ts`,
1696
+ * so importing `definePolicy` from the generated module constrains `table` to a
1697
+ * real table name and type-checks the `when` predicate — including Prisma-style
1698
+ * relation predicates (`is`/`some`/…) the `@lunora/do` pre-resolver now resolves
1699
+ * on reads. The runtime is byte-for-byte the untyped {@link definePolicy}; only
1700
+ * the compile-time surface narrows, so a policy authored either way is
1701
+ * discovered identically by the `rls()` chain.
1702
+ */
1703
1703
  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>;
1704
1704
  /**
1705
- * Declare a named permission a policy can check with `ctx.auth.can(...)`. Grant
1706
- * it to a role through `defineRole`'s `permissions`, register those roles with
1707
- * the middleware via `rls(policies, { roles })`, then check it in a policy with
1708
- * `when: ({ auth }) => auth.can(permission)`. See the `./index` JSDoc for a
1709
- * worked example.
1710
- */
1705
+ * Declare a named permission a policy can check with `ctx.auth.can(...)`. Grant
1706
+ * it to a role through `defineRole`'s `permissions`, register those roles with
1707
+ * the middleware via `rls(policies, { roles })`, then check it in a policy with
1708
+ * `when: ({ auth }) => auth.can(permission)`. See the `./index` JSDoc for a
1709
+ * worked example.
1710
+ */
1711
1711
  declare const definePermission: (name: string, options?: Omit<Permission, "name">) => Permission;
1712
1712
  /**
1713
- * Collect a list of policies into the structure the `rls()` middleware
1714
- * consumes. Multiple read policies on the same table OR together (any one
1715
- * matching reveals the row); multiple write policies for the same `(table,
1716
- * op)` AND together (every one must allow). The middleware keeps them in order
1717
- * and decides — see `./middleware`.
1718
- *
1719
- * Validates against an **accidentally duplicated policy** — the same
1720
- * `(table, on)` registered with the *same* decision function (a copy-paste, or
1721
- * the same policy object spread in twice). Because multiple DISTINCT policies
1722
- * per `(table, on)` are intentional, the check keys on the `when` reference too:
1723
- * only a reference-identical `when` for the same `(table, on)` is a real
1724
- * duplicate. Throws at module load so the misconfiguration surfaces immediately
1725
- * rather than as a silently double-evaluated predicate at request time.
1726
- */
1713
+ * Collect a list of policies into the structure the `rls()` middleware
1714
+ * consumes. Multiple read policies on the same table OR together (any one
1715
+ * matching reveals the row); multiple write policies for the same `(table,
1716
+ * op)` AND together (every one must allow). The middleware keeps them in order
1717
+ * and decides — see `./middleware`.
1718
+ *
1719
+ * Validates against an **accidentally duplicated policy** — the same
1720
+ * `(table, on)` registered with the *same* decision function (a copy-paste, or
1721
+ * the same policy object spread in twice). Because multiple DISTINCT policies
1722
+ * per `(table, on)` are intentional, the check keys on the `when` reference too:
1723
+ * only a reference-identical `when` for the same `(table, on)` is a real
1724
+ * duplicate. Throws at module load so the misconfiguration surfaces immediately
1725
+ * rather than as a silently double-evaluated predicate at request time.
1726
+ */
1727
1727
  declare const definePolicies: <Context = unknown>(policies: ReadonlyArray<Policy<Context>>) => ReadonlyArray<Policy<Context>>;
1728
1728
  declare const defineRole: (name: string, options?: Omit<Role, "name">) => Role;
1729
1729
  /**
1730
- * Structural mirror of `@lunora/do`'s `QueryArgs` and `CountArgs`. The
1731
- * runtime ORM in `@lunora/do`/`@lunora/d1` reads `baseWhere` /
1732
- * `restrictsCounts` straight off these option objects, so as long as the
1733
- * fields here stay name-compatible the wrapper is portable across the two
1734
- * dialects without an inter-package dependency.
1735
- */
1730
+ * Structural mirror of `@lunora/do`'s `QueryArgs` and `CountArgs`. The
1731
+ * runtime ORM in `@lunora/do`/`@lunora/d1` reads `baseWhere` /
1732
+ * `restrictsCounts` straight off these option objects, so as long as the
1733
+ * fields here stay name-compatible the wrapper is portable across the two
1734
+ * dialects without an inter-package dependency.
1735
+ */
1736
1736
  interface QueryArgs {
1737
1737
  baseWhere?: WhereInput;
1738
1738
  cursor?: null | string;
1739
1739
  limit?: number;
1740
1740
  orderBy?: ReadonlyArray<unknown>;
1741
1741
  /**
1742
- * Per-target-table read filter the RLS wrapper attaches so a `with` relation
1743
- * is policy-filtered on its own hop (see `@lunora/do`'s `QueryArgs`). Mirrors
1744
- * the top-level read: `(table) => readBase(table).baseWhere`.
1745
- */
1742
+ * Per-target-table read filter the RLS wrapper attaches so a `with` relation
1743
+ * is policy-filtered on its own hop (see `@lunora/do`'s `QueryArgs`). Mirrors
1744
+ * the top-level read: `(table) => readBase(table).baseWhere`.
1745
+ */
1746
1746
  relationBaseWhere?: (table: string) => undefined | WhereInput;
1747
1747
  restrictsCounts?: boolean;
1748
1748
  where?: WhereInput;
@@ -1815,18 +1815,18 @@ interface TableReaderLike {
1815
1815
  withSearchIndex: (indexName: string, search: (q: unknown) => unknown) => TableReaderLike;
1816
1816
  }
1817
1817
  /**
1818
- * Structural projection of the runtime ORM writer. The wrapper relies only
1819
- * on these fields, so it's interchangeable between `@lunora/do`'s
1820
- * `DatabaseWriterLike` and `@lunora/d1`'s `DatabaseWriterLike`.
1821
- */
1818
+ * Structural projection of the runtime ORM writer. The wrapper relies only
1819
+ * on these fields, so it's interchangeable between `@lunora/do`'s
1820
+ * `DatabaseWriterLike` and `@lunora/d1`'s `DatabaseWriterLike`.
1821
+ */
1822
1822
  interface DatabaseWriterLike {
1823
1823
  /**
1824
- * Reduce matching rows to a scalar. The RLS wrapper AND-merges the read
1825
- * `baseWhere` into `options` so the reduction only sees policy-visible rows
1826
- * (safe: an aggregate scoped to `where` never reveals a hidden row — see
1827
- * `@lunora/do`'s `RestrictableQueryOptions`). Required: the only writer ever
1828
- * wrapped is `@lunora/do`'s `createShardCtxDb`, which always implements it.
1829
- */
1824
+ * Reduce matching rows to a scalar. The RLS wrapper AND-merges the read
1825
+ * `baseWhere` into `options` so the reduction only sees policy-visible rows
1826
+ * (safe: an aggregate scoped to `where` never reveals a hidden row — see
1827
+ * `@lunora/do`'s `RestrictableQueryOptions`). Required: the only writer ever
1828
+ * wrapped is `@lunora/do`'s `createShardCtxDb`, which always implements it.
1829
+ */
1830
1830
  aggregate: (tableName: string, options: AggregateArgs) => Promise<null | number>;
1831
1831
  count: (tableName: string, whereOrArgs?: CountArgs | WhereInput) => Promise<number>;
1832
1832
  delete: (id: string, expectedTable?: string, options?: {
@@ -1847,10 +1847,10 @@ interface DatabaseWriterLike {
1847
1847
  findMany: (tableName: string, args?: QueryArgs) => Promise<QueryPage>;
1848
1848
  get: (id: string, expectedTable?: string) => Promise<Record<string, unknown> | null>;
1849
1849
  /**
1850
- * Group + reduce. Same `baseWhere` injection as `aggregate`: the per-group
1851
- * reduction is scoped to policy-visible rows, so a group count tallies only
1852
- * rows the caller may read. Required for the same reason as `aggregate`.
1853
- */
1850
+ * Group + reduce. Same `baseWhere` injection as `aggregate`: the per-group
1851
+ * reduction is scoped to policy-visible rows, so a group count tallies only
1852
+ * rows the caller may read. Required for the same reason as `aggregate`.
1853
+ */
1854
1854
  groupBy: (tableName: string, options: GroupByArgs) => Promise<ReadonlyArray<{
1855
1855
  key: Record<string, unknown>;
1856
1856
  value: null | number;
@@ -1865,13 +1865,13 @@ interface DatabaseWriterLike {
1865
1865
  limit?: number;
1866
1866
  }) => Promise<string[]>;
1867
1867
  /**
1868
- * Optional table-aware lookup. The underlying writer (e.g. `@lunora/do`)
1869
- * already knows the owning table of an id internally, so it can return
1870
- * `{ row, tableName }` in a single round-trip. When present, the RLS wrapper
1871
- * uses it to collapse the per-call membership-probe fan-out (1 `get` + N
1872
- * `findFirst` across every policy table) down to one lookup. Writers that
1873
- * don't implement it fall back to the probe path.
1874
- */
1868
+ * Optional table-aware lookup. The underlying writer (e.g. `@lunora/do`)
1869
+ * already knows the owning table of an id internally, so it can return
1870
+ * `{ row, tableName }` in a single round-trip. When present, the RLS wrapper
1871
+ * uses it to collapse the per-call membership-probe fan-out (1 `get` + N
1872
+ * `findFirst` across every policy table) down to one lookup. Writers that
1873
+ * don't implement it fall back to the probe path.
1874
+ */
1875
1875
  lookupById?: (id: string, expectedTable?: string) => Promise<null | {
1876
1876
  row: Record<string, unknown>;
1877
1877
  tableName: string;
@@ -1895,11 +1895,11 @@ interface DatabaseWriterLike {
1895
1895
  }>;
1896
1896
  query: (tableName: string) => TableReaderLike;
1897
1897
  /**
1898
- * Rank a row within its partition. A position is a count-of-rows-before, so
1899
- * — exactly like `count()` — it can't be trusted in an RLS-restricted
1900
- * reader: the wrapper fails it closed with `COUNT_RLS_UNSUPPORTED`. Required
1901
- * for the same reason as `aggregate`.
1902
- */
1898
+ * Rank a row within its partition. A position is a count-of-rows-before, so
1899
+ * — exactly like `count()` — it can't be trusted in an RLS-restricted
1900
+ * reader: the wrapper fails it closed with `COUNT_RLS_UNSUPPORTED`. Required
1901
+ * for the same reason as `aggregate`.
1902
+ */
1903
1903
  rank: (tableName: string, indexName: string, options: RankArgs) => Promise<null | {
1904
1904
  position: number;
1905
1905
  total: number;
@@ -1910,24 +1910,24 @@ interface DatabaseWriterLike {
1910
1910
  total: number;
1911
1911
  }>;
1912
1912
  /**
1913
- * Sorted pagination over a rank companion. The companion stores only the
1914
- * partition + sort keys + id, so an arbitrary read `baseWhere` can't be
1915
- * enforced against it (and re-filtering the fetched rows would break page
1916
- * sizing). RLS therefore fails it closed rather than leak hidden rows.
1917
- * Required for the same reason as `aggregate`.
1918
- */
1913
+ * Sorted pagination over a rank companion. The companion stores only the
1914
+ * partition + sort keys + id, so an arbitrary read `baseWhere` can't be
1915
+ * enforced against it (and re-filtering the fetched rows would break page
1916
+ * sizing). RLS therefore fails it closed rather than leak hidden rows.
1917
+ * Required for the same reason as `aggregate`.
1918
+ */
1919
1919
  rankPage: (tableName: string, indexName: string, options?: RankPageArgs) => Promise<QueryPage>;
1920
1920
  replace: (id: string, document: Record<string, unknown>, expectedTable?: string) => Promise<void>;
1921
1921
  restore?: (id: string, expectedTable?: string) => Promise<void>;
1922
1922
  }
1923
1923
  /**
1924
- * What a procedure's `ctx.db` must structurally satisfy for the middleware
1925
- * to wrap it. We deliberately mirror `@lunora/do`'s `DatabaseWriterLike`
1926
- * rather than `@lunora/server`'s nominal `DatabaseWriter`/`DatabaseReader`:
1927
- * the runtime adapter that flows in is the `DatabaseWriterLike`-shaped one,
1928
- * and structural matching keeps this module free of an `@lunora/do`-typed
1929
- * `ctx`.
1930
- */
1924
+ * What a procedure's `ctx.db` must structurally satisfy for the middleware
1925
+ * to wrap it. We deliberately mirror `@lunora/do`'s `DatabaseWriterLike`
1926
+ * rather than `@lunora/server`'s nominal `DatabaseWriter`/`DatabaseReader`:
1927
+ * the runtime adapter that flows in is the `DatabaseWriterLike`-shaped one,
1928
+ * and structural matching keeps this module free of an `@lunora/do`-typed
1929
+ * `ctx`.
1930
+ */
1931
1931
  type RlsDatabase = DatabaseWriterLike;
1932
1932
  /** Roles list source on the context. Tolerant of older auth states. */
1933
1933
  type AuthLike = {
@@ -1942,11 +1942,11 @@ interface RlsContextIn {
1942
1942
  }
1943
1943
  declare const rls: <Context extends RlsContextIn = RlsContextIn>(policies: ReadonlyArray<Policy<Context>>, options?: RlsOptions) => Middleware<Context, Context>;
1944
1944
  /**
1945
- * One `rls()` tag's read policies for a table, paired with the role→permission
1946
- * grants of that SAME middleware. Keeping the role map per-group is what lets a
1947
- * policy's `auth.can(...)` resolve against its own middleware's roles — never a
1948
- * permission registered on a different `rls()` step.
1949
- */
1945
+ * One `rls()` tag's read policies for a table, paired with the role→permission
1946
+ * grants of that SAME middleware. Keeping the role map per-group is what lets a
1947
+ * policy's `auth.can(...)` resolve against its own middleware's roles — never a
1948
+ * permission registered on a different `rls()` step.
1949
+ */
1950
1950
  interface ScopedReadPolicies {
1951
1951
  readonly policies: ReadonlyArray<Policy>;
1952
1952
  readonly rolePermissions: ReadonlyMap<string, ReadonlySet<string>>;
@@ -1975,86 +1975,86 @@ interface ShapeReadWhereRequest {
1975
1975
  readonly userId: null | string;
1976
1976
  }
1977
1977
  /**
1978
- * Build the read-policy registry from the registered functions (pass
1979
- * `Object.values(LUNORA_FUNCTIONS)`). Only `on: "read"` policies are collected,
1980
- * grouped per `rls()` middleware so each group keeps its own role→permission map
1981
- * (a `(table, when)` pair is de-duplicated within a tag). A tag reused across
1982
- * several procedures (a shared `const guard = rls(...)`) is folded once. This
1983
- * mirrors the request-time `rls()` path exactly: a policy's `auth.can(...)`
1984
- * resolves against the roles of the middleware that declared it, never a union.
1985
- */
1978
+ * Build the read-policy registry from the registered functions (pass
1979
+ * `Object.values(LUNORA_FUNCTIONS)`). Only `on: "read"` policies are collected,
1980
+ * grouped per `rls()` middleware so each group keeps its own role→permission map
1981
+ * (a `(table, when)` pair is de-duplicated within a tag). A tag reused across
1982
+ * several procedures (a shared `const guard = rls(...)`) is folded once. This
1983
+ * mirrors the request-time `rls()` path exactly: a policy's `auth.can(...)`
1984
+ * resolves against the roles of the middleware that declared it, never a union.
1985
+ */
1986
1986
  declare const buildRlsReadRegistry: (functions: Iterable<unknown>) => RlsReadRegistry;
1987
1987
  /**
1988
- * Compute the effective `where` a shape replicates: the table's RLS read
1989
- * base-where AND the shape's own predicate. Returns the shape predicate
1990
- * unchanged for a table with no read policy (a `.public()` or non-RLS table),
1991
- * and the FALSE sentinel (replicate nothing) when a `.rls("required")` schema
1992
- * exposes a protected, policy-less table.
1993
- */
1988
+ * Compute the effective `where` a shape replicates: the table's RLS read
1989
+ * base-where AND the shape's own predicate. Returns the shape predicate
1990
+ * unchanged for a table with no read policy (a `.public()` or non-RLS table),
1991
+ * and the FALSE sentinel (replicate nothing) when a `.rls("required")` schema
1992
+ * exposes a protected, policy-less table.
1993
+ */
1994
1994
  declare const composeShapeReadWhere: (registry: RlsReadRegistry, request: ShapeReadWhereRequest) => WhereInput;
1995
1995
  /**
1996
- * A shape declaration. `where` receives the trusted procedure context and the
1997
- * validated client args and returns the same {@link WhereInput} shape the RLS
1998
- * DSL uses, so the DO can AND-merge it with the table's read base-where via the
1999
- * existing where-compiler (zero second predicate implementation).
2000
- */
1996
+ * A shape declaration. `where` receives the trusted procedure context and the
1997
+ * validated client args and returns the same {@link WhereInput} shape the RLS
1998
+ * DSL uses, so the DO can AND-merge it with the table's read base-where via the
1999
+ * existing where-compiler (zero second predicate implementation).
2000
+ */
2001
2001
  interface ShapeDefinition<Args extends ValidatorMap = ValidatorMap, Context = QueryCtx> {
2002
2002
  /**
2003
- * Validator for the client-supplied shape parameters. Validated on the DO
2004
- * before `where` runs, so a malformed `args` envelope is rejected at the
2005
- * subscription boundary rather than silently widening the partition. Omit
2006
- * for a parameterless shape.
2007
- */
2003
+ * Validator for the client-supplied shape parameters. Validated on the DO
2004
+ * before `where` runs, so a malformed `args` envelope is rejected at the
2005
+ * subscription boundary rather than silently widening the partition. Omit
2006
+ * for a parameterless shape.
2007
+ */
2008
2008
  readonly args?: Args;
2009
2009
  /**
2010
- * Project the replicated rows to these columns (the system columns `_id` and
2011
- * `_creationTime` are always included). Omit to replicate every column. An
2012
- * empty array is rejected — it would replicate no data, which is never the
2013
- * intent.
2014
- */
2010
+ * Project the replicated rows to these columns (the system columns `_id` and
2011
+ * `_creationTime` are always included). Omit to replicate every column. An
2012
+ * empty array is rejected — it would replicate no data, which is never the
2013
+ * intent.
2014
+ */
2015
2015
  readonly columns?: ReadonlyArray<string>;
2016
2016
  /** Logical table this shape replicates a partition of. */
2017
2017
  readonly table: string;
2018
2018
  /**
2019
- * Predicate selecting the rows this shape replicates. AND-composed with the
2020
- * table's RLS read base-where on the DO. Runs server-side with a trusted
2021
- * `ctx` (identity/auth the client can't forge) and the validated client
2022
- * `args`; returns a {@link WhereInput} using the same operator set as the
2023
- * SQL compiler (`eq`/`in`/`lt`/… + `AND`/`OR`/`NOT`).
2024
- */
2019
+ * Predicate selecting the rows this shape replicates. AND-composed with the
2020
+ * table's RLS read base-where on the DO. Runs server-side with a trusted
2021
+ * `ctx` (identity/auth the client can't forge) and the validated client
2022
+ * `args`; returns a {@link WhereInput} using the same operator set as the
2023
+ * SQL compiler (`eq`/`in`/`lt`/… + `AND`/`OR`/`NOT`).
2024
+ */
2025
2025
  readonly where: (context: Context, args: InferValidatorMap<Args>) => WhereInput;
2026
2026
  }
2027
2027
  /** A {@link ShapeDefinition} plus the codegen discovery marker and a dispatch-shaped `compileWhere`. */
2028
2028
  interface RegisteredShape<Args extends ValidatorMap = ValidatorMap, Context = QueryCtx> extends ShapeDefinition<Args, Context> {
2029
2029
  readonly __lunoraShape: true;
2030
2030
  /**
2031
- * Validate `rawArgs`, then evaluate `where` under the trusted `ctx` and
2032
- * return its {@link WhereInput}. Used by the generated DO's `resolveShape`
2033
- * override: `ctx` is erased to `unknown` at this dispatch boundary (the DO
2034
- * builds it from the socket's verified identity and hands it back as the
2035
- * concrete {@link QueryContext} the predicate expects), exactly like
2036
- * `RegisteredLunoraFunction.handler` erases its context.
2037
- */
2031
+ * Validate `rawArgs`, then evaluate `where` under the trusted `ctx` and
2032
+ * return its {@link WhereInput}. Used by the generated DO's `resolveShape`
2033
+ * override: `ctx` is erased to `unknown` at this dispatch boundary (the DO
2034
+ * builds it from the socket's verified identity and hands it back as the
2035
+ * concrete {@link QueryContext} the predicate expects), exactly like
2036
+ * `RegisteredLunoraFunction.handler` erases its context.
2037
+ */
2038
2038
  readonly compileWhere: (context: unknown, rawArgs: Record<string, unknown>) => WhereInput;
2039
2039
  }
2040
2040
  /** Declare a replication shape. See the module docs for runtime semantics. */
2041
2041
  declare const defineShape: <Args extends ValidatorMap = ValidatorMap, Context = QueryCtx>(definition: ShapeDefinition<Args, Context>) => RegisteredShape<Args, Context>;
2042
2042
  /**
2043
- * Operations a storage rule can gate. `read` covers `download` / `getMetadata`
2044
- * / `getSignedUrl` / `getUrl`; `write` covers `store` / `generateUploadUrl`;
2045
- * `delete` is `delete`; `list` is a prefix listing (governed via the file
2046
- * browser / admin path, not `ctx.storage` which has no `list`).
2047
- */
2043
+ * Operations a storage rule can gate. `read` covers `download` / `getMetadata`
2044
+ * / `getSignedUrl` / `getUrl`; `write` covers `store` / `generateUploadUrl`;
2045
+ * `delete` is `delete`; `list` is a prefix listing (governed via the file
2046
+ * browser / admin path, not `ctx.storage` which has no `list`).
2047
+ */
2048
2048
  type StorageOperation = "delete" | "list" | "read" | "write";
2049
2049
  /** A rule's decision. `true` allows, `false` denies, `undefined` opts this rule out. */
2050
2050
  type StorageRuleDecision = boolean | undefined;
2051
2051
  /**
2052
- * Context handed to a storage rule. `auth` mirrors RLS's `PolicyContext.auth`
2053
- * (the per-request userId / roles / identity and the `can(permission)` helper),
2054
- * so a rule reads `({ auth, key }) => key.startsWith(`user/${auth.userId}/`)`.
2055
- * `key` is the object key the operation targets (for `list`, the listing
2056
- * prefix). `ctx` is the full procedure context the middleware closed over.
2057
- */
2052
+ * Context handed to a storage rule. `auth` mirrors RLS's `PolicyContext.auth`
2053
+ * (the per-request userId / roles / identity and the `can(permission)` helper),
2054
+ * so a rule reads `({ auth, key }) => key.startsWith(`user/${auth.userId}/`)`.
2055
+ * `key` is the object key the operation targets (for `list`, the listing
2056
+ * prefix). `ctx` is the full procedure context the middleware closed over.
2057
+ */
2058
2058
  interface StorageRuleContext<Context = unknown> {
2059
2059
  readonly auth: {
2060
2060
  readonly can: (permission: Permission | string) => boolean;
@@ -2069,11 +2069,11 @@ interface StorageRuleContext<Context = unknown> {
2069
2069
  /** A registered storage rule as stored in the rule table. */
2070
2070
  interface StorageRule<Context = unknown> {
2071
2071
  /**
2072
- * Logical bucket the rule governs — matched against the accessor's bucket
2073
- * (`ctx.storage.bucketName`, or the bucket selected via `ctx.storage.bucket(name)`).
2074
- * A rule only applies to operations on its own bucket. The unnamed bucket is
2075
- * `"default"`. Also surfaced in the studio's access-rules view.
2076
- */
2072
+ * Logical bucket the rule governs — matched against the accessor's bucket
2073
+ * (`ctx.storage.bucketName`, or the bucket selected via `ctx.storage.bucket(name)`).
2074
+ * A rule only applies to operations on its own bucket. The unnamed bucket is
2075
+ * `"default"`. Also surfaced in the studio's access-rules view.
2076
+ */
2077
2077
  readonly bucket: string;
2078
2078
  readonly on: StorageOperation;
2079
2079
  /** Optional key-prefix scope; the rule only governs keys under it. Absent ⇒ the whole bucket. */
@@ -2088,27 +2088,27 @@ interface DefineStorageRuleInput<Context = unknown> {
2088
2088
  when: (context: StorageRuleContext<Context>) => StorageRuleDecision;
2089
2089
  }
2090
2090
  /**
2091
- * Options for the `storageRules(rules, options)` middleware. `roles` registers
2092
- * the role→permission grants that back `ctx.auth.can(...)`, exactly as RLS's
2093
- * `RlsOptions.roles` does — fail-closed for unlisted roles.
2094
- */
2091
+ * Options for the `storageRules(rules, options)` middleware. `roles` registers
2092
+ * the role→permission grants that back `ctx.auth.can(...)`, exactly as RLS's
2093
+ * `RlsOptions.roles` does — fail-closed for unlisted roles.
2094
+ */
2095
2095
  interface StorageRulesOptions {
2096
2096
  readonly roles?: ReadonlyArray<Role>;
2097
2097
  }
2098
2098
  declare const defineStorageRule: <Context = unknown>(input: DefineStorageRuleInput<Context>) => StorageRule<Context>;
2099
2099
  /**
2100
- * Collect a list of storage rules into the structure the `storageRules()`
2101
- * middleware consumes. Multiple rules for the same `(bucket, on)` OR together —
2102
- * any one allowing grants the operation (each rule grants a slice of the
2103
- * keyspace).
2104
- *
2105
- * Validates against an **accidentally duplicated rule** — the same
2106
- * `(bucket, on, prefix)` registered with the *same* decision function (a
2107
- * copy-paste, or the same rule object spread in twice). Because multiple
2108
- * DISTINCT rules per `(bucket, on)` are intentional, the check keys on the
2109
- * `when` reference too. Throws at module load so the misconfiguration surfaces
2110
- * immediately rather than as a silently double-evaluated predicate.
2111
- */
2100
+ * Collect a list of storage rules into the structure the `storageRules()`
2101
+ * middleware consumes. Multiple rules for the same `(bucket, on)` OR together —
2102
+ * any one allowing grants the operation (each rule grants a slice of the
2103
+ * keyspace).
2104
+ *
2105
+ * Validates against an **accidentally duplicated rule** — the same
2106
+ * `(bucket, on, prefix)` registered with the *same* decision function (a
2107
+ * copy-paste, or the same rule object spread in twice). Because multiple
2108
+ * DISTINCT rules per `(bucket, on)` are intentional, the check keys on the
2109
+ * `when` reference too. Throws at module load so the misconfiguration surfaces
2110
+ * immediately rather than as a silently double-evaluated predicate.
2111
+ */
2112
2112
  declare const defineStorageRules: <Context = unknown>(rules: ReadonlyArray<StorageRule<Context>>) => ReadonlyArray<StorageRule<Context>>;
2113
2113
  /** 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`). */
2114
2114
  type StorageAuthLike = {