@lunora/server 1.0.0-alpha.83 → 1.0.0-alpha.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +1620 -1482
- package/dist/index.d.ts +1620 -1482
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/DOCUMENT_HISTORY_REDACTED_FIELDS-B5M6QNiq.mjs +1 -0
- package/dist/packem_shared/flushDeferredDeletes-DEJXlhop.mjs +1 -0
- package/dist/types.d.mts +21 -2
- package/dist/types.d.ts +21 -2
- package/package.json +4 -4
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Validator, Infer, ValidatorMap, InferValidatorMap, ColumnValidator
|
|
1
|
+
import { Validator, Infer, v, ValidatorMap, InferValidatorMap, ColumnValidator } from '@lunora/values';
|
|
2
2
|
export { type ColumnValidator, type GeoPoint, type Id, type Infer, ValidationError, type Validator, type ValidatorKind, v } from '@lunora/values';
|
|
3
|
-
import { ArgsValidator, InferArgs, RegisteredAction, ExposeConfig, X402ProcedureConfig, ActionCtx, MutationCtx, RegisteredMutation, QueryCtx, RegisteredQuery, DurableStreamOptions, RegisteredStream, FunctionKind, Secrets,
|
|
4
|
-
export { type AnyApi, type AuthState, type CachePurge, type DatabaseReader, type DatabaseWriter, type FunctionVisibility, type GeoBoundingBox, type GeoFilterBuilder, type GeoIndexDefinition, type GeoPointInput, type IndexDefinition, type IndexRangeBuilder, type LifecycleEventKind, type LogFields, type LunoraLogMethod, type LunoraLogger, type LunoraMetrics, type LunoraTracer, type LunoraWideEvent, type PaginationOptions, type PaginationResult, type RankSortKey, type ReadOnlyStorage, type RestCacheConfig, type RunQueryOptions, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type SearchFilterBuilder, type SearchIndexDefinition, type ShardMode, type SpanEvaluation, type SpanHandle, type SpanKind, type SpanLink, type SpanOptions, type Storage, type StorageMetadata, type StorageObjectHead, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerCtx, type TriggerDatabase, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type TtlDefinition, type VectorMatch, type VectorMatches, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowEventDefinition, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi } from "./types.mjs";
|
|
3
|
+
import { ArgsValidator, InferArgs, RegisteredAction, ExposeConfig, X402ProcedureConfig, ActionCtx, MutationCtx, RegisteredMutation, QueryCtx, RegisteredQuery, DurableStreamOptions, RegisteredStream, FunctionKind, Secrets, TableDefinition, RegisteredFunction, VectorIndexDefinition, Schema, AggregateOp, GlobalBackend, RelationDefinition, OnDeleteAction, SearchLanguage, SearchStrategy, ExternalSourceDefinition, TriggerBuilder, TriggerDefinition, VectorEmbedder, VectorMetric, DurableObjectJurisdiction, AggregateIndexDefinition, RankIndexDefinition, LifecycleEvent, ShardInitEvent, RegisteredLifecycleHook } from "./types.mjs";
|
|
4
|
+
export { type AnyApi, type AuthState, type CachePurge, type DatabaseReader, type DatabaseWriter, type FunctionVisibility, type GeoBoundingBox, type GeoFilterBuilder, type GeoIndexDefinition, type GeoPointInput, type IndexDefinition, type IndexRangeBuilder, type LifecycleEventKind, type LogFields, type LunoraLogMethod, type LunoraLogger, type LunoraMetrics, type LunoraTracer, type LunoraWideEvent, type MutationStorage, type PaginationOptions, type PaginationResult, type RankSortKey, type ReadOnlyStorage, type RestCacheConfig, type RunQueryOptions, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type SearchFilterBuilder, type SearchIndexDefinition, type ShardMode, type SpanEvaluation, type SpanHandle, type SpanKind, type SpanLink, type SpanOptions, type Storage, type StorageMetadata, type StorageObjectHead, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerCtx, type TriggerDatabase, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type TtlDefinition, type VectorMatch, type VectorMatches, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowEventDefinition, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi } from "./types.mjs";
|
|
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';
|
|
@@ -319,1657 +319,1795 @@ declare const initLunora: {
|
|
|
319
319
|
*/
|
|
320
320
|
declare const createSecrets: (env: Record<string, unknown>) => Secrets;
|
|
321
321
|
/**
|
|
322
|
-
*
|
|
323
|
-
* whose contents look like a credential (so a value surfaced as `received string
|
|
324
|
-
* "sk_live_…"` is masked even though the surrounding text is not a token); a
|
|
325
|
-
* `scheme://user:password@host` URL credential (the password segment); any
|
|
326
|
-
* known-prefix credential token wherever it appears, at any length; any value
|
|
327
|
-
* following a secret-named key in `KEY=value` / `KEY: value` form; and any
|
|
328
|
-
* remaining bare high-entropy ≥24-char token run anywhere in the message.
|
|
322
|
+
* Wrap a storage facade so it also accepts `deleteAfterCommit(key)`.
|
|
329
323
|
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
|
|
334
|
-
|
|
324
|
+
* Call once per dispatch, outside any read-stamping wrapper: `bucket()` delegates
|
|
325
|
+
* to the wrapped facade, so a sub-facade is still stamped by whatever wrapped it.
|
|
326
|
+
* @param storage the bucket-aware facade from `asBucketStorage`
|
|
327
|
+
*/
|
|
328
|
+
declare const withDeferredDeletes: (storage: unknown) => unknown;
|
|
329
|
+
/** How a flush ended, so a caller can assert on it without this module owning a logger. */
|
|
330
|
+
interface DeferredDeleteFlushResult {
|
|
331
|
+
/** How many keys were attempted. */
|
|
332
|
+
attempted: number;
|
|
333
|
+
/** Keys whose delete did not happen, with the reason. Empty when everything succeeded. */
|
|
334
|
+
failures: {
|
|
335
|
+
error: unknown;
|
|
336
|
+
key: string;
|
|
337
|
+
}[];
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Delete everything queued on `context.storage`, draining the queue as it goes,
|
|
341
|
+
* and report each failure through `context.log`.
|
|
335
342
|
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
343
|
+
* Draining first means a second flush of the same dispatch is a no-op rather than
|
|
344
|
+
* a second round of deletes.
|
|
345
|
+
*
|
|
346
|
+
* Never throws. A flush runs after the write committed, so there is no longer
|
|
347
|
+
* anything to fail into: rejecting here could only turn a leaked object into a
|
|
348
|
+
* failed response for a write that already succeeded. Failures are logged and
|
|
349
|
+
* also returned, so a dispatch can call this and ignore the result.
|
|
350
|
+
* @param context the dispatch context; a context with no queued deletes is a no-op
|
|
338
351
|
*/
|
|
339
|
-
declare const
|
|
340
|
-
/** One key's validation failure, secrets already redacted out of `message`. */
|
|
341
|
-
interface EnvKeyFailure {
|
|
342
|
-
/** The env key that failed. */
|
|
343
|
-
key: string;
|
|
344
|
-
/** Redacted human-readable reason. */
|
|
345
|
-
message: string;
|
|
346
|
-
}
|
|
352
|
+
declare const flushDeferredDeletes: (context: unknown) => Promise<DeferredDeleteFlushResult>;
|
|
347
353
|
/**
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
* Named export only (no default) per the repo export convention.
|
|
354
|
+
* The prefixed tables a single plugin `P` contributes, or an empty map when it
|
|
355
|
+
* ships no schema extension. Mirrors {@link PrefixedTables} at the plugin level
|
|
356
|
+
* so {@link InstalledTables} can fold a tuple of plugins.
|
|
353
357
|
*/
|
|
354
|
-
|
|
355
|
-
readonly
|
|
356
|
-
|
|
358
|
+
type ExtensionTablesOf<P> = P extends {
|
|
359
|
+
readonly extension: SchemaExtension<infer X> & {
|
|
360
|
+
readonly key: infer K;
|
|
361
|
+
};
|
|
362
|
+
} ? K extends string ? PrefixedTables<X, K> : Record<never, never> : Record<never, never>;
|
|
363
|
+
/**
|
|
364
|
+
* Fold a tuple of plugins onto a base table map `T`, accumulating each plugin's
|
|
365
|
+
* auto-prefixed extension tables left-to-right — the type-level mirror of
|
|
366
|
+
* {@link installPlugins} applying `mergeSchemaExtension` for each plugin in turn.
|
|
367
|
+
*/
|
|
368
|
+
type InstalledTables<T extends Record<string, TableDefinition>, Plugins extends ReadonlyArray<unknown>> = Plugins extends readonly [infer Head, ...infer Rest] ? InstalledTables<ExtensionTablesOf<Head> & T, Rest> : T;
|
|
369
|
+
/**
|
|
370
|
+
* Union every plugin's `ContextOut` in a tuple — the type-level mirror of the
|
|
371
|
+
* `ctx.api.<key>` additions {@link composePluginMiddleware} accumulates as each
|
|
372
|
+
* plugin middleware runs. Independent of the incoming context, which the builder
|
|
373
|
+
* infers at the `.use(...)` site.
|
|
374
|
+
*/
|
|
375
|
+
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;
|
|
376
|
+
/**
|
|
377
|
+
* Schema fragment a plugin contributes. Same shape as the `tables` map
|
|
378
|
+
* passed to `defineSchema`. Optional `vectorIndexes` mirror the top-level
|
|
379
|
+
* `defineSchema` argument so a plugin can ship vector decls alongside its
|
|
380
|
+
* tables.
|
|
381
|
+
*/
|
|
382
|
+
interface SchemaExtension<T extends Record<string, TableDefinition> = Record<string, TableDefinition>> {
|
|
383
|
+
/** Stable key identifying the plugin that owns this extension. */
|
|
384
|
+
readonly key: string;
|
|
385
|
+
/**
|
|
386
|
+
* Extension tables, keyed by **bare** name (e.g. `buckets`). At merge time
|
|
387
|
+
* each is auto-prefixed with `key` (`ratelimit_buckets`) so it can't
|
|
388
|
+
* collide with an app table; do **not** namespace manually.
|
|
389
|
+
*/
|
|
390
|
+
readonly tables: T;
|
|
391
|
+
/**
|
|
392
|
+
* Optional standalone vector indexes the plugin ships, keyed by index
|
|
393
|
+
* name. Merged into the host schema's `vectorIndexes`; a key collision
|
|
394
|
+
* with the base schema is a hard error (same policy as tables).
|
|
395
|
+
*/
|
|
396
|
+
readonly vectorIndexes?: Record<string, VectorIndexDefinition>;
|
|
357
397
|
}
|
|
358
|
-
/** A record of `v.*` validators describing the expected env shape. */
|
|
359
|
-
type EnvShape = Record<string, Validator>;
|
|
360
398
|
/**
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
* derives an args object from a validator map.
|
|
399
|
+
* Build a {@link SchemaExtension}. The `key` is a runtime tag (used for
|
|
400
|
+
* error messages on collision) and a type-level brand.
|
|
364
401
|
*/
|
|
365
|
-
|
|
402
|
+
declare const defineSchemaExtension: <T extends Record<string, TableDefinition>>(key: string, options: {
|
|
403
|
+
tables: T;
|
|
404
|
+
vectorIndexes?: Record<string, VectorIndexDefinition>;
|
|
405
|
+
}) => SchemaExtension<T>;
|
|
366
406
|
/**
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
407
|
+
* A plugin packages an optional schema extension and optional middleware.
|
|
408
|
+
* Both are independently usable: an app can install only the schema (e.g.
|
|
409
|
+
* for plugins that ship background workers but no per-request behavior)
|
|
410
|
+
* or only the middleware (plugins that augment ctx without persistent
|
|
411
|
+
* state).
|
|
372
412
|
*/
|
|
373
|
-
interface
|
|
374
|
-
/**
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
413
|
+
interface Plugin<TExtension extends Record<string, TableDefinition> = Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn> {
|
|
414
|
+
/**
|
|
415
|
+
* Optional schema extension. Apps install via
|
|
416
|
+
* `defineSchema(...).extend(plugin.extension)`.
|
|
417
|
+
*/
|
|
418
|
+
readonly extension?: SchemaExtension<TExtension>;
|
|
419
|
+
/** Stable key identifying the plugin. Matches `extension.key` when set. */
|
|
420
|
+
readonly key: string;
|
|
421
|
+
/**
|
|
422
|
+
* Optional middleware. Users attach with `c.query.use(plugin.middleware)`.
|
|
423
|
+
* The middleware can extend `ctx`; convention is to attach helpers under
|
|
424
|
+
* `ctx.api.<key>`, e.g.
|
|
425
|
+
*
|
|
426
|
+
* ```ts
|
|
427
|
+
* middleware: ({ ctx, next }) =>
|
|
428
|
+
* next({ ctx: { api: { ...ctx.api, ratelimit: api } } })
|
|
429
|
+
* ```
|
|
430
|
+
*/
|
|
431
|
+
readonly middleware?: Middleware<TContextIn, TContextOut>;
|
|
432
|
+
}
|
|
433
|
+
/** Options to {@link definePlugin}. */
|
|
434
|
+
interface DefinePluginOptions<TExtension extends Record<string, TableDefinition>, TContextIn, TContextOut> {
|
|
435
|
+
extension?: SchemaExtension<TExtension>;
|
|
436
|
+
middleware?: Middleware<TContextIn, TContextOut>;
|
|
378
437
|
}
|
|
379
438
|
/**
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
439
|
+
* Call signatures for {@link definePlugin}. When `extension` is supplied the
|
|
440
|
+
* returned plugin's `extension` is typed as PRESENT (not `?`), so the
|
|
441
|
+
* canonical install pattern `defineSchema(...).extend(plugin.extension)`
|
|
442
|
+
* typechecks without a non-null assertion — the shape every scaffold template
|
|
443
|
+
* ships. The bare-options signature keeps `extension` optional for plugins
|
|
444
|
+
* that carry only middleware.
|
|
445
|
+
*/
|
|
446
|
+
interface DefinePluginFunction {
|
|
447
|
+
<TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut> & {
|
|
448
|
+
extension: SchemaExtension<TExtension>;
|
|
449
|
+
}): Plugin<TExtension, TContextIn, TContextOut> & {
|
|
450
|
+
readonly extension: SchemaExtension<TExtension>;
|
|
451
|
+
};
|
|
452
|
+
<TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut>): Plugin<TExtension, TContextIn, TContextOut>;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Package a schema extension + middleware as a reusable plugin. Either
|
|
456
|
+
* field is optional — `definePlugin("foo", {})` is valid but degenerate.
|
|
457
|
+
*/
|
|
458
|
+
declare const definePlugin: DefinePluginFunction;
|
|
459
|
+
/**
|
|
460
|
+
* Bundle of registered functions a {@link Component} ships. Keys are the
|
|
461
|
+
* function's local name (e.g. `check`, `reset`); the registered function
|
|
462
|
+
* value carries its own kind / args / handler.
|
|
463
|
+
*
|
|
464
|
+
* Users re-export from their own lunora module so codegen picks them up:
|
|
383
465
|
*
|
|
384
466
|
* ```ts
|
|
385
|
-
*
|
|
467
|
+
* // lunora/ratelimit.ts
|
|
468
|
+
* import { ratelimit } from "@vendor/ratelimit-component";
|
|
469
|
+
* export const { check, reset } = ratelimit.functions;
|
|
470
|
+
* // Emits as `ratelimit:check` / `ratelimit:reset` in the generated `api`.
|
|
471
|
+
* ```
|
|
386
472
|
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
|
|
473
|
+
* Codegen follows the re-export back to the bundled `query/mutation/action`
|
|
474
|
+
* call (property access or destructuring both work), so the functions land in
|
|
475
|
+
* the generated `api` under the re-exporting file's namespace.
|
|
476
|
+
*/
|
|
477
|
+
type ComponentFunctions = Readonly<Record<string, RegisteredFunction<any, any, FunctionKind>>>;
|
|
478
|
+
/**
|
|
479
|
+
* Component = {@link Plugin} with a bundle of registered functions. The
|
|
480
|
+
* extension + middleware + functions are independent: a component can ship
|
|
481
|
+
* functions without a schema (e.g. a stateless utility), or a schema
|
|
482
|
+
* without functions (e.g. shared table definitions), and any combination.
|
|
483
|
+
*/
|
|
484
|
+
interface Component<TExtension extends Record<string, TableDefinition> = Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn, F extends ComponentFunctions = ComponentFunctions> extends Plugin<TExtension, TContextIn, TContextOut> {
|
|
485
|
+
readonly functions: F;
|
|
486
|
+
}
|
|
487
|
+
interface DefineComponentOptions<TExtension extends Record<string, TableDefinition>, TContextIn, TContextOut, F extends ComponentFunctions> extends DefinePluginOptions<TExtension, TContextIn, TContextOut> {
|
|
488
|
+
/** Registered functions the component ships. Keys are the function's local name. */
|
|
489
|
+
functions?: F;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Convenience wrapper around {@link definePlugin} that also bundles a set
|
|
493
|
+
* of registered functions. The resulting `component.functions` object is a
|
|
494
|
+
* record of `name → registered query/mutation/action`; consumers
|
|
495
|
+
* re-export entries so codegen discovers them as user functions:
|
|
391
496
|
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
497
|
+
* ```ts
|
|
498
|
+
* export const ratelimit = defineComponent("ratelimit", {
|
|
499
|
+
* // Bare `buckets` merges in as `ratelimit_buckets`.
|
|
500
|
+
* extension: defineSchemaExtension("ratelimit", { tables: { buckets } }),
|
|
501
|
+
* middleware: ({ ctx, next }) => next({ ctx: { ...ctx, ratelimit: api(ctx) } }),
|
|
502
|
+
* functions: {
|
|
503
|
+
* check: query.input({ key: v.string() }).query(async ({ ctx, args }) => ...),
|
|
504
|
+
* reset: mutation.input({ key: v.string() }).mutation(async ({ ctx, args }) => ...),
|
|
396
505
|
* },
|
|
397
|
-
* };
|
|
506
|
+
* });
|
|
398
507
|
* ```
|
|
399
508
|
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
509
|
+
* Re-exporting an entry (by property access or destructuring) is enough for
|
|
510
|
+
* codegen to discover it in the host app's namespace — the discovery resolver
|
|
511
|
+
* chases the re-export back to the bundled registration call.
|
|
402
512
|
*/
|
|
403
|
-
declare const
|
|
404
|
-
declare class LunoraError extends LunoraError$1 {
|
|
405
|
-
constructor(code: LunoraErrorCode, message?: string, data?: unknown);
|
|
406
|
-
}
|
|
513
|
+
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>;
|
|
407
514
|
/**
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
* stays assignable under bivariant parameter checking. That is the whole reason
|
|
412
|
-
* the shared helper can serve both callers, hence the rule exemption.
|
|
515
|
+
* Map every key `K` of an extension's table map `X` to its auto-prefixed name
|
|
516
|
+
* `${Key}_${K}`. Mirrors the runtime prefixing in {@link mergeSchemaExtension}
|
|
517
|
+
* so the typed `.extend(...)` chain reflects the real merged table names.
|
|
413
518
|
*/
|
|
414
|
-
|
|
415
|
-
aggregate(tableName: string, options: unknown): Promise<unknown>;
|
|
416
|
-
count(tableName: string, where?: unknown): Promise<number>;
|
|
417
|
-
delete(id: string, expectedTable?: string, options?: {
|
|
418
|
-
hard?: boolean;
|
|
419
|
-
}): Promise<void>;
|
|
420
|
-
deleteMany?(ids: ReadonlyArray<string>, options?: {
|
|
421
|
-
limit?: number;
|
|
422
|
-
}, expectedTable?: string): Promise<{
|
|
423
|
-
deleted: number;
|
|
424
|
-
}>;
|
|
425
|
-
deleteWhere?(tableName: string, where: Record<string, unknown>, options?: {
|
|
426
|
-
limit?: number;
|
|
427
|
-
}): Promise<{
|
|
428
|
-
deleted: number;
|
|
429
|
-
}>;
|
|
430
|
-
findFirst(tableName: string, args?: unknown): Promise<unknown>;
|
|
431
|
-
findFirstOrThrow(tableName: string, args?: unknown): Promise<unknown>;
|
|
432
|
-
findMany(tableName: string, args?: unknown): Promise<unknown>;
|
|
433
|
-
get(id: string, expectedTable?: string): Promise<unknown>;
|
|
434
|
-
groupBy(tableName: string, options: unknown): Promise<unknown>;
|
|
435
|
-
insert(tableName: string, document: Record<string, unknown>): Promise<string>;
|
|
436
|
-
insertMany?(tableName: string, documents: ReadonlyArray<Record<string, unknown>>, options?: {
|
|
437
|
-
limit?: number;
|
|
438
|
-
skipDuplicates?: boolean;
|
|
439
|
-
}): Promise<(string | null)[]>;
|
|
440
|
-
patch(id: string, patch: Record<string, unknown>, expectedTable?: string): Promise<void>;
|
|
441
|
-
patchMany?(patches: ReadonlyArray<{
|
|
442
|
-
id: string;
|
|
443
|
-
patch: Record<string, unknown>;
|
|
444
|
-
}>, options?: {
|
|
445
|
-
limit?: number;
|
|
446
|
-
}, expectedTable?: string): Promise<{
|
|
447
|
-
patched: number;
|
|
448
|
-
}>;
|
|
449
|
-
patchWhere?(tableName: string, args: {
|
|
450
|
-
patch: Record<string, unknown>;
|
|
451
|
-
where: Record<string, unknown>;
|
|
452
|
-
}, options?: {
|
|
453
|
-
limit?: number;
|
|
454
|
-
}): Promise<{
|
|
455
|
-
patched: number;
|
|
456
|
-
}>;
|
|
457
|
-
query(tableName: string): {
|
|
458
|
-
withGeoIndex(indexName: string, build: (q: unknown) => unknown): unknown;
|
|
459
|
-
withSearchIndex(indexName: string, search: (q: unknown) => unknown): unknown;
|
|
460
|
-
};
|
|
461
|
-
rank(tableName: string, indexName: string, options: unknown): Promise<unknown>;
|
|
462
|
-
rankPage(tableName: string, indexName: string, options?: unknown): Promise<unknown>;
|
|
463
|
-
replace(id: string, document: Record<string, unknown>, expectedTable?: string): Promise<void>;
|
|
464
|
-
restore?(id: string, expectedTable?: string): Promise<void>;
|
|
465
|
-
}
|
|
466
|
-
/** The per-table accessor object returned for the `ctx.db` table form. */
|
|
467
|
-
interface FacadeEntry {
|
|
468
|
-
aggregate: (options: unknown) => Promise<unknown>;
|
|
469
|
-
count: (where?: unknown) => Promise<number>;
|
|
470
|
-
delete: (id: string) => Promise<void>;
|
|
471
|
-
deleteMany: {
|
|
472
|
-
(ids: ReadonlyArray<string>, options?: {
|
|
473
|
-
limit?: number;
|
|
474
|
-
}): Promise<{
|
|
475
|
-
deleted: number;
|
|
476
|
-
}>;
|
|
477
|
-
(args: {
|
|
478
|
-
limit?: number;
|
|
479
|
-
where: Record<string, unknown>;
|
|
480
|
-
}): Promise<{
|
|
481
|
-
deleted: number;
|
|
482
|
-
}>;
|
|
483
|
-
};
|
|
484
|
-
/** `true` when at least one row matches `where` (or any row exists when omitted). Honors RLS like `findFirst`. */
|
|
485
|
-
exists: (where?: unknown) => Promise<boolean>;
|
|
486
|
-
findFirst: (args?: unknown) => Promise<unknown>;
|
|
487
|
-
findFirstOrThrow: (args?: unknown) => Promise<unknown>;
|
|
488
|
-
findMany: (args?: unknown) => Promise<unknown>;
|
|
489
|
-
get: (id: string) => Promise<unknown>;
|
|
490
|
-
groupBy: (options: unknown) => Promise<unknown>;
|
|
491
|
-
/** Physically remove a row (and physically cascade), bypassing `.softDelete()`. */
|
|
492
|
-
hardDelete: (id: string) => Promise<void>;
|
|
493
|
-
insert: (document: Record<string, unknown>, options?: FacadeInsertOptions) => Promise<null | string>;
|
|
494
|
-
/**
|
|
495
|
-
* Insert many documents into this table in one call. With
|
|
496
|
-
* `{ skipDuplicates: true }`, UNIQUE breaches resolve to `null` for that row
|
|
497
|
-
* instead of failing the batch. The typed facade narrows the return to
|
|
498
|
-
* `Id<T>[]` when skipDuplicates is not requested.
|
|
499
|
-
*/
|
|
500
|
-
insertMany: (documents: ReadonlyArray<Record<string, unknown>>, options?: {
|
|
501
|
-
limit?: number;
|
|
502
|
-
skipDuplicates?: boolean;
|
|
503
|
-
}) => Promise<(string | null)[]>;
|
|
504
|
-
patch: (id: string, patch: Record<string, unknown>) => Promise<void>;
|
|
505
|
-
patchMany: {
|
|
506
|
-
(patches: ReadonlyArray<{
|
|
507
|
-
id: string;
|
|
508
|
-
values: Record<string, unknown>;
|
|
509
|
-
}>, options?: {
|
|
510
|
-
limit?: number;
|
|
511
|
-
}): Promise<{
|
|
512
|
-
patched: number;
|
|
513
|
-
}>;
|
|
514
|
-
(args: {
|
|
515
|
-
limit?: number;
|
|
516
|
-
values: Record<string, unknown>;
|
|
517
|
-
where: Record<string, unknown>;
|
|
518
|
-
}): Promise<{
|
|
519
|
-
patched: number;
|
|
520
|
-
}>;
|
|
521
|
-
};
|
|
522
|
-
rank: (indexName: string, options: unknown) => Promise<unknown>;
|
|
523
|
-
rankPage: (indexName: string, options?: unknown) => Promise<unknown>;
|
|
524
|
-
replace: (id: string, document: Record<string, unknown>) => Promise<void>;
|
|
525
|
-
/** Un-soft-delete a row: clears the `.softDelete()` marker (by-id, so it reaches a row list reads hide). */
|
|
526
|
-
restore: (id: string) => Promise<void>;
|
|
527
|
-
/** Insert when no row matches `target`, else patch the match. Composes `findFirst` + `insert`/`patch`, so RLS applies to each step. */
|
|
528
|
-
upsert: (args: UpsertArgs) => Promise<UpsertResult>;
|
|
529
|
-
/** Sequential `upsert` over many rows sharing one `target`; returns one result per input row in order. */
|
|
530
|
-
upsertMany: (args: UpsertManyArgs) => Promise<UpsertResult[]>;
|
|
531
|
-
withGeoIndex: (indexName: string, build: (q: unknown) => unknown) => unknown;
|
|
532
|
-
withSearchIndex: (indexName: string, search: (q: unknown) => unknown) => unknown;
|
|
533
|
-
}
|
|
534
|
-
/** Options accepted by the per-table `insert` accessor. */
|
|
535
|
-
interface FacadeInsertOptions {
|
|
536
|
-
/**
|
|
537
|
-
* When `true`, a UNIQUE-constraint breach is swallowed: the insert becomes a
|
|
538
|
-
* silent no-op and resolves to `null` instead of throwing a `CONFLICT`. Any
|
|
539
|
-
* other error still propagates. Mirrors better-drizzle's `create({ skipDuplicates })`.
|
|
540
|
-
*/
|
|
541
|
-
skipDuplicates?: boolean;
|
|
542
|
-
}
|
|
543
|
-
/** The conflict target for `upsert`/`upsertMany`: one field name or a tuple of them. */
|
|
544
|
-
type UpsertTarget = ReadonlyArray<string> | string;
|
|
545
|
-
/** Argument to the per-table `upsert` accessor. */
|
|
546
|
-
interface UpsertArgs {
|
|
547
|
-
/** Document inserted when no existing row matches the `target`. */
|
|
548
|
-
create: Record<string, unknown>;
|
|
549
|
-
/** Field(s) — typically a `.unique()` column or unique index — used to look up an existing row. */
|
|
550
|
-
target: UpsertTarget;
|
|
551
|
-
/** Patch applied when an existing row matches the `target`. Defaults to `create`. */
|
|
552
|
-
update?: Record<string, unknown>;
|
|
553
|
-
}
|
|
554
|
-
/** Result of an `upsert`: the row's id and whether it was freshly inserted (`true`) or updated (`false`). */
|
|
555
|
-
interface UpsertResult {
|
|
556
|
-
created: boolean;
|
|
557
|
-
id: string;
|
|
558
|
-
}
|
|
559
|
-
/** Argument to the per-table `upsertMany` accessor — a shared `target` plus per-row create/update payloads. */
|
|
560
|
-
interface UpsertManyArgs {
|
|
561
|
-
rows: ReadonlyArray<{
|
|
562
|
-
create: Record<string, unknown>;
|
|
563
|
-
update?: Record<string, unknown>;
|
|
564
|
-
}>;
|
|
565
|
-
target: UpsertTarget;
|
|
566
|
-
}
|
|
519
|
+
type PrefixedTables<X extends Record<string, TableDefinition>, Key extends string> = { [K in keyof X as K extends string ? `${Key}_${K}` : K]: X[K]; };
|
|
567
520
|
/**
|
|
568
|
-
*
|
|
521
|
+
* Merge a {@link SchemaExtension} into an existing schema. Returns a new
|
|
522
|
+
* schema object — never mutates the input.
|
|
569
523
|
*
|
|
570
|
-
*
|
|
571
|
-
* `
|
|
572
|
-
*
|
|
573
|
-
*
|
|
574
|
-
*
|
|
575
|
-
* mutate — a row in an unrelated table (IDOR). Writers that ignore the second
|
|
576
|
-
* argument keep their previous global behaviour; the scoping is opt-in via this
|
|
577
|
-
* forwarded name.
|
|
578
|
-
*/
|
|
579
|
-
declare const bindTableFacade: (writer: FacadeWriterLike, tableName: string) => FacadeEntry;
|
|
580
|
-
/** The kitcn-style `ctx.orm` namespace over a per-table facade map. */
|
|
581
|
-
interface OrmLike {
|
|
582
|
-
delete: (table: string, id: string) => Promise<void>;
|
|
583
|
-
insert: (table: string) => {
|
|
584
|
-
values: (document: Record<string, unknown>) => Promise<null | string>;
|
|
585
|
-
};
|
|
586
|
-
query: Record<string, FacadeEntry>;
|
|
587
|
-
replace: (table: string, id: string) => {
|
|
588
|
-
with: (document: Record<string, unknown>) => Promise<void>;
|
|
589
|
-
};
|
|
590
|
-
update: (table: string, id: string) => {
|
|
591
|
-
set: (values: Record<string, unknown>) => Promise<void>;
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
/** Build `ctx.orm` over a per-table facade map (table name → FacadeEntry). */
|
|
595
|
-
declare const bindOrm: (facade: Record<string, FacadeEntry>) => OrmLike;
|
|
596
|
-
/** HTTP verbs the typed {@link httpRoute} builder can bind to. */
|
|
597
|
-
type HttpMethod = "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT";
|
|
598
|
-
/**
|
|
599
|
-
* Context handed to an HTTP action handler. A narrower view of {@link ActionContext}:
|
|
600
|
-
* HTTP actions run in the worker (the "action runtime"), separate from the
|
|
601
|
-
* transactional store, so there is no direct `db` / `vectors` surface — reach the
|
|
602
|
-
* data layer through `runQuery` / `runMutation` / `runAction`, which forward to
|
|
603
|
-
* the owning shard. `db`'s absence is principled: an HTTP handler is not
|
|
604
|
-
* transactional.
|
|
524
|
+
* Extension tables are auto-namespaced: each bare table name is prefixed with
|
|
525
|
+
* the extension `key` (`buckets` → `ratelimit_buckets`), Convex-Components
|
|
526
|
+
* style, and every intra-extension reference (relation targets, aggregate /
|
|
527
|
+
* rank index `on`, standalone vector index `table`) is rewritten to match.
|
|
528
|
+
* References to base/app tables are left untouched.
|
|
605
529
|
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
609
|
-
*
|
|
610
|
-
* `.storage(...)`) on the generated app builder.
|
|
530
|
+
* Because each extension lives in its own `key` namespace, app↔component
|
|
531
|
+
* collisions are impossible. The only remaining hard error is two extensions
|
|
532
|
+
* sharing the same `key` and producing the same prefixed table (or vector
|
|
533
|
+
* index) name — silent shadow would let one plugin hijack another's data.
|
|
611
534
|
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
|
|
621
|
-
type HttpActionCtx = Pick<ActionCtx, "auth" | "cache" | "fetch" | "runAction" | "runMutation" | "runQuery"> & {
|
|
622
|
-
readonly scheduler?: ActionCtx["scheduler"];
|
|
623
|
-
readonly storage?: ActionCtx["storage"];
|
|
624
|
-
};
|
|
625
|
-
/** A raw handler wrapped by {@link httpAction}. Receives the raw request, returns the raw response. */
|
|
626
|
-
type HttpActionHandler = (context: HttpActionCtx, request: Request) => Promise<Response> | Response;
|
|
627
|
-
/**
|
|
628
|
-
* The hono {@link https://hono.dev | Hono} environment used by {@link httpRouter}.
|
|
629
|
-
* The runtime injects the per-request {@link HttpActionCtx} on the private
|
|
630
|
-
* `__lunoraCtx` binding; the router's lifting middleware promotes it to
|
|
631
|
-
* `c.var.lunora` so handlers can read it as a typed variable.
|
|
632
|
-
*/
|
|
633
|
-
interface LunoraHttpEnv {
|
|
634
|
-
Bindings: Record<string, unknown> & {
|
|
635
|
-
__lunoraCtx?: HttpActionCtx;
|
|
636
|
-
};
|
|
637
|
-
Variables: {
|
|
638
|
-
lunora: HttpActionCtx;
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
/** The hono app type {@link httpRouter} returns. */
|
|
642
|
-
type LunoraHttpApp = Hono<LunoraHttpEnv>;
|
|
643
|
-
/** A compiled route handler: a hono handler that resolves to a raw {@link Response}. */
|
|
644
|
-
type LunoraRouteHandler = (c: Context<LunoraHttpEnv>) => Promise<Response>;
|
|
645
|
-
/**
|
|
646
|
-
* Wrap a `(ctx, request) => Response` handler as a hono handler. The raw escape
|
|
647
|
-
* hatch — mount it with `app.all(path, httpAction(fn))`. `ctx` is the
|
|
648
|
-
* runtime-injected {@link HttpActionCtx} lifted into `c.var.lunora` by
|
|
649
|
-
* {@link httpRouter}; `request` is the underlying `c.req.raw`.
|
|
535
|
+
* Re-runs {@link validateIndexFields} against the merged table set before
|
|
536
|
+
* returning: `defineSchema` only validates the tables it was called with, so
|
|
537
|
+
* without this an extension-contributed index with a typo'd/out-of-shape
|
|
538
|
+
* field (or a duplicate name within one kind) would never be checked at all.
|
|
539
|
+
* Re-validating the whole merged set (base + prefixed extension tables) is
|
|
540
|
+
* cheap and idempotent for the base tables, which already passed this same
|
|
541
|
+
* check when the base schema was built. Both callers of this function —
|
|
542
|
+
* `withExtend.extend()` (`./schema`) and `installPlugins` (below) — get the
|
|
543
|
+
* re-validation for free from this single call site (plan 258 §4/§9 Q3).
|
|
650
544
|
*/
|
|
651
|
-
declare const
|
|
545
|
+
declare const mergeSchemaExtension: <T extends Record<string, TableDefinition>, X extends Record<string, TableDefinition>, Key extends string = string>(base: Schema<T>, extension: SchemaExtension<X> & {
|
|
546
|
+
readonly key: Key;
|
|
547
|
+
}) => Schema<PrefixedTables<X, Key> & T>;
|
|
652
548
|
/**
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
*
|
|
656
|
-
*
|
|
549
|
+
* Install several plugins' schema extensions in one call — the one-shot
|
|
550
|
+
* counterpart to chaining `defineSchema(...).extend(a).extend(b)`. Plugins
|
|
551
|
+
* without an `extension` (middleware-only) are skipped; tables from those that
|
|
552
|
+
* do are auto-prefixed and reference-rewritten exactly as
|
|
553
|
+
* {@link mergeSchemaExtension} does for a single `.extend(...)`.
|
|
657
554
|
*
|
|
658
555
|
* ```ts
|
|
659
|
-
* const
|
|
660
|
-
*
|
|
661
|
-
* app.post("/webhook", httpAction(onWebhook));
|
|
662
|
-
* app.get("/users/:id", getUser);
|
|
663
|
-
* export default createWorker({ httpRouter: app, ... });
|
|
556
|
+
* const schema = installPlugins(defineSchema({ todos }), [ratelimit, audit]);
|
|
557
|
+
* // → todos + ratelimit_* + audit_*
|
|
664
558
|
* ```
|
|
665
559
|
*
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
* `c.var.lunora` be silently `undefined` despite its non-optional type.
|
|
560
|
+
* Pair it with {@link composePluginMiddleware} to attach every plugin's
|
|
561
|
+
* middleware in a single `.use(...)`, so installing N plugins is two calls
|
|
562
|
+
* rather than N `.extend(...)` + N `.use(...)`.
|
|
670
563
|
*/
|
|
671
|
-
declare const
|
|
672
|
-
/** The `{ ctx, searchParams, body, params }` a typed route handler receives. */
|
|
673
|
-
interface HttpRouteHandlerOptions<SearchParams extends ArgsValidator, Body extends ArgsValidator, Params extends ArgsValidator> {
|
|
674
|
-
body: InferArgs<Body>;
|
|
675
|
-
ctx: HttpActionCtx;
|
|
676
|
-
params: InferArgs<Params>;
|
|
677
|
-
searchParams: InferArgs<SearchParams>;
|
|
678
|
-
}
|
|
564
|
+
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>>;
|
|
679
565
|
/**
|
|
680
|
-
*
|
|
681
|
-
*
|
|
682
|
-
*
|
|
683
|
-
* `
|
|
684
|
-
*
|
|
685
|
-
|
|
686
|
-
interface HttpStreamHandlerOptions<SearchParams extends ArgsValidator, Params extends ArgsValidator> {
|
|
687
|
-
ctx: HttpActionCtx;
|
|
688
|
-
params: InferArgs<Params>;
|
|
689
|
-
request: Request;
|
|
690
|
-
searchParams: InferArgs<SearchParams>;
|
|
691
|
-
signal: AbortSignal;
|
|
692
|
-
}
|
|
693
|
-
/**
|
|
694
|
-
* A typed REST route under construction. `.searchParams()` / `.body()` /
|
|
695
|
-
* `.params()` accumulate validator maps (later calls merge, a colliding key
|
|
696
|
-
* wins) that decode the URL query, JSON body, and hono path params into the
|
|
697
|
-
* handler's typed `searchParams` / `body` / `params`. Like the procedure
|
|
698
|
-
* builder, `.output(validator)` defaults to the `undefined` sentinel — while
|
|
699
|
-
* unset the handler is generic over its own return; once set the handler must
|
|
700
|
-
* return that type and the result is parsed through the validator before
|
|
701
|
-
* serialization. `[Output] extends [undefined]` is tuple-wrapped so a union
|
|
702
|
-
* `Output` doesn't distribute and the test is for the exact sentinel.
|
|
566
|
+
* Compose every plugin's middleware into a single middleware you attach with one
|
|
567
|
+
* `.use(...)`. Plugins without middleware (schema-only) are skipped; the rest run
|
|
568
|
+
* in array order, each seeing the context the previous one widened, so the final
|
|
569
|
+
* `next({ ctx })` the builder receives carries every plugin's `ctx.api.<key>`
|
|
570
|
+
* additions. Equivalent to `.use(a.middleware).use(b.middleware)…` but as one
|
|
571
|
+
* value, the middleware sibling of {@link installPlugins}.
|
|
703
572
|
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
573
|
+
* `ContextIn` is left free so the builder infers it from the context at the
|
|
574
|
+
* `.use(...)` site; the result type widens it by the union of the plugins'
|
|
575
|
+
* outputs.
|
|
706
576
|
*/
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
/**
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
*/
|
|
718
|
-
cacheTag: (value: string) => HttpRouteBuilder<SearchParams, Body, Params, Output>;
|
|
719
|
-
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;
|
|
720
|
-
output: <V extends Validator>(validator: V) => HttpRouteBuilder<SearchParams, Body, Params, Infer<V>>;
|
|
721
|
-
params: <P extends ArgsValidator>(validators: P) => HttpRouteBuilder<SearchParams, Body, P & Params, Output>;
|
|
722
|
-
searchParams: <S extends ArgsValidator>(validators: S) => HttpRouteBuilder<S & SearchParams, Body, Params, Output>;
|
|
723
|
-
/**
|
|
724
|
-
* Terminal: declare this route as a streaming Server-Sent Events endpoint.
|
|
725
|
-
* The handler is an async generator (or any function returning an
|
|
726
|
-
* `AsyncIterable<R>`) that yields one chunk per SSE `data:` frame; on
|
|
727
|
-
* iterator completion the route writes a final `event: complete` frame; on
|
|
728
|
-
* throw, an `event: error` frame is written with `{code, message}` before
|
|
729
|
-
* the stream closes. The chunks are JSON-encoded; `R` is inferred from the
|
|
730
|
-
* handler's yielded type.
|
|
731
|
-
* @experimental Reconnect/POST-body/wire-fidelity design questions are still open, so the shape may change.
|
|
732
|
-
*/
|
|
733
|
-
stream: <R>(handler: (options: HttpStreamHandlerOptions<SearchParams, Params>) => AsyncGenerator<R, void, void> | AsyncIterable<R>) => LunoraRouteHandler;
|
|
734
|
-
/**
|
|
735
|
-
* Attach a `Vary` header to the response so Cloudflare stores separate
|
|
736
|
-
* cached variants per distinct value of the listed request headers.
|
|
737
|
-
*/
|
|
738
|
-
vary: (value: string) => HttpRouteBuilder<SearchParams, Body, Params, Output>;
|
|
577
|
+
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>;
|
|
578
|
+
/** Options for `.vectorize(field, opts)` (DSL Shape A). */
|
|
579
|
+
interface VectorizeOptions<Shape extends Record<string, Validator> = Record<string, Validator>> {
|
|
580
|
+
dimensions: number;
|
|
581
|
+
embed: VectorEmbedder;
|
|
582
|
+
/** Logical index name; must match a `[[vectorize]]` binding in wrangler. */
|
|
583
|
+
index: string;
|
|
584
|
+
/** Fields mirrored into Vectorize metadata for filtering. */
|
|
585
|
+
metadata?: ReadonlyArray<keyof Shape & string>;
|
|
586
|
+
metric: VectorMetric;
|
|
739
587
|
}
|
|
740
|
-
/**
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
delete: HttpRouteFactory;
|
|
745
|
-
get: HttpRouteFactory;
|
|
746
|
-
head: HttpRouteFactory;
|
|
747
|
-
options: HttpRouteFactory;
|
|
748
|
-
patch: HttpRouteFactory;
|
|
749
|
-
post: HttpRouteFactory;
|
|
750
|
-
put: HttpRouteFactory;
|
|
588
|
+
/** A `one` (many-to-one) relation descriptor; phantom `Target` carries the target table name. */
|
|
589
|
+
interface OneRelation<Target extends string = string> extends RelationDefinition {
|
|
590
|
+
readonly __target?: Target;
|
|
591
|
+
readonly kind: "one";
|
|
751
592
|
}
|
|
752
|
-
/**
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
*
|
|
757
|
-
* ```ts
|
|
758
|
-
* export const listTodos = httpRoute
|
|
759
|
-
* .get("/api/todos")
|
|
760
|
-
* .searchParams({ limit: v.number(), q: v.optional(v.string()) })
|
|
761
|
-
* .output(v.array(v.object({ id: v.string(), text: v.string() })))
|
|
762
|
-
* .handler(async ({ ctx, searchParams }) => ctx.runQuery(api.todos.list, searchParams));
|
|
763
|
-
*
|
|
764
|
-
* export const getTodo = httpRoute
|
|
765
|
-
* .get("/api/todos/:id")
|
|
766
|
-
* .params({ id: v.string() })
|
|
767
|
-
* .handler(async ({ ctx, params }) => ctx.runQuery(api.todos.get, params));
|
|
768
|
-
*
|
|
769
|
-
* const app = httpRouter();
|
|
770
|
-
* app.get("/api/todos", listTodos);
|
|
771
|
-
* app.get("/api/todos/:id", getTodo);
|
|
772
|
-
* ```
|
|
773
|
-
*/
|
|
774
|
-
declare const httpRoute: HttpRoute;
|
|
775
|
-
/**
|
|
776
|
-
* Structural view of an R2 object body, as returned by `@lunora/storage`'s
|
|
777
|
-
* `download()`. Re-declared here (not imported) so `@lunora/server` takes no
|
|
778
|
-
* runtime dependency on `@lunora/storage`; the real binding satisfies the shape.
|
|
779
|
-
*/
|
|
780
|
-
interface StorageObjectBody {
|
|
781
|
-
/** The object body stream (`null` for a zero-byte object). */
|
|
782
|
-
body: ReadableStream | null;
|
|
783
|
-
etag: string;
|
|
784
|
-
httpMetadata?: {
|
|
785
|
-
contentType?: string;
|
|
786
|
-
};
|
|
787
|
-
key: string;
|
|
788
|
-
/** Hex SHA-256, when R2 carries a checksum (surfaced by `@lunora/storage`). */
|
|
789
|
-
sha256?: string;
|
|
790
|
-
/** Base64 SHA-256 (RFC 9530 digest encoding), when R2 carries a checksum. */
|
|
791
|
-
sha256Base64?: string;
|
|
792
|
-
size: number;
|
|
593
|
+
/** A `many` (one-to-many) relation descriptor; phantom `Target` carries the target table name. */
|
|
594
|
+
interface ManyRelation<Target extends string = string> extends RelationDefinition {
|
|
595
|
+
readonly __target?: Target;
|
|
596
|
+
readonly kind: "many";
|
|
793
597
|
}
|
|
794
|
-
/**
|
|
795
|
-
interface
|
|
796
|
-
|
|
797
|
-
|
|
598
|
+
/** The `r` argument passed to `.relations((r) => …)`. */
|
|
599
|
+
interface RelationBuilder {
|
|
600
|
+
/** One-to-many: the FK `field` lives on the target table, matching this table's `references` (default `_id`). */
|
|
601
|
+
many: <Target extends string>(table: Target, options: {
|
|
602
|
+
field: string;
|
|
603
|
+
references?: string;
|
|
604
|
+
}) => ManyRelation<Target>;
|
|
605
|
+
/** Many-to-one: the FK `field` lives on this table, pointing at `table`.`references` (default `_id`). */
|
|
606
|
+
one: <Target extends string>(table: Target, options: {
|
|
607
|
+
field: string;
|
|
608
|
+
onDelete?: OnDeleteAction;
|
|
609
|
+
references?: string;
|
|
610
|
+
}) => OneRelation<Target>;
|
|
798
611
|
}
|
|
799
612
|
/**
|
|
800
|
-
*
|
|
801
|
-
* `
|
|
802
|
-
*
|
|
803
|
-
* `head` is required rather than optional-with-a-fallback because the fallback
|
|
804
|
-
* is the bug: without it a ranged request has to start a full-object `download`
|
|
805
|
-
* just to learn the size, then throw that body away. `@lunora/storage`'s `head`
|
|
806
|
-
* already degrades internally to a 0-length ranged `get()` on a binding with no
|
|
807
|
-
* HEAD, so there is nothing a caller here could usefully do that it does not.
|
|
613
|
+
* Options for the inline `.aggregateIndex(name, opts)` builder. `op` defaults to
|
|
614
|
+
* `count` so `aggregateIndex("byUser", { by: ["userId"] })` is a single-line
|
|
615
|
+
* `COUNT(*) GROUP BY userId` accelerator.
|
|
808
616
|
*/
|
|
809
|
-
interface
|
|
810
|
-
/**
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
}
|
|
819
|
-
/** Any ctx that carries a {@link StorageDownloader} on `.storage` (Query/Mutation/Action ctx all do). */
|
|
820
|
-
interface ContextWithStorage {
|
|
821
|
-
storage: StorageDownloader;
|
|
617
|
+
interface InlineAggregateIndexOptions<Shape extends Record<string, Validator> = Record<string, Validator>> {
|
|
618
|
+
/** Group keys; counter rows are one per distinct tuple. Omitted = single-row aggregate over the whole table. */
|
|
619
|
+
by?: ReadonlyArray<keyof Shape & string>;
|
|
620
|
+
/** The column the reducer applies to. Required for `sum`/`min`/`max`/`avg`; ignored for `count`. */
|
|
621
|
+
field?: keyof Shape & string;
|
|
622
|
+
/** Reducer (default `count`). */
|
|
623
|
+
op?: AggregateOp;
|
|
624
|
+
/** Static predicate baked into the counter — only matching rows are aggregated. */
|
|
625
|
+
where?: Record<string, unknown>;
|
|
822
626
|
}
|
|
823
627
|
/**
|
|
824
|
-
*
|
|
825
|
-
*
|
|
826
|
-
*
|
|
827
|
-
*
|
|
828
|
-
* handler can guard a request-derived header value before writing it — the fix the
|
|
829
|
-
* `http_action_response_header_injection` advisor lint points to.
|
|
830
|
-
*/
|
|
831
|
-
declare const isSafeHeaderValue: (value: string) => boolean;
|
|
832
|
-
/**
|
|
833
|
-
* Stream a stored object as an HTTP {@link Response} from an `httpAction`
|
|
834
|
-
* handler, with correct `Content-Type`, `ETag`, and `Accept-Ranges: bytes`.
|
|
835
|
-
* Honors a single-range `Range` request → **206 Partial Content** with
|
|
836
|
-
* `Content-Range` + `Content-Length`; otherwise **200**. A missing object is a
|
|
837
|
-
* **404**; an out-of-bounds range is a **416** with a `Content-Range` of
|
|
838
|
-
* `bytes` star-slash-size.
|
|
839
|
-
*
|
|
840
|
-
* A range request resolves its window against a body-free `head()`, then issues
|
|
841
|
-
* ONE `download()` with the resolved `{ offset, length }` so R2 streams just
|
|
842
|
-
* those bytes — the slice is never buffered in the isolate, and no full-object
|
|
843
|
-
* body transfer is started only to be cancelled. A request that cannot produce a
|
|
844
|
-
* 206 at all (no `Range`, multi-range, malformed) skips the `head()` entirely and
|
|
845
|
-
* streams straight from a single `download()`. For very
|
|
846
|
-
* large objects a signed URL (`ctx.storage.getSignedUrl`) is still cheaper since
|
|
847
|
-
* the client then ranges against R2/CDN directly with no Worker hop.
|
|
848
|
-
*/
|
|
849
|
-
declare const serveStorageObject: (context: ContextWithStorage, key: string, request: Request) => Promise<Response>;
|
|
850
|
-
/**
|
|
851
|
-
* What the worker does with a resolver's identity when it fails contract
|
|
852
|
-
* validation (a forged / malformed claim set arriving from an untrusted token).
|
|
853
|
-
* `"anonymous"` (default, safe) treats the request as anonymous, so the bad
|
|
854
|
-
* identity never reaches a policy as a valid identity (`ctx.auth.userId`
|
|
855
|
-
* becomes `undefined`). `"reject"` fails the request closed (a `401`) — use
|
|
856
|
-
* when a malformed credential should be a hard error, not a silent downgrade.
|
|
628
|
+
* Options for the inline `.rankIndex(name, opts)` builder. `sortBy` is required;
|
|
629
|
+
* accepts either an array of `{ field, direction }` keys, or the shorthand
|
|
630
|
+
* `["field"]` (asc) / `{ field: "desc" }` map entries. `partitionBy` scopes the
|
|
631
|
+
* rank — omitted ⇒ one global rank over the whole table.
|
|
857
632
|
*/
|
|
858
|
-
|
|
859
|
-
/**
|
|
860
|
-
|
|
861
|
-
/**
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
633
|
+
interface InlineRankIndexOptions<Shape extends Record<string, Validator> = Record<string, Validator>> {
|
|
634
|
+
/** Columns that scope each ranking; omitted ⇒ one global rank. */
|
|
635
|
+
partitionBy?: ReadonlyArray<keyof Shape & string>;
|
|
636
|
+
/** Ordered sort keys driving the rank. Required. */
|
|
637
|
+
sortBy: ReadonlyArray<{
|
|
638
|
+
direction?: "asc" | "desc";
|
|
639
|
+
field: keyof Shape & string;
|
|
640
|
+
}>;
|
|
641
|
+
/** Static predicate baked into the index; only matching rows enter. */
|
|
642
|
+
where?: Record<string, unknown>;
|
|
867
643
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
} | {
|
|
872
|
-
error: string;
|
|
873
|
-
ok: false;
|
|
874
|
-
};
|
|
875
|
-
/**
|
|
876
|
-
* A declared identity claim contract. Carries the codegen discovery brand, the
|
|
877
|
-
* declared claim validators, the reject policy, and a runtime `validate`. The
|
|
878
|
-
* `TClaims` type parameter is the inferred claim shape (always extending
|
|
879
|
-
* `{ userId: string }`); it is phantom (no runtime field) and exists so
|
|
880
|
-
* `@lunora/codegen` and {@link InferIdentity} can recover the type.
|
|
881
|
-
*/
|
|
882
|
-
interface IdentityContract<TClaims extends {
|
|
883
|
-
userId: string;
|
|
884
|
-
} = {
|
|
885
|
-
userId: string;
|
|
886
|
-
}> {
|
|
644
|
+
interface TableBuilder<Shape extends Record<string, Validator> = Record<string, Validator>> extends TableDefinition<Shape> {
|
|
645
|
+
/** Declare an aggregate (counter/sum/…) maintained by triggers for O(1) reads. */
|
|
646
|
+
aggregateIndex: (name: string, options?: InlineAggregateIndexOptions<Shape>) => TableBuilder<Shape>;
|
|
887
647
|
/**
|
|
888
|
-
*
|
|
889
|
-
*
|
|
648
|
+
* Stamp every row with `_commitSeq` — a per-shard integer, allocated once
|
|
649
|
+
* per mutation and strictly increasing in **commit order**, refreshed on
|
|
650
|
+
* every write to the row (insert, patch, replace, and the marker flip a
|
|
651
|
+
* `.softDelete()` performs).
|
|
652
|
+
*
|
|
653
|
+
* `_creationTime` is wall-clock and therefore cannot order commits: the
|
|
654
|
+
* clock is read when the handler runs, the write lands when the transaction
|
|
655
|
+
* commits, and nothing ties those instants together. A changefeed paging on
|
|
656
|
+
* `_creationTime` can skip a row permanently. Paging on `_commitSeq`
|
|
657
|
+
* (`where: { _commitSeq: { gt: cursor } }, orderBy: ["_commitSeq"]`) cannot.
|
|
658
|
+
*
|
|
659
|
+
* It orders COMMITS, not rows: one mutation's rows share a value. A bounded
|
|
660
|
+
* page can therefore end mid-group, so a consumer must checkpoint at a
|
|
661
|
+
* sequence it has seen the whole of, never at the last row of a full page.
|
|
662
|
+
* An action's writes are the exception to the grouping — they commit
|
|
663
|
+
* independently, so each gets its own sequence.
|
|
664
|
+
*
|
|
665
|
+
* Ordered, not contiguous — read a gap as "nothing to see", never as loss.
|
|
666
|
+
* Per-shard, not global: two shards allocate independently, so a cursor is
|
|
667
|
+
* only meaningful against the shard it came from. Rejected on `.global()`
|
|
668
|
+
* tables, which have no shard-local transaction to allocate inside.
|
|
669
|
+
*
|
|
670
|
+
* **A hard delete is invisible to the feed.** The sequence lives on the row,
|
|
671
|
+
* so a physically removed row takes it along: the row stops appearing, but
|
|
672
|
+
* no event says it went away. Pair `.commitOrdered()` with `.softDelete()`
|
|
673
|
+
* when the feed must observe deletes — the tombstone flip is an UPDATE, so
|
|
674
|
+
* it advances the sequence and pages through like any other change.
|
|
890
675
|
*/
|
|
891
|
-
|
|
892
|
-
readonly __lunoraIdentity: true;
|
|
893
|
-
/** The declared claim validators (a `@lunora/values` validator map). */
|
|
894
|
-
readonly claims: ValidatorMap;
|
|
895
|
-
/** Reject policy applied at the trust boundary. See {@link IdentityRejectMode}. */
|
|
896
|
-
readonly onInvalid: IdentityRejectMode;
|
|
676
|
+
commitOrdered: () => TableBuilder<Shape>;
|
|
897
677
|
/**
|
|
898
|
-
*
|
|
899
|
-
*
|
|
900
|
-
*
|
|
901
|
-
*
|
|
678
|
+
* Mark this table as written outside Lunora's discoverable insert path —
|
|
679
|
+
* by an adapter, a migration, or framework middleware (e.g. `@lunora/auth`'s
|
|
680
|
+
* better-auth tables, `@lunora/ratelimit`'s store). Advisor insert-path lints
|
|
681
|
+
* (`table_without_insert`) then skip it instead of flagging the absent
|
|
682
|
+
* `ctx.db.insert(...)`.
|
|
902
683
|
*/
|
|
903
|
-
|
|
684
|
+
externallyManaged: () => TableBuilder<Shape>;
|
|
685
|
+
/**
|
|
686
|
+
* Declare a geospatial index over a `v.geoPoint()` column. The runtime keeps
|
|
687
|
+
* a geohash companion so `withGeoIndex(name, q => q.near(point, radius))` and
|
|
688
|
+
* `.within(bbox)` resolve as a geohash-prefix range scan + Haversine
|
|
689
|
+
* refine/sort. `options.precision` tunes the geohash length (default 9).
|
|
690
|
+
*/
|
|
691
|
+
geoIndex: (name: string, options: {
|
|
692
|
+
field: keyof Shape & string;
|
|
693
|
+
precision?: number;
|
|
694
|
+
}) => TableBuilder<Shape>;
|
|
695
|
+
/**
|
|
696
|
+
* Mark this table as global (cross-shard). Backed by **D1** by default;
|
|
697
|
+
* pass `{ backend: "hyperdrive" }` to store it in a Postgres/MySQL database
|
|
698
|
+
* via Cloudflare Hyperdrive (PlanetScale, Neon, …) instead. Either way the
|
|
699
|
+
* table stays reactive — live queries re-run on write.
|
|
700
|
+
*/
|
|
701
|
+
global: (options?: {
|
|
702
|
+
backend?: GlobalBackend;
|
|
703
|
+
}) => TableBuilder<Shape>;
|
|
704
|
+
/** Add a secondary index. */
|
|
705
|
+
index: (name: string, fields: ReadonlyArray<(keyof Shape & string) | (typeof SYSTEM_INDEX_FIELDS)[number]>, options?: {
|
|
706
|
+
unique?: boolean;
|
|
707
|
+
}) => TableBuilder<Shape>;
|
|
708
|
+
/**
|
|
709
|
+
* Declare this table EPHEMERAL — state the shard rebuilds rather than
|
|
710
|
+
* remembers.
|
|
711
|
+
*
|
|
712
|
+
* A memory table is a full `ctx.db` table: indexes, `where`, `orderBy`,
|
|
713
|
+
* pagination, relations, live queries. What it is not is durable. Its rows
|
|
714
|
+
* are wiped the moment the Durable Object is reconstructed — which happens
|
|
715
|
+
* on every eviction, and a WebSocket-hibernating shard is evicted often — so
|
|
716
|
+
* a memory table holds only what can be derived again: presence and cursors,
|
|
717
|
+
* a live participant list, a rate-limit window, an actor's scratch state.
|
|
718
|
+
*
|
|
719
|
+
* Pair it with `onShardInit` to rebuild whatever the app needs present.
|
|
720
|
+
* The framework guarantees the ordering: every memory table is cleared, and
|
|
721
|
+
* every init hook has run, before any handler can read one. Without a hook a
|
|
722
|
+
* memory table simply comes back empty, which is a correct state for
|
|
723
|
+
* presence and a wrong one for a cache someone is treating as authoritative.
|
|
724
|
+
*
|
|
725
|
+
* **On Cloudflare the rows still transit the DO's SQLite.** workerd exposes
|
|
726
|
+
* exactly one SQL handle and no memory-backed database, so `.memory()` buys
|
|
727
|
+
* the LIFETIME (and skips the CDC changelog, so an append-heavy presence
|
|
728
|
+
* table does not grow the op-log), not the write. Treat it as "state I am
|
|
729
|
+
* happy to lose", not as "state that is free to write" — see
|
|
730
|
+
* `PlatformCapabilities.memoryTables`, rated `emulated` for exactly this
|
|
731
|
+
* reason.
|
|
732
|
+
*
|
|
733
|
+
* Rejected alongside `.global()` (a D1 table is not this shard's to clear),
|
|
734
|
+
* `.commitOrdered()` (a sequence that resets is not a sequence), and
|
|
735
|
+
* `.source()` (an externally-materialized table is not ours to wipe).
|
|
736
|
+
*/
|
|
737
|
+
memory: () => TableBuilder<Shape>;
|
|
738
|
+
/**
|
|
739
|
+
* Name the column holding the owning user's id, so "only the owner sees these
|
|
740
|
+
* rows" is declared once here rather than restated in every shape.
|
|
741
|
+
*
|
|
742
|
+
* A `defineShape({ table, owner: true })` over this table derives its predicate
|
|
743
|
+
* from the field: the subscriber's verified `ctx.auth.userId` must match, and an
|
|
744
|
+
* anonymous subscriber is denied. Pairs naturally with `.shardBy(field)` on the
|
|
745
|
+
* same column — the shard key routes the storage, `ownedBy` states who the rows
|
|
746
|
+
* belong to — but the two are independent and either can be used alone.
|
|
747
|
+
*
|
|
748
|
+
* This is a *shape* declaration, not an RLS policy: it narrows what a shape
|
|
749
|
+
* replicates. Guarding procedure reads/writes is still `rls(...)`'s job.
|
|
750
|
+
*/
|
|
751
|
+
ownedBy: (field: keyof Shape & string) => TableBuilder<Shape>;
|
|
752
|
+
/**
|
|
753
|
+
* Opt this table OUT of secure-by-default RLS. Under a schema marked
|
|
754
|
+
* `.rls("required")`, every table is protected (the write path denies raw,
|
|
755
|
+
* non-RLS `ctx.db` access); calling `.public()` exempts this one table so a
|
|
756
|
+
* plain `query`/`mutation` may read/write it without an RLS policy. No effect
|
|
757
|
+
* when the schema does not require RLS.
|
|
758
|
+
*/
|
|
759
|
+
public: () => TableBuilder<Shape>;
|
|
760
|
+
/**
|
|
761
|
+
* Declare a rank index (sorted companion table, btree-backed) for
|
|
762
|
+
* `rank(row)` / `rankPage()` reads in O(log n). See {@link RankIndexDefinition}.
|
|
763
|
+
*/
|
|
764
|
+
rankIndex: (name: string, options: InlineRankIndexOptions<Shape>) => TableBuilder<Shape>;
|
|
765
|
+
/** Declare relations to other tables, loaded via `findMany({ with })`. */
|
|
766
|
+
relations: (build: (r: RelationBuilder) => Record<string, RelationDefinition>) => TableBuilder<Shape>;
|
|
767
|
+
/**
|
|
768
|
+
* Add a full-text search index over `field`, queried with
|
|
769
|
+
* `.withSearchIndex(name, q => q.search(field, term))`. `field` may be a
|
|
770
|
+
* dot-separated path into a nested object (`"properties.name"`).
|
|
771
|
+
* `filterFields` (at most 16) lists the columns `.eq()` may narrow by inside
|
|
772
|
+
* the search. `language` selects the text analysis (accent folding always,
|
|
773
|
+
* plus that language's stopwords). `staged: true` skips the migration-time
|
|
774
|
+
* backfill on a large existing table — pre-existing rows stay unsearchable
|
|
775
|
+
* until `__lunora_admin__:backfillSearch` is run against the deployment. `strategy: "native"` uses the engine's
|
|
776
|
+
* own full-text index where it has one (Postgres) — faster on large corpora,
|
|
777
|
+
* at the cost of the engine ranking rather than the shared scorer.
|
|
778
|
+
*/
|
|
779
|
+
searchIndex: (name: string, options: {
|
|
780
|
+
field: string;
|
|
781
|
+
filterFields?: ReadonlyArray<string>;
|
|
782
|
+
language?: SearchLanguage;
|
|
783
|
+
staged?: boolean;
|
|
784
|
+
strategy?: SearchStrategy;
|
|
785
|
+
}) => TableBuilder<Shape>;
|
|
786
|
+
/** Route storage by the named field — one DO per distinct value. */
|
|
787
|
+
shardBy: (field: keyof Shape & string) => TableBuilder<Shape>;
|
|
788
|
+
/**
|
|
789
|
+
* Turn on soft delete. Adds a nullable timestamp column (`options.field`,
|
|
790
|
+
* default `deletedAt`) and changes `ctx.db.<table>.delete()` to **set** it
|
|
791
|
+
* instead of removing the row; `onDelete: "cascade"` children are recursively
|
|
792
|
+
* soft-deleted too. **List reads** (`findMany`/`findFirst`/`query()`/`count`/
|
|
793
|
+
* `aggregate`/relation loads) then hide soft-deleted rows unless they pass
|
|
794
|
+
* `includeDeleted: true`; by-id `get`/`patch`/`replace` and the new
|
|
795
|
+
* `restore()` still address the row directly. `hardDelete()` physically
|
|
796
|
+
* removes it (cascading as a real delete). Note: `includeDeleted` is a read
|
|
797
|
+
* scope, not access control — anyone who can run the read can set it; a unique
|
|
798
|
+
* index still rejects a new row that collides with a soft-deleted one (the row
|
|
799
|
+
* physically persists).
|
|
800
|
+
*/
|
|
801
|
+
softDelete: (options?: {
|
|
802
|
+
field?: string;
|
|
803
|
+
}) => TableBuilder<Shape>;
|
|
804
|
+
/**
|
|
805
|
+
* Materialize this table from an external Postgres/MySQL behind Cloudflare
|
|
806
|
+
* Hyperdrive (plan 077). A system-driven poll loop reads the tenant slice
|
|
807
|
+
* (`query`, with params bound from `tenantBy`) and lands it in the DO's SQLite,
|
|
808
|
+
* after which `defineShape` carries it to clients unchanged. Implies
|
|
809
|
+
* `.externallyManaged()` (rows come from the ingest loop, not user mutations).
|
|
810
|
+
*
|
|
811
|
+
* Orthogonal to `.shardBy()` — combine them for per-tenant DOs. **Under
|
|
812
|
+
* `.shardBy()` `tenantBy` is mandatory** (the tenant-isolation boundary); the
|
|
813
|
+
* `external_source_unscoped` advisor lint fails the build when it is absent, and
|
|
814
|
+
* `external_source_on_global` rejects combining `.source()` with `.global()`.
|
|
815
|
+
*/
|
|
816
|
+
source: (definition: ExternalSourceDefinition) => TableBuilder<Shape>;
|
|
817
|
+
/** Declare named lifecycle triggers fired inline within the write path. */
|
|
818
|
+
triggers: (build: (t: TriggerBuilder<Shape>) => Record<string, TriggerDefinition>) => TableBuilder<Shape>;
|
|
819
|
+
/**
|
|
820
|
+
* Declare a table-level TTL: a DO alarm-driven sweep auto-deletes rows whose
|
|
821
|
+
* expiry has passed (or soft-deletes them when the table also
|
|
822
|
+
* `.softDelete()`s). `field` is an epoch-millisecond column; without
|
|
823
|
+
* `options.after` its value is the absolute expiry instant, with `after` the
|
|
824
|
+
* row expires `after` ms past `field` (`field + after`). Coarse, cheap,
|
|
825
|
+
* table-level — for per-row schedules use `@lunora/scheduler`.
|
|
826
|
+
*/
|
|
827
|
+
ttl: (field: keyof Shape & string, options?: {
|
|
828
|
+
after?: number;
|
|
829
|
+
}) => TableBuilder<Shape>;
|
|
830
|
+
/** Declare a vector index over a single text field on this table. */
|
|
831
|
+
vectorize: (field: keyof Shape & string, options: VectorizeOptions<Shape>) => TableBuilder<Shape>;
|
|
832
|
+
}
|
|
833
|
+
/** Options for `defineVectorIndex(...)` (DSL Shape B). */
|
|
834
|
+
interface VectorIndexOptions {
|
|
835
|
+
dimensions: number;
|
|
836
|
+
embed: VectorEmbedder;
|
|
837
|
+
/** Optional projection of the source row into Vectorize metadata. */
|
|
838
|
+
metadata?: (row: Record<string, unknown>) => Record<string, unknown>;
|
|
839
|
+
metric: VectorMetric;
|
|
840
|
+
/** The vector source: which table, and how to derive the embedded text. */
|
|
841
|
+
source: {
|
|
842
|
+
select: (row: Record<string, unknown>) => string;
|
|
843
|
+
table: string;
|
|
844
|
+
};
|
|
904
845
|
}
|
|
905
|
-
/** Recover the declared claim type from a {@link defineIdentity} contract. */
|
|
906
|
-
type InferIdentity<T> = T extends IdentityContract<infer TClaims> ? TClaims : never;
|
|
907
846
|
/**
|
|
908
|
-
*
|
|
909
|
-
*
|
|
910
|
-
* (e.g. `userId` is missing or not a required string), the argument type
|
|
911
|
-
* collapses to `never` and the call fails to typecheck.
|
|
912
|
-
* @example
|
|
913
|
-
* export const identity = defineIdentity({ userId: v.string(), tenantId: v.optional(v.string()), scopes: v.optional(v.array(v.string())) });
|
|
847
|
+
* Build a table definition. Returned object is both the table definition (for
|
|
848
|
+
* `defineSchema`) and a fluent builder for indexes + sharding metadata.
|
|
914
849
|
*/
|
|
915
|
-
declare const
|
|
916
|
-
userId: string;
|
|
917
|
-
} ? A : never, options?: DefineIdentityOptions) => IdentityContract<InferValidatorMap<A> & {
|
|
918
|
-
userId: string;
|
|
919
|
-
}>;
|
|
920
|
-
/** Handler for a connection-lifecycle hook. */
|
|
921
|
-
type LifecycleHandler = (context: MutationCtx, event: LifecycleEvent) => Promise<void> | void;
|
|
922
|
-
/** Handler for a shard-init hook. */
|
|
923
|
-
type ShardInitHandler = (context: MutationCtx, event: ShardInitEvent) => Promise<void> | void;
|
|
924
|
-
/** Register a hook that fires once when a client's WebSocket connects. */
|
|
925
|
-
declare const onConnect: (handler: LifecycleHandler) => RegisteredLifecycleHook;
|
|
926
|
-
/** Register a hook that fires once when a client's WebSocket disconnects. */
|
|
927
|
-
declare const onDisconnect: (handler: LifecycleHandler) => RegisteredLifecycleHook;
|
|
850
|
+
declare const defineTable: <Shape extends Record<string, Validator>>(inputShape: Shape) => TableBuilder<Shape>;
|
|
928
851
|
/**
|
|
929
|
-
*
|
|
930
|
-
*
|
|
931
|
-
*
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
*
|
|
936
|
-
*
|
|
937
|
-
*
|
|
938
|
-
*
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
*
|
|
949
|
-
*
|
|
950
|
-
*
|
|
951
|
-
*
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
*
|
|
956
|
-
*
|
|
957
|
-
*
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
*
|
|
852
|
+
* Declare a standalone vector index (DSL Shape B). Pass the returned value in
|
|
853
|
+
* the `vectorIndexes` map of {@link defineSchema} when the source is derived
|
|
854
|
+
* from multiple fields or a computation rather than a single column.
|
|
855
|
+
*/
|
|
856
|
+
declare const defineVectorIndex: (options: VectorIndexOptions) => VectorIndexDefinition;
|
|
857
|
+
/**
|
|
858
|
+
* Options for the standalone `defineAggregateIndex(name, opts)` helper (DSL
|
|
859
|
+
* Shape B). Unlike the inline `.aggregateIndex(...)` builder, this form takes
|
|
860
|
+
* the owning table explicitly via `on` — handy when a single counter wants to
|
|
861
|
+
* live next to the schema map rather than inside a table chain.
|
|
862
|
+
*/
|
|
863
|
+
interface AggregateIndexOptions {
|
|
864
|
+
by?: ReadonlyArray<string>;
|
|
865
|
+
field?: string;
|
|
866
|
+
on: string;
|
|
867
|
+
op?: AggregateOp;
|
|
868
|
+
where?: Record<string, unknown>;
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Declare a standalone aggregate index. Pass the returned value to
|
|
872
|
+
* `defineSchema(tables, vectorIndexes, aggregateIndexes)` keyed by index name —
|
|
873
|
+
* the schema attaches it to `tables[on].aggregateIndexes` so runtime consumers
|
|
874
|
+
* (DO + D1) read every index uniformly off the table definition.
|
|
875
|
+
*/
|
|
876
|
+
declare const defineAggregateIndex: (name: string, options: AggregateIndexOptions) => AggregateIndexDefinition;
|
|
877
|
+
/**
|
|
878
|
+
* Options for the standalone `defineRankIndex(name, opts)` helper (DSL Shape B).
|
|
879
|
+
* Mirrors the inline `.rankIndex(...)` builder but takes the owning table via
|
|
880
|
+
* `table` so it can sit next to the schema map.
|
|
881
|
+
*/
|
|
882
|
+
interface RankIndexOptions {
|
|
883
|
+
partitionBy?: ReadonlyArray<string>;
|
|
884
|
+
sortBy: ReadonlyArray<{
|
|
885
|
+
direction?: "asc" | "desc";
|
|
886
|
+
field: string;
|
|
887
|
+
}>;
|
|
888
|
+
table: string;
|
|
889
|
+
where?: Record<string, unknown>;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Declare a standalone rank index. Pass the returned value to
|
|
893
|
+
* `defineSchema(tables, vectorIndexes, aggregateIndexes, rankIndexes)` keyed
|
|
894
|
+
* by index name — the schema attaches it to `tables[on].rankIndexes`.
|
|
895
|
+
*/
|
|
896
|
+
declare const defineRankIndex: (name: string, options: RankIndexOptions) => RankIndexDefinition;
|
|
897
|
+
/**
|
|
898
|
+
* Build the application schema. The first argument is the table map; the
|
|
899
|
+
* optional second argument registers standalone `defineVectorIndex(...)`
|
|
900
|
+
* declarations (DSL Shape B) keyed by index name. The optional third argument
|
|
901
|
+
* registers standalone `defineAggregateIndex(...)` declarations (DSL Shape B);
|
|
902
|
+
* the optional fourth argument registers standalone `defineRankIndex(...)`
|
|
903
|
+
* declarations. Both are folded into the matching `tables[on].*Indexes` array
|
|
904
|
+
* so runtime backends read every index uniformly off the table definition.
|
|
905
|
+
*/
|
|
906
|
+
/**
|
|
907
|
+
* Schema with an in-place `.extend(plugin.extension)` method. Used so apps
|
|
908
|
+
* can compose plugin schemas: `defineSchema({...}).extend(authPlugin.extension)`.
|
|
970
909
|
*
|
|
971
|
-
*
|
|
972
|
-
*
|
|
973
|
-
*
|
|
974
|
-
*
|
|
975
|
-
*
|
|
976
|
-
*
|
|
910
|
+
* `extend` is non-mutating — returns a fresh `ExtendableSchema` containing
|
|
911
|
+
* the merged tables. Extension tables are auto-namespaced by the extension
|
|
912
|
+
* `key` (`buckets` → `ratelimit_buckets`), so the merged type carries the
|
|
913
|
+
* prefixed names via {@link PrefixedTables}. Chains:
|
|
914
|
+
* `defineSchema(...).extend(a).extend(b)` is the typed equivalent of merging
|
|
915
|
+
* `a`'s prefixed tables then `b`'s.
|
|
977
916
|
*/
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
917
|
+
type ExtendableSchema<T extends Record<string, TableDefinition>> = {
|
|
918
|
+
extend: <X extends Record<string, TableDefinition>, Key extends string>(extension: SchemaExtension<X> & {
|
|
919
|
+
readonly key: Key;
|
|
920
|
+
}) => ExtendableSchema<PrefixedTables<X, Key> & T>;
|
|
921
|
+
/**
|
|
922
|
+
* Pin every Durable Object the app reaches — shards, fan-out, subscriptions,
|
|
923
|
+
* the scheduler, and `ctx.containers` — to a Cloudflare data-residency
|
|
924
|
+
* jurisdiction (`"eu"`, `"us"`, `"fedramp"`). Codegen reads this off the
|
|
925
|
+
* schema and emits it into the generated worker's `createWorker({ jurisdiction })`
|
|
926
|
+
* (and `ctx.scheduler` / `ctx.containers`). Non-mutating: returns a fresh
|
|
927
|
+
* `ExtendableSchema`, so it composes with `.rls(...)` / `.extend(...)` in any order.
|
|
928
|
+
*
|
|
929
|
+
* ⚠️ **Set this once, before your first deploy — changing or removing it
|
|
930
|
+
* strands data.** A Durable Object name maps to a *different* ID in each
|
|
931
|
+
* jurisdiction, so toggling this on an existing app makes every shard, scheduler
|
|
932
|
+
* job, and session DO resolve to a NEW, empty DO; the previous data stays in the
|
|
933
|
+
* old jurisdiction's DOs and is no longer reachable. There is no in-place
|
|
934
|
+
* migration — you would have to export from the old jurisdiction and import
|
|
935
|
+
* into the new one.
|
|
936
|
+
*
|
|
937
|
+
* Note: this pins **DO-backed** state only. D1-backed state — `.global()`
|
|
938
|
+
* tables and `@lunora/auth` sessions alike — is governed by D1's own location
|
|
939
|
+
* settings, not this option.
|
|
940
|
+
* @see https://developers.cloudflare.com/durable-objects/reference/data-location/
|
|
941
|
+
*/
|
|
942
|
+
jurisdiction: (jurisdiction: DurableObjectJurisdiction) => ExtendableSchema<T>;
|
|
943
|
+
/**
|
|
944
|
+
* Turn on secure-by-default RLS for the whole schema. Every table is then
|
|
945
|
+
* protected — the DO/D1 write path denies raw, non-RLS `ctx.db` access, so a
|
|
946
|
+
* procedure that forgets `.use(rls(...))` fails closed. Opt a table out with
|
|
947
|
+
* `.public()`. Non-mutating: returns a fresh `ExtendableSchema` carrying the
|
|
948
|
+
* mode, so `.rls("required")` composes with `.extend(...)` either order.
|
|
949
|
+
*/
|
|
950
|
+
rls: (mode: "required") => ExtendableSchema<T>;
|
|
951
|
+
} & Schema<T>;
|
|
983
952
|
/**
|
|
984
|
-
*
|
|
985
|
-
* `
|
|
986
|
-
*
|
|
953
|
+
* Columns every row carries implicitly (never part of a table's declared
|
|
954
|
+
* `shape`), so `.index()` may legitimately name them. The single source for
|
|
955
|
+
* both the compile-time allow-list (`TableBuilder["index"]`'s `fields` type,
|
|
956
|
+
* via `(typeof SYSTEM_INDEX_FIELDS)[number]`) and the runtime cross-check
|
|
957
|
+
* below (via `SYSTEM_INDEX_FIELDS_SET`) — declared once so the two can't
|
|
958
|
+
* drift apart.
|
|
987
959
|
*/
|
|
988
|
-
|
|
960
|
+
declare const SYSTEM_INDEX_FIELDS: readonly ["_commitSeq", "_creationTime", "_id"];
|
|
961
|
+
/**
|
|
962
|
+
* Per-table, per-KIND index→declared-fields map: for each table, each index
|
|
963
|
+
* KIND (`index` | `rank` | `geo`) that has at least one declared index maps
|
|
964
|
+
* to a name→fields record for that kind only. Distilled by
|
|
965
|
+
* {@link indexFieldsFromSchema}; this is the shape `mask()`'s
|
|
966
|
+
* `MaskOptions.indexFields` expects (see `./mask/types`), so a table not
|
|
967
|
+
* present here (no declared indexes of any kind) is simply absent from the
|
|
968
|
+
* map rather than mapped to `{}`, and a kind with no declared indexes on a
|
|
969
|
+
* table that HAS other kinds is simply absent from that table's entry.
|
|
970
|
+
*
|
|
971
|
+
* Kept per kind (rather than one flat name→fields record) because the engine
|
|
972
|
+
* resolves `withIndex`/`withGeoIndex`/rank reads in THREE separate
|
|
973
|
+
* namespaces (`tableDefinition.indexes` / `.geoIndexes` / `.rankIndexes` —
|
|
974
|
+
* see `@lunora/shard-engine`'s `ctx-db.ts`), so the same name can legally and
|
|
975
|
+
* unambiguously denote a different index per kind. A flat map would let one
|
|
976
|
+
* kind's fields silently shadow another's for a colliding name, producing a
|
|
977
|
+
* wrong-namespace answer from the mask guard (checking the wrong index's
|
|
978
|
+
* fields) instead of the documented fail-open (missing lookup) — see plan 258.
|
|
979
|
+
*/
|
|
980
|
+
type IndexFieldsByTable = Readonly<Record<string, {
|
|
981
|
+
readonly geo?: Readonly<Record<string, ReadonlyArray<string>>>;
|
|
982
|
+
readonly index?: Readonly<Record<string, ReadonlyArray<string>>>;
|
|
983
|
+
readonly rank?: Readonly<Record<string, ReadonlyArray<string>>>;
|
|
984
|
+
}>>;
|
|
985
|
+
declare const indexFieldsFromSchema: (schema: Schema) => IndexFieldsByTable;
|
|
986
|
+
declare const defineSchema: <T extends Record<string, TableDefinition>>(tables: T, vectorIndexes?: Record<string, VectorIndexDefinition>, aggregateIndexes?: Record<string, AggregateIndexDefinition>, rankIndexes?: Record<string, RankIndexDefinition>) => ExtendableSchema<T>;
|
|
989
987
|
/**
|
|
990
|
-
*
|
|
991
|
-
*
|
|
992
|
-
*
|
|
993
|
-
*
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
/**
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
988
|
+
* Fields never written into a snapshot, whatever table they appear on.
|
|
989
|
+
*
|
|
990
|
+
* Names rather than types, because that is the only signal available here: a
|
|
991
|
+
* trigger sees values, not the column metadata that would say "this one is a
|
|
992
|
+
* secret". Extend it per app rather than relying on this list being complete.
|
|
993
|
+
*/
|
|
994
|
+
declare const DEFAULT_REDACTED_FIELDS: ReadonlyArray<string>;
|
|
995
|
+
declare const DOCUMENT_HISTORY_BARE_TABLE = "versions";
|
|
996
|
+
/** The prefixed table name the extension produces at merge time. */
|
|
997
|
+
declare const DOCUMENT_HISTORY_TABLE: "documentHistory_versions";
|
|
998
|
+
/** One recorded version, as {@link DocumentHistoryFunctions.listForDocument} returns it. */
|
|
999
|
+
interface DocumentHistoryEntry {
|
|
1000
|
+
/** The row as it stood after the write, redacted. Absent for a delete, and when `truncated`. */
|
|
1001
|
+
doc?: Record<string, unknown>;
|
|
1002
|
+
/** The row this version belongs to. */
|
|
1003
|
+
documentId: string;
|
|
1004
|
+
/** Which write produced this version. */
|
|
1005
|
+
op: "delete" | "insert" | "update";
|
|
1006
|
+
/** The row as it stood before the write, redacted. Absent for an insert, and when `truncated`. */
|
|
1007
|
+
previous?: Record<string, unknown>;
|
|
1008
|
+
/** When the write happened (epoch ms). */
|
|
1009
|
+
recordedAt: number;
|
|
1010
|
+
/** The table the row lives in. */
|
|
1011
|
+
tableName: string;
|
|
1012
|
+
/** `true` when the snapshots were dropped for exceeding `maxSnapshotBytes`. */
|
|
1013
|
+
truncated?: boolean;
|
|
1014
|
+
}
|
|
1015
|
+
/** Options for {@link defineDocumentHistory}. */
|
|
1016
|
+
interface DefineDocumentHistoryOptions {
|
|
1017
|
+
/**
|
|
1018
|
+
* Cap on one serialized snapshot (bytes). Past it the entry is written
|
|
1019
|
+
* without its snapshots and marked `truncated`. Defaults to 64 KB.
|
|
1020
|
+
*/
|
|
1021
|
+
maxSnapshotBytes?: number;
|
|
1022
|
+
/**
|
|
1023
|
+
* Extra field names to drop from every snapshot, on top of the built-in
|
|
1024
|
+
* secret-shaped defaults.
|
|
1025
|
+
*/
|
|
1026
|
+
redact?: ReadonlyArray<string>;
|
|
1027
|
+
/**
|
|
1028
|
+
* How long (ms) an entry is kept. `vacuum` deletes entries older than this.
|
|
1029
|
+
* Defaults to 90 days.
|
|
1030
|
+
*/
|
|
1031
|
+
retentionMs?: number;
|
|
1032
|
+
}
|
|
1033
|
+
/** The registered functions a document-history component ships. */
|
|
1034
|
+
interface DocumentHistoryFunctions {
|
|
1035
|
+
/**
|
|
1036
|
+
* **Internal** query: the recorded versions of one row, newest first.
|
|
1037
|
+
*
|
|
1038
|
+
* Internal because an entry is a full row snapshot, including columns the
|
|
1039
|
+
* table's own RLS hides — wrap it in a procedure of your own that applies
|
|
1040
|
+
* whatever authorization the surface needs.
|
|
1041
|
+
*
|
|
1042
|
+
* Pass `before` to read the history as of an instant: the first entry back is
|
|
1043
|
+
* the last version at or before it, which is what a point-in-time
|
|
1044
|
+
* reconstruction needs.
|
|
1045
|
+
*/
|
|
1046
|
+
listForDocument: RegisteredQuery<{
|
|
1047
|
+
before: ReturnType<typeof v.optional>;
|
|
1048
|
+
documentId: ReturnType<typeof v.string>;
|
|
1049
|
+
limit: ReturnType<typeof v.optional>;
|
|
1050
|
+
}, DocumentHistoryEntry[]>;
|
|
1051
|
+
/**
|
|
1052
|
+
* Internal mutation that deletes entries older than the retention window,
|
|
1053
|
+
* oldest first, and reports how many it removed. Schedule it on a cron.
|
|
1054
|
+
*
|
|
1055
|
+
* Compare `deleted` against `limit` to decide whether to run again rather
|
|
1056
|
+
* than assuming one pass drained the backlog.
|
|
1016
1057
|
*/
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
/** Ceiling on how many `orderBy` entries a request may ask for. Defaults to 8. */
|
|
1023
|
-
readonly maxOrderBy?: number;
|
|
1024
|
-
/** Allow-list of sortable columns. Pass `[]` to fix the order server-side. */
|
|
1025
|
-
readonly orderBy: ReadonlyArray<O>;
|
|
1026
|
-
}
|
|
1027
|
-
/** The validator map handed to `.input()`. Typed precisely so `args` infers end-to-end. */
|
|
1028
|
-
interface ListArgsValidators<F, O extends string> {
|
|
1029
|
-
cursor: ColumnValidator<null | number | string | undefined, null | number | string | undefined>;
|
|
1030
|
-
limit: ColumnValidator<number | undefined, number | undefined>;
|
|
1031
|
-
orderBy: ColumnValidator<ListOrderByEntry<O>[] | undefined, ListOrderByEntry<O>[] | undefined>;
|
|
1032
|
-
where: ColumnValidator<ListWhere<F> | undefined, ListWhere<F> | undefined>;
|
|
1058
|
+
vacuum: RegisteredMutation<{
|
|
1059
|
+
limit: ReturnType<typeof v.optional>;
|
|
1060
|
+
}, {
|
|
1061
|
+
deleted: number;
|
|
1062
|
+
}>;
|
|
1033
1063
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1064
|
+
/** The component shape {@link defineDocumentHistory} returns. */
|
|
1065
|
+
type DocumentHistoryComponent = {
|
|
1066
|
+
functions: DocumentHistoryFunctions;
|
|
1037
1067
|
/**
|
|
1038
|
-
*
|
|
1039
|
-
*
|
|
1040
|
-
* `{ field, direction }[]` into `ctx.db`'s `{ column: direction }[]`.
|
|
1068
|
+
* The `.triggers(...)` argument that records this table's versions —
|
|
1069
|
+
* `.triggers(history.record)`.
|
|
1041
1070
|
*
|
|
1042
|
-
*
|
|
1043
|
-
*
|
|
1044
|
-
*
|
|
1071
|
+
* `after*` on all three ops: a `before*` handler runs while the write can
|
|
1072
|
+
* still be aborted, and a history entry for a write that never happened is a
|
|
1073
|
+
* lie the reader has no way to detect.
|
|
1045
1074
|
*/
|
|
1046
|
-
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1075
|
+
record: (t: TriggerBuilder) => Record<string, TriggerDefinition>;
|
|
1076
|
+
} & Component<{
|
|
1077
|
+
[DOCUMENT_HISTORY_BARE_TABLE]: ReturnType<typeof defineTable>;
|
|
1078
|
+
}>;
|
|
1050
1079
|
/**
|
|
1051
|
-
*
|
|
1052
|
-
*
|
|
1053
|
-
* for the shape and the reasoning behind it.
|
|
1054
|
-
*
|
|
1055
|
-
* Curried on the document type: `defineListArgs<Doc<"messages">>()({ … })`. The
|
|
1056
|
-
* extra `()` buys the thing that matters — with `Doc` bound, `filter` keys and
|
|
1057
|
-
* `orderBy` entries are checked against the table's real columns, so a typo or a
|
|
1058
|
-
* column renamed out from under the endpoint is a COMPILE error instead of a
|
|
1059
|
-
* predicate that silently matches nothing. TypeScript has no partial type-argument
|
|
1060
|
-
* inference, so binding `Doc` explicitly while still inferring `F` and `O` from
|
|
1061
|
-
* the config requires the second call.
|
|
1080
|
+
* The document-history schema extension: one `versions` table, auto-namespaced
|
|
1081
|
+
* to `documentHistory_versions` at merge time.
|
|
1062
1082
|
*/
|
|
1063
|
-
declare const
|
|
1083
|
+
declare const documentHistoryExtension: SchemaExtension<{
|
|
1084
|
+
[DOCUMENT_HISTORY_BARE_TABLE]: ReturnType<typeof defineTable>;
|
|
1085
|
+
}>;
|
|
1064
1086
|
/**
|
|
1065
|
-
*
|
|
1066
|
-
* (`
|
|
1067
|
-
*
|
|
1068
|
-
*
|
|
1087
|
+
* Build a document-history {@link Component} — schema extension, the
|
|
1088
|
+
* `.triggers()` recorder, and the `listForDocument` / `vacuum` functions.
|
|
1089
|
+
* @param options history configuration (retention, redaction, snapshot cap).
|
|
1090
|
+
* @returns a component bundling the extension, the functions, and the recorder.
|
|
1091
|
+
*/
|
|
1092
|
+
declare const defineDocumentHistory: (options?: DefineDocumentHistoryOptions) => DocumentHistoryComponent;
|
|
1093
|
+
/**
|
|
1094
|
+
* Redact secrets from a free-form message. Masks, in order: any quoted value
|
|
1095
|
+
* whose contents look like a credential (so a value surfaced as `received string
|
|
1096
|
+
* "sk_live_…"` is masked even though the surrounding text is not a token); a
|
|
1097
|
+
* `scheme://user:password@host` URL credential (the password segment); any
|
|
1098
|
+
* known-prefix credential token wherever it appears, at any length; any value
|
|
1099
|
+
* following a secret-named key in `KEY=value` / `KEY: value` form; and any
|
|
1100
|
+
* remaining bare high-entropy ≥24-char token run anywhere in the message.
|
|
1069
1101
|
*
|
|
1070
|
-
*
|
|
1071
|
-
*
|
|
1072
|
-
*
|
|
1073
|
-
*
|
|
1074
|
-
*
|
|
1075
|
-
*
|
|
1076
|
-
*
|
|
1077
|
-
*
|
|
1078
|
-
* upstream type).
|
|
1102
|
+
* This is BEST-EFFORT defense-in-depth, NOT a guarantee: a short, prefix-less
|
|
1103
|
+
* secret under a non-secret-named key (and embedded credentials in shapes not
|
|
1104
|
+
* enumerated here) can still slip through. Treat it as a backstop — prefer
|
|
1105
|
+
* structured logging that never serializes raw env/secret fields in the first
|
|
1106
|
+
* place over relying on post-hoc scrubbing of untrusted data.
|
|
1107
|
+
*
|
|
1108
|
+
* Exported because it is independently useful — call it before logging anything
|
|
1109
|
+
* derived from `env`, request bodies, or thrown errors.
|
|
1079
1110
|
*/
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
interface RankPageRowLike {
|
|
1088
|
-
doc: Record<string, unknown>;
|
|
1089
|
-
key: RankPageRowKeyLike;
|
|
1090
|
-
}
|
|
1091
|
-
/** Structural mirror of `@lunora/shard-engine`'s `ShardRankPageResult` — the `rankPageRows` return shape. */
|
|
1092
|
-
interface ShardRankPageResultLike {
|
|
1093
|
-
directions: ReadonlyArray<"asc" | "desc">;
|
|
1094
|
-
hasMore: boolean;
|
|
1095
|
-
rows: ReadonlyArray<RankPageRowLike>;
|
|
1111
|
+
declare const redactSecrets: (message: string) => string;
|
|
1112
|
+
/** One key's validation failure, secrets already redacted out of `message`. */
|
|
1113
|
+
interface EnvKeyFailure {
|
|
1114
|
+
/** The env key that failed. */
|
|
1115
|
+
key: string;
|
|
1116
|
+
/** Redacted human-readable reason. */
|
|
1117
|
+
message: string;
|
|
1096
1118
|
}
|
|
1097
1119
|
/**
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1100
|
-
* `
|
|
1101
|
-
*
|
|
1102
|
-
*
|
|
1120
|
+
* Thrown when one or more env keys are missing or fail validation. Carries the
|
|
1121
|
+
* structured list of `failures` (each with the offending `key`) so callers can
|
|
1122
|
+
* react programmatically; `message` is the joined, secret-redacted summary.
|
|
1123
|
+
*
|
|
1124
|
+
* Named export only (no default) per the repo export convention.
|
|
1103
1125
|
*/
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
limit?: number;
|
|
1108
|
-
orderBy?: ReadonlyArray<unknown>;
|
|
1109
|
-
/**
|
|
1110
|
-
* Per-target-table read filter the RLS wrapper attaches so a `with` relation
|
|
1111
|
-
* is policy-filtered on its own hop (see `@lunora/do`'s `QueryArgs`). Mirrors
|
|
1112
|
-
* the top-level read: `(table) => readBase(table).baseWhere`.
|
|
1113
|
-
*/
|
|
1114
|
-
relationBaseWhere?: (table: string) => undefined | WhereInput;
|
|
1115
|
-
restrictsCounts?: boolean;
|
|
1116
|
-
where?: WhereInput;
|
|
1117
|
-
with?: Record<string, unknown>;
|
|
1118
|
-
}
|
|
1119
|
-
interface CountArgs {
|
|
1120
|
-
baseWhere?: WhereInput;
|
|
1121
|
-
relationBaseWhere?: (table: string) => undefined | WhereInput;
|
|
1122
|
-
restrictsCounts?: boolean;
|
|
1123
|
-
where?: WhereInput;
|
|
1124
|
-
}
|
|
1125
|
-
/** Structural mirror of `@lunora/do`'s `AggregateOptions` — only the fields the wrapper touches. */
|
|
1126
|
-
interface AggregateArgs$1 {
|
|
1127
|
-
baseWhere?: WhereInput;
|
|
1128
|
-
field?: string;
|
|
1129
|
-
op: string;
|
|
1130
|
-
relationBaseWhere?: (table: string) => undefined | WhereInput;
|
|
1131
|
-
restrictsCounts?: boolean;
|
|
1132
|
-
where?: WhereInput;
|
|
1133
|
-
}
|
|
1134
|
-
/** Structural mirror of `@lunora/do`'s `GroupByOptions`. */
|
|
1135
|
-
interface GroupByArgs$1 {
|
|
1136
|
-
agg?: {
|
|
1137
|
-
field?: string;
|
|
1138
|
-
op: string;
|
|
1139
|
-
};
|
|
1140
|
-
baseWhere?: WhereInput;
|
|
1141
|
-
by: ReadonlyArray<string>;
|
|
1142
|
-
relationBaseWhere?: (table: string) => undefined | WhereInput;
|
|
1143
|
-
restrictsCounts?: boolean;
|
|
1144
|
-
where?: WhereInput;
|
|
1145
|
-
}
|
|
1146
|
-
/** Structural mirror of `@lunora/do`'s `RankOptions`. */
|
|
1147
|
-
interface RankArgs {
|
|
1148
|
-
baseWhere?: WhereInput;
|
|
1149
|
-
restrictsCounts?: boolean;
|
|
1150
|
-
row: Record<string, unknown> | string;
|
|
1151
|
-
where?: WhereInput;
|
|
1152
|
-
}
|
|
1153
|
-
/** Structural mirror of `@lunora/do`'s `RankBeforeOptions`. */
|
|
1154
|
-
interface RankBeforeArgs {
|
|
1155
|
-
partitionKey: string;
|
|
1156
|
-
restrictsCounts?: boolean;
|
|
1157
|
-
rowId: string;
|
|
1158
|
-
sortValues: ReadonlyArray<unknown>;
|
|
1159
|
-
}
|
|
1160
|
-
/** Structural mirror of `@lunora/do`'s `RankPageOptions`. */
|
|
1161
|
-
interface RankPageArgs {
|
|
1162
|
-
baseWhere?: WhereInput;
|
|
1163
|
-
cursor?: null | string;
|
|
1164
|
-
restrictsCounts?: boolean;
|
|
1165
|
-
take?: number;
|
|
1166
|
-
where?: WhereInput;
|
|
1126
|
+
declare class LunoraEnvError extends LunoraError$1 {
|
|
1127
|
+
readonly failures: ReadonlyArray<EnvKeyFailure>;
|
|
1128
|
+
constructor(failures: ReadonlyArray<EnvKeyFailure>);
|
|
1167
1129
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1130
|
+
/** A record of `v.*` validators describing the expected env shape. */
|
|
1131
|
+
type EnvShape = Record<string, Validator>;
|
|
1132
|
+
/**
|
|
1133
|
+
* The typed output of {@link defineEnv}. Optional validators (`v.optional(...)`)
|
|
1134
|
+
* become optional keys; everything else is required. Mirrors how `InferArgs`
|
|
1135
|
+
* derives an args object from a validator map.
|
|
1136
|
+
*/
|
|
1137
|
+
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]>; };
|
|
1138
|
+
/**
|
|
1139
|
+
* The accessor returned by {@link defineEnv}. A typed view over an `env` object
|
|
1140
|
+
* plus a `.parse(env)` escape hatch that validates every key eagerly.
|
|
1141
|
+
*
|
|
1142
|
+
* Call the accessor with the worker's `env` to get the typed, lazily-validated
|
|
1143
|
+
* proxy: `const config = defineEnv({ … }); const { PORT } = config(env);`.
|
|
1144
|
+
*/
|
|
1145
|
+
interface EnvAccessor<S extends EnvShape> {
|
|
1146
|
+
/** Validate every key eagerly and return the typed, plain (non-proxy) object. Use for fail-fast-at-boot. */
|
|
1147
|
+
parse: (env: unknown) => InferEnv<S>;
|
|
1148
|
+
/** Lazily-validated, per-key-cached typed view over `env`. Keys are validated on first access. */
|
|
1149
|
+
(env: unknown): InferEnv<S>;
|
|
1172
1150
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1151
|
+
/**
|
|
1152
|
+
* Define a typed, validated accessor over a Worker's `env`. Pass a record of
|
|
1153
|
+
* `v.*` validators; receive an accessor that validates lazily per key (cached
|
|
1154
|
+
* per `env` identity) and infers its output type from the validators.
|
|
1155
|
+
*
|
|
1156
|
+
* ```ts
|
|
1157
|
+
* import { defineEnv, v } from "@lunora/server";
|
|
1158
|
+
*
|
|
1159
|
+
* const config = defineEnv({
|
|
1160
|
+
* STRIPE_KEY: v.string(),
|
|
1161
|
+
* PORT: v.optional(v.number()),
|
|
1162
|
+
* });
|
|
1163
|
+
*
|
|
1164
|
+
* export default {
|
|
1165
|
+
* fetch(request, env) {
|
|
1166
|
+
* const { STRIPE_KEY, PORT } = config(env); // STRIPE_KEY: string, PORT?: number
|
|
1167
|
+
* // …
|
|
1168
|
+
* },
|
|
1169
|
+
* };
|
|
1170
|
+
* ```
|
|
1171
|
+
*
|
|
1172
|
+
* Throws {@link LunoraEnvError} (secrets redacted) when a key is missing or
|
|
1173
|
+
* invalid — lazily on first access of that key, or eagerly via `config.parse(env)`.
|
|
1174
|
+
*/
|
|
1175
|
+
declare const defineEnv: <S extends EnvShape>(shape: S) => EnvAccessor<S>;
|
|
1176
|
+
declare class LunoraError extends LunoraError$1 {
|
|
1177
|
+
constructor(code: LunoraErrorCode, message?: string, data?: unknown);
|
|
1185
1178
|
}
|
|
1186
1179
|
/**
|
|
1187
|
-
*
|
|
1188
|
-
*
|
|
1189
|
-
* `DatabaseWriterLike` and
|
|
1180
|
+
* Minimal structural writer the facade binds over. Declared with **method**
|
|
1181
|
+
* syntax (not arrow properties) so a more-specifically-typed writer — both
|
|
1182
|
+
* `@lunora/do`'s `DatabaseWriterLike` and the RLS middleware's wrapped writer —
|
|
1183
|
+
* stays assignable under bivariant parameter checking. That is the whole reason
|
|
1184
|
+
* the shared helper can serve both callers, hence the rule exemption.
|
|
1190
1185
|
*/
|
|
1191
|
-
interface
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
* (safe: an aggregate scoped to `where` never reveals a hidden row — see
|
|
1196
|
-
* `@lunora/do`'s `RestrictableQueryOptions`). Required: the only writer ever
|
|
1197
|
-
* wrapped is `@lunora/do`'s `createShardCtxDb`, which always implements it.
|
|
1198
|
-
*/
|
|
1199
|
-
aggregate: (tableName: string, options: AggregateArgs$1) => Promise<null | number>;
|
|
1200
|
-
count: (tableName: string, whereOrArgs?: CountArgs | WhereInput) => Promise<number>;
|
|
1201
|
-
delete: (id: string, expectedTable?: string, options?: {
|
|
1202
|
-
hard?: boolean;
|
|
1203
|
-
}) => Promise<void>;
|
|
1204
|
-
/** Uncapped, chunked erase of a whole table. The RLS wrapper gates each row like a single delete. */
|
|
1205
|
-
deleteAll?: (tableName: string, options?: {
|
|
1206
|
-
chunkSize?: number;
|
|
1186
|
+
interface FacadeWriterLike {
|
|
1187
|
+
aggregate(tableName: string, options: unknown): Promise<unknown>;
|
|
1188
|
+
count(tableName: string, where?: unknown): Promise<number>;
|
|
1189
|
+
delete(id: string, expectedTable?: string, options?: {
|
|
1207
1190
|
hard?: boolean;
|
|
1208
|
-
})
|
|
1209
|
-
|
|
1210
|
-
}>;
|
|
1211
|
-
deleteMany: (ids: ReadonlyArray<string>, options?: {
|
|
1191
|
+
}): Promise<void>;
|
|
1192
|
+
deleteMany?(ids: ReadonlyArray<string>, options?: {
|
|
1212
1193
|
limit?: number;
|
|
1213
|
-
}, expectedTable?: string)
|
|
1194
|
+
}, expectedTable?: string): Promise<{
|
|
1214
1195
|
deleted: number;
|
|
1215
1196
|
}>;
|
|
1216
|
-
deleteWhere
|
|
1197
|
+
deleteWhere?(tableName: string, where: Record<string, unknown>, options?: {
|
|
1217
1198
|
limit?: number;
|
|
1218
|
-
})
|
|
1199
|
+
}): Promise<{
|
|
1219
1200
|
deleted: number;
|
|
1220
1201
|
}>;
|
|
1221
|
-
findFirst
|
|
1222
|
-
findFirstOrThrow
|
|
1223
|
-
findMany
|
|
1224
|
-
get
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
* rows the caller may read. Required for the same reason as `aggregate`.
|
|
1229
|
-
*/
|
|
1230
|
-
groupBy: (tableName: string, options: GroupByArgs$1) => Promise<ReadonlyArray<{
|
|
1231
|
-
key: Record<string, unknown>;
|
|
1232
|
-
value: null | number;
|
|
1233
|
-
}>>;
|
|
1234
|
-
insert: (tableName: string, document: Record<string, unknown>) => Promise<string>;
|
|
1235
|
-
insertMany: (tableName: string, documents: ReadonlyArray<Record<string, unknown>>, options?: {
|
|
1202
|
+
findFirst(tableName: string, args?: unknown): Promise<unknown>;
|
|
1203
|
+
findFirstOrThrow(tableName: string, args?: unknown): Promise<unknown>;
|
|
1204
|
+
findMany(tableName: string, args?: unknown): Promise<unknown>;
|
|
1205
|
+
get(id: string, expectedTable?: string): Promise<unknown>;
|
|
1206
|
+
groupBy(tableName: string, options: unknown): Promise<unknown>;
|
|
1207
|
+
insert(tableName: string, document: Record<string, unknown>): Promise<string>;
|
|
1208
|
+
insertMany?(tableName: string, documents: ReadonlyArray<Record<string, unknown>>, options?: {
|
|
1236
1209
|
limit?: number;
|
|
1237
1210
|
skipDuplicates?: boolean;
|
|
1238
|
-
})
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
limit?: number;
|
|
1242
|
-
}) => Promise<string[]>;
|
|
1243
|
-
/**
|
|
1244
|
-
* Optional table-aware lookup. The underlying writer (e.g. `@lunora/do`)
|
|
1245
|
-
* already knows the owning table of an id internally, so it can return
|
|
1246
|
-
* `{ row, tableName }` in a single round-trip. When present, the RLS wrapper
|
|
1247
|
-
* uses it to collapse the per-call membership-probe fan-out (1 `get` + N
|
|
1248
|
-
* `findFirst` across every policy table) down to one lookup. Writers that
|
|
1249
|
-
* don't implement it fall back to the probe path.
|
|
1250
|
-
*/
|
|
1251
|
-
lookupById?: (id: string, expectedTable?: string) => Promise<null | {
|
|
1252
|
-
row: Record<string, unknown>;
|
|
1253
|
-
tableName: string;
|
|
1254
|
-
}>;
|
|
1255
|
-
patch: (id: string, patch: Record<string, unknown>, expectedTable?: string) => Promise<void>;
|
|
1256
|
-
patchMany: (patches: ReadonlyArray<{
|
|
1211
|
+
}): Promise<(string | null)[]>;
|
|
1212
|
+
patch(id: string, patch: Record<string, unknown>, expectedTable?: string): Promise<void>;
|
|
1213
|
+
patchMany?(patches: ReadonlyArray<{
|
|
1257
1214
|
id: string;
|
|
1258
1215
|
patch: Record<string, unknown>;
|
|
1259
1216
|
}>, options?: {
|
|
1260
1217
|
limit?: number;
|
|
1261
|
-
}, expectedTable?: string)
|
|
1218
|
+
}, expectedTable?: string): Promise<{
|
|
1262
1219
|
patched: number;
|
|
1263
1220
|
}>;
|
|
1264
|
-
patchWhere
|
|
1221
|
+
patchWhere?(tableName: string, args: {
|
|
1265
1222
|
patch: Record<string, unknown>;
|
|
1266
|
-
where:
|
|
1223
|
+
where: Record<string, unknown>;
|
|
1267
1224
|
}, options?: {
|
|
1268
1225
|
limit?: number;
|
|
1269
|
-
})
|
|
1226
|
+
}): Promise<{
|
|
1270
1227
|
patched: number;
|
|
1271
1228
|
}>;
|
|
1272
|
-
query
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1229
|
+
query(tableName: string): {
|
|
1230
|
+
withGeoIndex(indexName: string, build: (q: unknown) => unknown): unknown;
|
|
1231
|
+
withSearchIndex(indexName: string, search: (q: unknown) => unknown): unknown;
|
|
1232
|
+
};
|
|
1233
|
+
rank(tableName: string, indexName: string, options: unknown): Promise<unknown>;
|
|
1234
|
+
rankPage(tableName: string, indexName: string, options?: unknown): Promise<unknown>;
|
|
1235
|
+
replace(id: string, document: Record<string, unknown>, expectedTable?: string): Promise<void>;
|
|
1236
|
+
restore?(id: string, expectedTable?: string): Promise<void>;
|
|
1237
|
+
}
|
|
1238
|
+
/** The per-table accessor object returned for the `ctx.db` table form. */
|
|
1239
|
+
interface FacadeEntry {
|
|
1240
|
+
aggregate: (options: unknown) => Promise<unknown>;
|
|
1241
|
+
count: (where?: unknown) => Promise<number>;
|
|
1242
|
+
delete: (id: string) => Promise<void>;
|
|
1243
|
+
deleteMany: {
|
|
1244
|
+
(ids: ReadonlyArray<string>, options?: {
|
|
1245
|
+
limit?: number;
|
|
1246
|
+
}): Promise<{
|
|
1247
|
+
deleted: number;
|
|
1248
|
+
}>;
|
|
1249
|
+
(args: {
|
|
1250
|
+
limit?: number;
|
|
1251
|
+
where: Record<string, unknown>;
|
|
1252
|
+
}): Promise<{
|
|
1253
|
+
deleted: number;
|
|
1254
|
+
}>;
|
|
1255
|
+
};
|
|
1256
|
+
/** `true` when at least one row matches `where` (or any row exists when omitted). Honors RLS like `findFirst`. */
|
|
1257
|
+
exists: (where?: unknown) => Promise<boolean>;
|
|
1258
|
+
findFirst: (args?: unknown) => Promise<unknown>;
|
|
1259
|
+
findFirstOrThrow: (args?: unknown) => Promise<unknown>;
|
|
1260
|
+
findMany: (args?: unknown) => Promise<unknown>;
|
|
1261
|
+
get: (id: string) => Promise<unknown>;
|
|
1262
|
+
groupBy: (options: unknown) => Promise<unknown>;
|
|
1263
|
+
/** Physically remove a row (and physically cascade), bypassing `.softDelete()`. */
|
|
1264
|
+
hardDelete: (id: string) => Promise<void>;
|
|
1265
|
+
insert: (document: Record<string, unknown>, options?: FacadeInsertOptions) => Promise<null | string>;
|
|
1296
1266
|
/**
|
|
1297
|
-
*
|
|
1298
|
-
*
|
|
1299
|
-
*
|
|
1300
|
-
*
|
|
1267
|
+
* Insert many documents into this table in one call. With
|
|
1268
|
+
* `{ skipDuplicates: true }`, UNIQUE breaches resolve to `null` for that row
|
|
1269
|
+
* instead of failing the batch. The typed facade narrows the return to
|
|
1270
|
+
* `Id<T>[]` when skipDuplicates is not requested.
|
|
1301
1271
|
*/
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1272
|
+
insertMany: (documents: ReadonlyArray<Record<string, unknown>>, options?: {
|
|
1273
|
+
limit?: number;
|
|
1274
|
+
skipDuplicates?: boolean;
|
|
1275
|
+
}) => Promise<(string | null)[]>;
|
|
1276
|
+
patch: (id: string, patch: Record<string, unknown>) => Promise<void>;
|
|
1277
|
+
patchMany: {
|
|
1278
|
+
(patches: ReadonlyArray<{
|
|
1279
|
+
id: string;
|
|
1280
|
+
values: Record<string, unknown>;
|
|
1281
|
+
}>, options?: {
|
|
1282
|
+
limit?: number;
|
|
1283
|
+
}): Promise<{
|
|
1284
|
+
patched: number;
|
|
1285
|
+
}>;
|
|
1286
|
+
(args: {
|
|
1287
|
+
limit?: number;
|
|
1288
|
+
values: Record<string, unknown>;
|
|
1289
|
+
where: Record<string, unknown>;
|
|
1290
|
+
}): Promise<{
|
|
1291
|
+
patched: number;
|
|
1292
|
+
}>;
|
|
1293
|
+
};
|
|
1294
|
+
rank: (indexName: string, options: unknown) => Promise<unknown>;
|
|
1295
|
+
rankPage: (indexName: string, options?: unknown) => Promise<unknown>;
|
|
1296
|
+
replace: (id: string, document: Record<string, unknown>) => Promise<void>;
|
|
1297
|
+
/** Un-soft-delete a row: clears the `.softDelete()` marker (by-id, so it reaches a row list reads hide). */
|
|
1298
|
+
restore: (id: string) => Promise<void>;
|
|
1299
|
+
/** Insert when no row matches `target`, else patch the match. Composes `findFirst` + `insert`/`patch`, so RLS applies to each step. */
|
|
1300
|
+
upsert: (args: UpsertArgs) => Promise<UpsertResult>;
|
|
1301
|
+
/** Sequential `upsert` over many rows sharing one `target`; returns one result per input row in order. */
|
|
1302
|
+
upsertMany: (args: UpsertManyArgs) => Promise<UpsertResult[]>;
|
|
1303
|
+
withGeoIndex: (indexName: string, build: (q: unknown) => unknown) => unknown;
|
|
1304
|
+
withSearchIndex: (indexName: string, search: (q: unknown) => unknown) => unknown;
|
|
1305
|
+
}
|
|
1306
|
+
/** Options accepted by the per-table `insert` accessor. */
|
|
1307
|
+
interface FacadeInsertOptions {
|
|
1305
1308
|
/**
|
|
1306
|
-
*
|
|
1307
|
-
*
|
|
1309
|
+
* When `true`, a UNIQUE-constraint breach is swallowed: the insert becomes a
|
|
1310
|
+
* silent no-op and resolves to `null` instead of throwing a `CONFLICT`. Any
|
|
1311
|
+
* other error still propagates. Mirrors better-drizzle's `create({ skipDuplicates })`.
|
|
1308
1312
|
*/
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1313
|
+
skipDuplicates?: boolean;
|
|
1314
|
+
}
|
|
1315
|
+
/** The conflict target for `upsert`/`upsertMany`: one field name or a tuple of them. */
|
|
1316
|
+
type UpsertTarget = ReadonlyArray<string> | string;
|
|
1317
|
+
/** Argument to the per-table `upsert` accessor. */
|
|
1318
|
+
interface UpsertArgs {
|
|
1319
|
+
/** Document inserted when no existing row matches the `target`. */
|
|
1320
|
+
create: Record<string, unknown>;
|
|
1321
|
+
/** Field(s) — typically a `.unique()` column or unique index — used to look up an existing row. */
|
|
1322
|
+
target: UpsertTarget;
|
|
1323
|
+
/** Patch applied when an existing row matches the `target`. Defaults to `create`. */
|
|
1324
|
+
update?: Record<string, unknown>;
|
|
1325
|
+
}
|
|
1326
|
+
/** Result of an `upsert`: the row's id and whether it was freshly inserted (`true`) or updated (`false`). */
|
|
1327
|
+
interface UpsertResult {
|
|
1328
|
+
created: boolean;
|
|
1329
|
+
id: string;
|
|
1330
|
+
}
|
|
1331
|
+
/** Argument to the per-table `upsertMany` accessor — a shared `target` plus per-row create/update payloads. */
|
|
1332
|
+
interface UpsertManyArgs {
|
|
1333
|
+
rows: ReadonlyArray<{
|
|
1334
|
+
create: Record<string, unknown>;
|
|
1335
|
+
update?: Record<string, unknown>;
|
|
1316
1336
|
}>;
|
|
1337
|
+
target: UpsertTarget;
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Bind a structural writer to one table, producing its `ctx.db` table accessor.
|
|
1341
|
+
*
|
|
1342
|
+
* The by-id accessors (`get`/`delete`/`patch`/`replace`) forward the bound
|
|
1343
|
+
* `tableName` as `expectedTable` so the underlying writer scopes its id lookup
|
|
1344
|
+
* to this table. Without it, a branded `Id<"posts">` carrying another table's
|
|
1345
|
+
* id would resolve cross-table (the writer probes every table by id), letting
|
|
1346
|
+
* `ctx.db.posts.get(foreignId)` read — or `.delete`/`.patch`/`.replace`
|
|
1347
|
+
* mutate — a row in an unrelated table (IDOR). Writers that ignore the second
|
|
1348
|
+
* argument keep their previous global behaviour; the scoping is opt-in via this
|
|
1349
|
+
* forwarded name.
|
|
1350
|
+
*/
|
|
1351
|
+
declare const bindTableFacade: (writer: FacadeWriterLike, tableName: string) => FacadeEntry;
|
|
1352
|
+
/** The kitcn-style `ctx.orm` namespace over a per-table facade map. */
|
|
1353
|
+
interface OrmLike {
|
|
1354
|
+
delete: (table: string, id: string) => Promise<void>;
|
|
1355
|
+
insert: (table: string) => {
|
|
1356
|
+
values: (document: Record<string, unknown>) => Promise<null | string>;
|
|
1357
|
+
};
|
|
1358
|
+
query: Record<string, FacadeEntry>;
|
|
1359
|
+
replace: (table: string, id: string) => {
|
|
1360
|
+
with: (document: Record<string, unknown>) => Promise<void>;
|
|
1361
|
+
};
|
|
1362
|
+
update: (table: string, id: string) => {
|
|
1363
|
+
set: (values: Record<string, unknown>) => Promise<void>;
|
|
1364
|
+
};
|
|
1317
1365
|
}
|
|
1366
|
+
/** Build `ctx.orm` over a per-table facade map (table name → FacadeEntry). */
|
|
1367
|
+
declare const bindOrm: (facade: Record<string, FacadeEntry>) => OrmLike;
|
|
1368
|
+
/** HTTP verbs the typed {@link httpRoute} builder can bind to. */
|
|
1369
|
+
type HttpMethod = "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT";
|
|
1318
1370
|
/**
|
|
1319
|
-
*
|
|
1320
|
-
*
|
|
1321
|
-
*
|
|
1322
|
-
*
|
|
1323
|
-
*
|
|
1324
|
-
*
|
|
1371
|
+
* Context handed to an HTTP action handler. A narrower view of {@link ActionContext}:
|
|
1372
|
+
* HTTP actions run in the worker (the "action runtime"), separate from the
|
|
1373
|
+
* transactional store, so there is no direct `db` / `vectors` surface — reach the
|
|
1374
|
+
* data layer through `runQuery` / `runMutation` / `runAction`, which forward to
|
|
1375
|
+
* the owning shard. `db`'s absence is principled: an HTTP handler is not
|
|
1376
|
+
* transactional.
|
|
1377
|
+
*
|
|
1378
|
+
* `scheduler` and `storage` ARE present, because neither needs the shard — the
|
|
1379
|
+
* scheduler talks to the scheduler DO, and R2 is a worker binding an HTTP
|
|
1380
|
+
* handler can reach where an action does. Both are optional: each exists only
|
|
1381
|
+
* when the app declared the matching capability (`.scheduler(...)` /
|
|
1382
|
+
* `.storage(...)`) on the generated app builder.
|
|
1383
|
+
*
|
|
1384
|
+
* Omitting them was costly out of proportion to the gap. Without `scheduler`,
|
|
1385
|
+
* "receive webhook → enqueue the real work → return 200" — the shape HTTP
|
|
1386
|
+
* actions exist for — forced a hop through a mutation plus a closed allow-list
|
|
1387
|
+
* of target strings, because a function reference cannot cross the RPC boundary
|
|
1388
|
+
* and a free-form target on an unauthenticated endpoint is a "call any internal
|
|
1389
|
+
* function" primitive. Without `storage`, any helper the ctx was threaded into
|
|
1390
|
+
* had to be typed for its storage-touching branch, so a handler was barred from
|
|
1391
|
+
* the helper even on the branches that never went near storage.
|
|
1325
1392
|
*/
|
|
1326
|
-
type
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
getIdentity?: () => Promise<Record<string, unknown> | null>;
|
|
1330
|
-
roles?: ReadonlyArray<string>;
|
|
1331
|
-
userId?: null | string;
|
|
1393
|
+
type HttpActionCtx = Pick<ActionCtx, "auth" | "cache" | "fetch" | "runAction" | "runMutation" | "runQuery"> & {
|
|
1394
|
+
readonly scheduler?: ActionCtx["scheduler"];
|
|
1395
|
+
readonly storage?: ActionCtx["storage"];
|
|
1332
1396
|
};
|
|
1333
|
-
/**
|
|
1334
|
-
|
|
1335
|
-
auth?: AuthLike;
|
|
1336
|
-
db: RlsDatabase;
|
|
1337
|
-
}
|
|
1338
|
-
declare const rls: <Context extends RlsContextIn = RlsContextIn>(policies: ReadonlyArray<Policy<Context>>, options?: RlsOptions) => Middleware<Context, Context>;
|
|
1397
|
+
/** A raw handler wrapped by {@link httpAction}. Receives the raw request, returns the raw response. */
|
|
1398
|
+
type HttpActionHandler = (context: HttpActionCtx, request: Request) => Promise<Response> | Response;
|
|
1339
1399
|
/**
|
|
1340
|
-
* The
|
|
1341
|
-
*
|
|
1342
|
-
*
|
|
1400
|
+
* The hono {@link https://hono.dev | Hono} environment used by {@link httpRouter}.
|
|
1401
|
+
* The runtime injects the per-request {@link HttpActionCtx} on the private
|
|
1402
|
+
* `__lunoraCtx` binding; the router's lifting middleware promotes it to
|
|
1403
|
+
* `c.var.lunora` so handlers can read it as a typed variable.
|
|
1343
1404
|
*/
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1405
|
+
interface LunoraHttpEnv {
|
|
1406
|
+
Bindings: Record<string, unknown> & {
|
|
1407
|
+
__lunoraCtx?: HttpActionCtx;
|
|
1347
1408
|
};
|
|
1348
|
-
|
|
1409
|
+
Variables: {
|
|
1410
|
+
lunora: HttpActionCtx;
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
/** The hono app type {@link httpRouter} returns. */
|
|
1414
|
+
type LunoraHttpApp = Hono<LunoraHttpEnv>;
|
|
1415
|
+
/** A compiled route handler: a hono handler that resolves to a raw {@link Response}. */
|
|
1416
|
+
type LunoraRouteHandler = (c: Context<LunoraHttpEnv>) => Promise<Response>;
|
|
1349
1417
|
/**
|
|
1350
|
-
*
|
|
1351
|
-
*
|
|
1352
|
-
* {@link
|
|
1418
|
+
* Wrap a `(ctx, request) => Response` handler as a hono handler. The raw escape
|
|
1419
|
+
* hatch — mount it with `app.all(path, httpAction(fn))`. `ctx` is the
|
|
1420
|
+
* runtime-injected {@link HttpActionCtx} lifted into `c.var.lunora` by
|
|
1421
|
+
* {@link httpRouter}; `request` is the underlying `c.req.raw`.
|
|
1353
1422
|
*/
|
|
1354
|
-
|
|
1423
|
+
declare const httpAction: (handler: HttpActionHandler) => LunoraRouteHandler;
|
|
1355
1424
|
/**
|
|
1356
|
-
*
|
|
1357
|
-
* `
|
|
1358
|
-
*
|
|
1359
|
-
*
|
|
1425
|
+
* Create the hono app for HTTP actions. Pre-wired with a middleware that lifts
|
|
1426
|
+
* the runtime-injected `c.env.__lunoraCtx` into `c.var.lunora`, so both
|
|
1427
|
+
* {@link httpAction} and the typed {@link httpRoute} builder can read the action
|
|
1428
|
+
* context. The full hono surface is available — plugins, path params, `.route`:
|
|
1429
|
+
*
|
|
1430
|
+
* ```ts
|
|
1431
|
+
* const app = httpRouter();
|
|
1432
|
+
* app.use("*", cors());
|
|
1433
|
+
* app.post("/webhook", httpAction(onWebhook));
|
|
1434
|
+
* app.get("/users/:id", getUser);
|
|
1435
|
+
* export default createWorker({ httpRouter: app, ... });
|
|
1436
|
+
* ```
|
|
1437
|
+
*
|
|
1438
|
+
* The lifting middleware throws if the context is absent. `createWorker` injects
|
|
1439
|
+
* it on every request the router sees, so this only trips when the app is run
|
|
1440
|
+
* outside the runtime — a misconfiguration we surface loudly rather than let
|
|
1441
|
+
* `c.var.lunora` be silently `undefined` despite its non-optional type.
|
|
1360
1442
|
*/
|
|
1361
|
-
|
|
1443
|
+
declare const httpRouter: () => LunoraHttpApp;
|
|
1444
|
+
/** The `{ ctx, searchParams, body, params }` a typed route handler receives. */
|
|
1445
|
+
interface HttpRouteHandlerOptions<SearchParams extends ArgsValidator, Body extends ArgsValidator, Params extends ArgsValidator> {
|
|
1446
|
+
body: InferArgs<Body>;
|
|
1447
|
+
ctx: HttpActionCtx;
|
|
1448
|
+
params: InferArgs<Params>;
|
|
1449
|
+
searchParams: InferArgs<SearchParams>;
|
|
1450
|
+
}
|
|
1362
1451
|
/**
|
|
1363
|
-
*
|
|
1364
|
-
*
|
|
1365
|
-
* `
|
|
1366
|
-
*
|
|
1452
|
+
* The `{ ctx, searchParams, params, request, signal }` a streaming HTTP
|
|
1453
|
+
* handler receives. There is no parsed `body` — streams are typically GET, and
|
|
1454
|
+
* the raw `request` is exposed if a handler needs to read the body itself.
|
|
1455
|
+
* `signal` is tripped when the client disconnects.
|
|
1456
|
+
* @experimental Part of the HTTP-SSE stream surface; reconnect/POST-body design questions are still open.
|
|
1367
1457
|
*/
|
|
1368
|
-
interface
|
|
1369
|
-
|
|
1370
|
-
|
|
1458
|
+
interface HttpStreamHandlerOptions<SearchParams extends ArgsValidator, Params extends ArgsValidator> {
|
|
1459
|
+
ctx: HttpActionCtx;
|
|
1460
|
+
params: InferArgs<Params>;
|
|
1461
|
+
request: Request;
|
|
1462
|
+
searchParams: InferArgs<SearchParams>;
|
|
1463
|
+
signal: AbortSignal;
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* A typed REST route under construction. `.searchParams()` / `.body()` /
|
|
1467
|
+
* `.params()` accumulate validator maps (later calls merge, a colliding key
|
|
1468
|
+
* wins) that decode the URL query, JSON body, and hono path params into the
|
|
1469
|
+
* handler's typed `searchParams` / `body` / `params`. Like the procedure
|
|
1470
|
+
* builder, `.output(validator)` defaults to the `undefined` sentinel — while
|
|
1471
|
+
* unset the handler is generic over its own return; once set the handler must
|
|
1472
|
+
* return that type and the result is parsed through the validator before
|
|
1473
|
+
* serialization. `[Output] extends [undefined]` is tuple-wrapped so a union
|
|
1474
|
+
* `Output` doesn't distribute and the test is for the exact sentinel.
|
|
1475
|
+
*
|
|
1476
|
+
* The terminal `.handler()` yields a {@link LunoraRouteHandler} — mount it
|
|
1477
|
+
* directly with `app.get(path, route)`.
|
|
1478
|
+
*/
|
|
1479
|
+
interface HttpRouteBuilder<SearchParams extends ArgsValidator, Body extends ArgsValidator, Params extends ArgsValidator, Output = undefined> {
|
|
1480
|
+
body: <B extends ArgsValidator>(validators: B) => HttpRouteBuilder<SearchParams, B & Body, Params, Output>;
|
|
1371
1481
|
/**
|
|
1372
|
-
*
|
|
1373
|
-
*
|
|
1374
|
-
* collide with an app table; do **not** namespace manually.
|
|
1482
|
+
* Attach a `Cache-Control` header to the response. Only meaningful when
|
|
1483
|
+
* Workers Cache is enabled in `wrangler.jsonc` (`"cache": { "enabled": true }`).
|
|
1375
1484
|
*/
|
|
1376
|
-
|
|
1485
|
+
cacheControl: (value: string) => HttpRouteBuilder<SearchParams, Body, Params, Output>;
|
|
1377
1486
|
/**
|
|
1378
|
-
*
|
|
1379
|
-
*
|
|
1380
|
-
* with the base schema is a hard error (same policy as tables).
|
|
1487
|
+
* Attach a `Cache-Tag` header to the response for tag-based purging via
|
|
1488
|
+
* `ctx.cache.purge({ tags: [...] })`.
|
|
1381
1489
|
*/
|
|
1382
|
-
|
|
1490
|
+
cacheTag: (value: string) => HttpRouteBuilder<SearchParams, Body, Params, Output>;
|
|
1491
|
+
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;
|
|
1492
|
+
output: <V extends Validator>(validator: V) => HttpRouteBuilder<SearchParams, Body, Params, Infer<V>>;
|
|
1493
|
+
params: <P extends ArgsValidator>(validators: P) => HttpRouteBuilder<SearchParams, Body, P & Params, Output>;
|
|
1494
|
+
searchParams: <S extends ArgsValidator>(validators: S) => HttpRouteBuilder<S & SearchParams, Body, Params, Output>;
|
|
1495
|
+
/**
|
|
1496
|
+
* Terminal: declare this route as a streaming Server-Sent Events endpoint.
|
|
1497
|
+
* The handler is an async generator (or any function returning an
|
|
1498
|
+
* `AsyncIterable<R>`) that yields one chunk per SSE `data:` frame; on
|
|
1499
|
+
* iterator completion the route writes a final `event: complete` frame; on
|
|
1500
|
+
* throw, an `event: error` frame is written with `{code, message}` before
|
|
1501
|
+
* the stream closes. The chunks are JSON-encoded; `R` is inferred from the
|
|
1502
|
+
* handler's yielded type.
|
|
1503
|
+
* @experimental Reconnect/POST-body/wire-fidelity design questions are still open, so the shape may change.
|
|
1504
|
+
*/
|
|
1505
|
+
stream: <R>(handler: (options: HttpStreamHandlerOptions<SearchParams, Params>) => AsyncGenerator<R, void, void> | AsyncIterable<R>) => LunoraRouteHandler;
|
|
1506
|
+
/**
|
|
1507
|
+
* Attach a `Vary` header to the response so Cloudflare stores separate
|
|
1508
|
+
* cached variants per distinct value of the listed request headers.
|
|
1509
|
+
*/
|
|
1510
|
+
vary: (value: string) => HttpRouteBuilder<SearchParams, Body, Params, Output>;
|
|
1511
|
+
}
|
|
1512
|
+
/** Opens a fresh {@link HttpRouteBuilder}. The `path` documents intent; hono owns the actual routing at mount. */
|
|
1513
|
+
type HttpRouteFactory = (path: string) => HttpRouteBuilder<EmptyArgs, EmptyArgs, EmptyArgs>;
|
|
1514
|
+
/** The verb-keyed entry point: `httpRoute.get("/api/todos")…`. */
|
|
1515
|
+
interface HttpRoute {
|
|
1516
|
+
delete: HttpRouteFactory;
|
|
1517
|
+
get: HttpRouteFactory;
|
|
1518
|
+
head: HttpRouteFactory;
|
|
1519
|
+
options: HttpRouteFactory;
|
|
1520
|
+
patch: HttpRouteFactory;
|
|
1521
|
+
post: HttpRouteFactory;
|
|
1522
|
+
put: HttpRouteFactory;
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Typed REST route builder. Compiles down to a {@link LunoraRouteHandler}, so a
|
|
1526
|
+
* typed route and a hand-written {@link httpAction} are interchangeable when
|
|
1527
|
+
* mounted on {@link httpRouter}:
|
|
1528
|
+
*
|
|
1529
|
+
* ```ts
|
|
1530
|
+
* export const listTodos = httpRoute
|
|
1531
|
+
* .get("/api/todos")
|
|
1532
|
+
* .searchParams({ limit: v.number(), q: v.optional(v.string()) })
|
|
1533
|
+
* .output(v.array(v.object({ id: v.string(), text: v.string() })))
|
|
1534
|
+
* .handler(async ({ ctx, searchParams }) => ctx.runQuery(api.todos.list, searchParams));
|
|
1535
|
+
*
|
|
1536
|
+
* export const getTodo = httpRoute
|
|
1537
|
+
* .get("/api/todos/:id")
|
|
1538
|
+
* .params({ id: v.string() })
|
|
1539
|
+
* .handler(async ({ ctx, params }) => ctx.runQuery(api.todos.get, params));
|
|
1540
|
+
*
|
|
1541
|
+
* const app = httpRouter();
|
|
1542
|
+
* app.get("/api/todos", listTodos);
|
|
1543
|
+
* app.get("/api/todos/:id", getTodo);
|
|
1544
|
+
* ```
|
|
1545
|
+
*/
|
|
1546
|
+
declare const httpRoute: HttpRoute;
|
|
1547
|
+
/**
|
|
1548
|
+
* Structural view of an R2 object body, as returned by `@lunora/storage`'s
|
|
1549
|
+
* `download()`. Re-declared here (not imported) so `@lunora/server` takes no
|
|
1550
|
+
* runtime dependency on `@lunora/storage`; the real binding satisfies the shape.
|
|
1551
|
+
*/
|
|
1552
|
+
interface StorageObjectBody {
|
|
1553
|
+
/** The object body stream (`null` for a zero-byte object). */
|
|
1554
|
+
body: ReadableStream | null;
|
|
1555
|
+
etag: string;
|
|
1556
|
+
httpMetadata?: {
|
|
1557
|
+
contentType?: string;
|
|
1558
|
+
};
|
|
1559
|
+
key: string;
|
|
1560
|
+
/** Hex SHA-256, when R2 carries a checksum (surfaced by `@lunora/storage`). */
|
|
1561
|
+
sha256?: string;
|
|
1562
|
+
/** Base64 SHA-256 (RFC 9530 digest encoding), when R2 carries a checksum. */
|
|
1563
|
+
sha256Base64?: string;
|
|
1564
|
+
size: number;
|
|
1565
|
+
}
|
|
1566
|
+
/** Byte window forwarded to `download()` so R2 streams just the requested slice. */
|
|
1567
|
+
interface StorageRange {
|
|
1568
|
+
length: number;
|
|
1569
|
+
offset: number;
|
|
1383
1570
|
}
|
|
1384
1571
|
/**
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
* A plugin packages an optional schema extension and optional middleware.
|
|
1394
|
-
* Both are independently usable: an app can install only the schema (e.g.
|
|
1395
|
-
* for plugins that ship background workers but no per-request behavior)
|
|
1396
|
-
* or only the middleware (plugins that augment ctx without persistent
|
|
1397
|
-
* state).
|
|
1572
|
+
* The minimal storage surface {@link serveStorageObject} needs: a metadata-rich
|
|
1573
|
+
* `download`, plus the body-free `head` a range request resolves against.
|
|
1574
|
+
*
|
|
1575
|
+
* `head` is required rather than optional-with-a-fallback because the fallback
|
|
1576
|
+
* is the bug: without it a ranged request has to start a full-object `download`
|
|
1577
|
+
* just to learn the size, then throw that body away. `@lunora/storage`'s `head`
|
|
1578
|
+
* already degrades internally to a 0-length ranged `get()` on a binding with no
|
|
1579
|
+
* HEAD, so there is nothing a caller here could usefully do that it does not.
|
|
1398
1580
|
*/
|
|
1399
|
-
interface
|
|
1400
|
-
/**
|
|
1401
|
-
|
|
1402
|
-
* `defineSchema(...).extend(plugin.extension)`.
|
|
1403
|
-
*/
|
|
1404
|
-
readonly extension?: SchemaExtension<TExtension>;
|
|
1405
|
-
/** Stable key identifying the plugin. Matches `extension.key` when set. */
|
|
1406
|
-
readonly key: string;
|
|
1407
|
-
/**
|
|
1408
|
-
* Optional middleware. Users attach with `c.query.use(plugin.middleware)`.
|
|
1409
|
-
* The middleware can extend `ctx`; convention is to attach helpers under
|
|
1410
|
-
* `ctx.api.<key>`, e.g.
|
|
1411
|
-
*
|
|
1412
|
-
* ```ts
|
|
1413
|
-
* middleware: ({ ctx, next }) =>
|
|
1414
|
-
* next({ ctx: { api: { ...ctx.api, ratelimit: api } } })
|
|
1415
|
-
* ```
|
|
1416
|
-
*/
|
|
1417
|
-
readonly middleware?: Middleware<TContextIn, TContextOut>;
|
|
1581
|
+
interface StorageHead {
|
|
1582
|
+
/** Object metadata with no body. `size` is the FULL object size (mirrors R2). */
|
|
1583
|
+
head: (key: string) => Promise<Omit<StorageObjectBody, "body"> | null>;
|
|
1418
1584
|
}
|
|
1419
|
-
/**
|
|
1420
|
-
interface
|
|
1421
|
-
|
|
1422
|
-
|
|
1585
|
+
/** The storage surface {@link serveStorageObject} reads through. */
|
|
1586
|
+
interface StorageDownloader extends StorageHead {
|
|
1587
|
+
download: (key: string, options?: {
|
|
1588
|
+
range?: StorageRange;
|
|
1589
|
+
}) => Promise<StorageObjectBody | null>;
|
|
1423
1590
|
}
|
|
1424
|
-
/**
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
* canonical install pattern `defineSchema(...).extend(plugin.extension)`
|
|
1428
|
-
* typechecks without a non-null assertion — the shape every scaffold template
|
|
1429
|
-
* ships. The bare-options signature keeps `extension` optional for plugins
|
|
1430
|
-
* that carry only middleware.
|
|
1431
|
-
*/
|
|
1432
|
-
interface DefinePluginFunction {
|
|
1433
|
-
<TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut> & {
|
|
1434
|
-
extension: SchemaExtension<TExtension>;
|
|
1435
|
-
}): Plugin<TExtension, TContextIn, TContextOut> & {
|
|
1436
|
-
readonly extension: SchemaExtension<TExtension>;
|
|
1437
|
-
};
|
|
1438
|
-
<TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut>): Plugin<TExtension, TContextIn, TContextOut>;
|
|
1591
|
+
/** Any ctx that carries a {@link StorageDownloader} on `.storage` (Query/Mutation/Action ctx all do). */
|
|
1592
|
+
interface ContextWithStorage {
|
|
1593
|
+
storage: StorageDownloader;
|
|
1439
1594
|
}
|
|
1440
1595
|
/**
|
|
1441
|
-
*
|
|
1442
|
-
*
|
|
1596
|
+
* True when `value` is safe to use as an HTTP header field-value: no CR, LF, or
|
|
1597
|
+
* NUL. Guards against response-header injection / `Headers`-construction throws
|
|
1598
|
+
* when reflecting attacker-influenced object metadata (e.g. a stored
|
|
1599
|
+
* `Content-Type`). Exported (see the `export {}` at the file end) so an `httpAction`
|
|
1600
|
+
* handler can guard a request-derived header value before writing it — the fix the
|
|
1601
|
+
* `http_action_response_header_injection` advisor lint points to.
|
|
1443
1602
|
*/
|
|
1444
|
-
declare const
|
|
1603
|
+
declare const isSafeHeaderValue: (value: string) => boolean;
|
|
1445
1604
|
/**
|
|
1446
|
-
*
|
|
1447
|
-
*
|
|
1448
|
-
*
|
|
1449
|
-
*
|
|
1450
|
-
*
|
|
1451
|
-
*
|
|
1452
|
-
* ```ts
|
|
1453
|
-
* // lunora/ratelimit.ts
|
|
1454
|
-
* import { ratelimit } from "@vendor/ratelimit-component";
|
|
1455
|
-
* export const { check, reset } = ratelimit.functions;
|
|
1456
|
-
* // Emits as `ratelimit:check` / `ratelimit:reset` in the generated `api`.
|
|
1457
|
-
* ```
|
|
1605
|
+
* Stream a stored object as an HTTP {@link Response} from an `httpAction`
|
|
1606
|
+
* handler, with correct `Content-Type`, `ETag`, and `Accept-Ranges: bytes`.
|
|
1607
|
+
* Honors a single-range `Range` request → **206 Partial Content** with
|
|
1608
|
+
* `Content-Range` + `Content-Length`; otherwise **200**. A missing object is a
|
|
1609
|
+
* **404**; an out-of-bounds range is a **416** with a `Content-Range` of
|
|
1610
|
+
* `bytes` star-slash-size.
|
|
1458
1611
|
*
|
|
1459
|
-
*
|
|
1460
|
-
*
|
|
1461
|
-
* the
|
|
1612
|
+
* A range request resolves its window against a body-free `head()`, then issues
|
|
1613
|
+
* ONE `download()` with the resolved `{ offset, length }` so R2 streams just
|
|
1614
|
+
* those bytes — the slice is never buffered in the isolate, and no full-object
|
|
1615
|
+
* body transfer is started only to be cancelled. A request that cannot produce a
|
|
1616
|
+
* 206 at all (no `Range`, multi-range, malformed) skips the `head()` entirely and
|
|
1617
|
+
* streams straight from a single `download()`. For very
|
|
1618
|
+
* large objects a signed URL (`ctx.storage.getSignedUrl`) is still cheaper since
|
|
1619
|
+
* the client then ranges against R2/CDN directly with no Worker hop.
|
|
1462
1620
|
*/
|
|
1463
|
-
|
|
1621
|
+
declare const serveStorageObject: (context: ContextWithStorage, key: string, request: Request) => Promise<Response>;
|
|
1464
1622
|
/**
|
|
1465
|
-
*
|
|
1466
|
-
*
|
|
1467
|
-
*
|
|
1468
|
-
*
|
|
1623
|
+
* What the worker does with a resolver's identity when it fails contract
|
|
1624
|
+
* validation (a forged / malformed claim set arriving from an untrusted token).
|
|
1625
|
+
* `"anonymous"` (default, safe) treats the request as anonymous, so the bad
|
|
1626
|
+
* identity never reaches a policy as a valid identity (`ctx.auth.userId`
|
|
1627
|
+
* becomes `undefined`). `"reject"` fails the request closed (a `401`) — use
|
|
1628
|
+
* when a malformed credential should be a hard error, not a silent downgrade.
|
|
1469
1629
|
*/
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1630
|
+
type IdentityRejectMode = "anonymous" | "reject";
|
|
1631
|
+
/** Options for {@link defineIdentity}. */
|
|
1632
|
+
interface DefineIdentityOptions {
|
|
1633
|
+
/**
|
|
1634
|
+
* How to handle a resolver identity that violates the contract at the trust
|
|
1635
|
+
* boundary. Defaults to `"anonymous"` (a forged claim set is downgraded to
|
|
1636
|
+
* anonymous rather than flowing in as an unchecked cast).
|
|
1637
|
+
*/
|
|
1638
|
+
readonly onInvalid?: IdentityRejectMode;
|
|
1476
1639
|
}
|
|
1640
|
+
/** Result of validating a candidate identity against the contract. */
|
|
1641
|
+
type IdentityValidation = {
|
|
1642
|
+
ok: true;
|
|
1643
|
+
} | {
|
|
1644
|
+
error: string;
|
|
1645
|
+
ok: false;
|
|
1646
|
+
};
|
|
1477
1647
|
/**
|
|
1478
|
-
*
|
|
1479
|
-
*
|
|
1480
|
-
*
|
|
1481
|
-
*
|
|
1482
|
-
*
|
|
1483
|
-
* ```ts
|
|
1484
|
-
* export const ratelimit = defineComponent("ratelimit", {
|
|
1485
|
-
* // Bare `buckets` merges in as `ratelimit_buckets`.
|
|
1486
|
-
* extension: defineSchemaExtension("ratelimit", { tables: { buckets } }),
|
|
1487
|
-
* middleware: ({ ctx, next }) => next({ ctx: { ...ctx, ratelimit: api(ctx) } }),
|
|
1488
|
-
* functions: {
|
|
1489
|
-
* check: query.input({ key: v.string() }).query(async ({ ctx, args }) => ...),
|
|
1490
|
-
* reset: mutation.input({ key: v.string() }).mutation(async ({ ctx, args }) => ...),
|
|
1491
|
-
* },
|
|
1492
|
-
* });
|
|
1493
|
-
* ```
|
|
1494
|
-
*
|
|
1495
|
-
* Re-exporting an entry (by property access or destructuring) is enough for
|
|
1496
|
-
* codegen to discover it in the host app's namespace — the discovery resolver
|
|
1497
|
-
* chases the re-export back to the bundled registration call.
|
|
1648
|
+
* A declared identity claim contract. Carries the codegen discovery brand, the
|
|
1649
|
+
* declared claim validators, the reject policy, and a runtime `validate`. The
|
|
1650
|
+
* `TClaims` type parameter is the inferred claim shape (always extending
|
|
1651
|
+
* `{ userId: string }`); it is phantom (no runtime field) and exists so
|
|
1652
|
+
* `@lunora/codegen` and {@link InferIdentity} can recover the type.
|
|
1498
1653
|
*/
|
|
1499
|
-
|
|
1654
|
+
interface IdentityContract<TClaims extends {
|
|
1655
|
+
userId: string;
|
|
1656
|
+
} = {
|
|
1657
|
+
userId: string;
|
|
1658
|
+
}> {
|
|
1659
|
+
/**
|
|
1660
|
+
* Phantom carrier for the inferred claim type. Never populated at runtime
|
|
1661
|
+
* (`undefined`); present only so the type flows to codegen / {@link InferIdentity}.
|
|
1662
|
+
*/
|
|
1663
|
+
readonly __claimType?: TClaims;
|
|
1664
|
+
readonly __lunoraIdentity: true;
|
|
1665
|
+
/** The declared claim validators (a `@lunora/values` validator map). */
|
|
1666
|
+
readonly claims: ValidatorMap;
|
|
1667
|
+
/** Reject policy applied at the trust boundary. See {@link IdentityRejectMode}. */
|
|
1668
|
+
readonly onInvalid: IdentityRejectMode;
|
|
1669
|
+
/**
|
|
1670
|
+
* Validate a resolver's returned identity against the declared claims. On
|
|
1671
|
+
* success the caller keeps the original identity untouched (so undeclared
|
|
1672
|
+
* claims are forwarded verbatim, preserving today's behaviour); on failure
|
|
1673
|
+
* the worker applies the `onInvalid` policy.
|
|
1674
|
+
*/
|
|
1675
|
+
validate: (identity: Record<string, unknown>) => IdentityValidation;
|
|
1676
|
+
}
|
|
1677
|
+
/** Recover the declared claim type from a {@link defineIdentity} contract. */
|
|
1678
|
+
type InferIdentity<T> = T extends IdentityContract<infer TClaims> ? TClaims : never;
|
|
1500
1679
|
/**
|
|
1501
|
-
*
|
|
1502
|
-
*
|
|
1503
|
-
*
|
|
1680
|
+
* Declare the identity claim contract. `claims` is a `@lunora/values` validator
|
|
1681
|
+
* map whose inferred type must extend `{ userId: string }` — if it does not
|
|
1682
|
+
* (e.g. `userId` is missing or not a required string), the argument type
|
|
1683
|
+
* collapses to `never` and the call fails to typecheck.
|
|
1684
|
+
* @example
|
|
1685
|
+
* export const identity = defineIdentity({ userId: v.string(), tenantId: v.optional(v.string()), scopes: v.optional(v.array(v.string())) });
|
|
1504
1686
|
*/
|
|
1505
|
-
|
|
1687
|
+
declare const defineIdentity: <A extends ValidatorMap>(claims: InferValidatorMap<A> extends {
|
|
1688
|
+
userId: string;
|
|
1689
|
+
} ? A : never, options?: DefineIdentityOptions) => IdentityContract<InferValidatorMap<A> & {
|
|
1690
|
+
userId: string;
|
|
1691
|
+
}>;
|
|
1692
|
+
/** Handler for a connection-lifecycle hook. */
|
|
1693
|
+
type LifecycleHandler = (context: MutationCtx, event: LifecycleEvent) => Promise<void> | void;
|
|
1694
|
+
/** Handler for a shard-init hook. */
|
|
1695
|
+
type ShardInitHandler = (context: MutationCtx, event: ShardInitEvent) => Promise<void> | void;
|
|
1696
|
+
/** Register a hook that fires once when a client's WebSocket connects. */
|
|
1697
|
+
declare const onConnect: (handler: LifecycleHandler) => RegisteredLifecycleHook;
|
|
1698
|
+
/** Register a hook that fires once when a client's WebSocket disconnects. */
|
|
1699
|
+
declare const onDisconnect: (handler: LifecycleHandler) => RegisteredLifecycleHook;
|
|
1506
1700
|
/**
|
|
1507
|
-
*
|
|
1508
|
-
*
|
|
1509
|
-
*
|
|
1510
|
-
* Extension tables are auto-namespaced: each bare table name is prefixed with
|
|
1511
|
-
* the extension `key` (`buckets` → `ratelimit_buckets`), Convex-Components
|
|
1512
|
-
* style, and every intra-extension reference (relation targets, aggregate /
|
|
1513
|
-
* rank index `on`, standalone vector index `table`) is rewritten to match.
|
|
1514
|
-
* References to base/app tables are left untouched.
|
|
1515
|
-
*
|
|
1516
|
-
* Because each extension lives in its own `key` namespace, app↔component
|
|
1517
|
-
* collisions are impossible. The only remaining hard error is two extensions
|
|
1518
|
-
* sharing the same `key` and producing the same prefixed table (or vector
|
|
1519
|
-
* index) name — silent shadow would let one plugin hijack another's data.
|
|
1701
|
+
* Register a hook that fires ONCE per Durable Object instance, before any
|
|
1702
|
+
* handler on that instance can run — the re-init half of `.memory()` tables.
|
|
1520
1703
|
*
|
|
1521
|
-
*
|
|
1522
|
-
*
|
|
1523
|
-
*
|
|
1524
|
-
*
|
|
1525
|
-
*
|
|
1526
|
-
*
|
|
1527
|
-
* check when the base schema was built. Both callers of this function —
|
|
1528
|
-
* `withExtend.extend()` (`./schema`) and `installPlugins` (below) — get the
|
|
1529
|
-
* re-validation for free from this single call site (plan 258 §4/§9 Q3).
|
|
1530
|
-
*/
|
|
1531
|
-
declare const mergeSchemaExtension: <T extends Record<string, TableDefinition>, X extends Record<string, TableDefinition>, Key extends string = string>(base: Schema<T>, extension: SchemaExtension<X> & {
|
|
1532
|
-
readonly key: Key;
|
|
1533
|
-
}) => Schema<PrefixedTables<X, Key> & T>;
|
|
1534
|
-
/**
|
|
1535
|
-
* Install several plugins' schema extensions in one call — the one-shot
|
|
1536
|
-
* counterpart to chaining `defineSchema(...).extend(a).extend(b)`. Plugins
|
|
1537
|
-
* without an `extension` (middleware-only) are skipped; tables from those that
|
|
1538
|
-
* do are auto-prefixed and reference-rewritten exactly as
|
|
1539
|
-
* {@link mergeSchemaExtension} does for a single `.extend(...)`.
|
|
1704
|
+
* A shard is not a process that stays up. Cloudflare reconstructs the Durable
|
|
1705
|
+
* Object after every eviction, and a shard whose sockets are hibernating is
|
|
1706
|
+
* evicted routinely, so "cold start" is a steady-state event rather than a rare
|
|
1707
|
+
* one. Everything the shard held in memory is gone at that moment: the JS heap,
|
|
1708
|
+
* and every `.memory()` table, which the framework has already cleared by the
|
|
1709
|
+
* time this hook runs.
|
|
1540
1710
|
*
|
|
1541
1711
|
* ```ts
|
|
1542
|
-
*
|
|
1543
|
-
*
|
|
1712
|
+
* // lunora/init.ts
|
|
1713
|
+
* import { onShardInit } from "@lunora/server";
|
|
1714
|
+
*
|
|
1715
|
+
* export const warm = onShardInit(async (ctx, event) => {
|
|
1716
|
+
* // Rebuild ephemeral state from the durable tables that outlived us.
|
|
1717
|
+
* for await (const member of ctx.db.roomMembers.iterate({ where: { roomId: event.shardKey } })) {
|
|
1718
|
+
* await ctx.db.presence.insert({ userId: member.userId, status: "away" });
|
|
1719
|
+
* }
|
|
1720
|
+
* });
|
|
1544
1721
|
* ```
|
|
1545
1722
|
*
|
|
1546
|
-
*
|
|
1547
|
-
*
|
|
1548
|
-
*
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
/**
|
|
1552
|
-
* Compose every plugin's middleware into a single middleware you attach with one
|
|
1553
|
-
* `.use(...)`. Plugins without middleware (schema-only) are skipped; the rest run
|
|
1554
|
-
* in array order, each seeing the context the previous one widened, so the final
|
|
1555
|
-
* `next({ ctx })` the builder receives carries every plugin's `ctx.api.<key>`
|
|
1556
|
-
* additions. Equivalent to `.use(a.middleware).use(b.middleware)…` but as one
|
|
1557
|
-
* value, the middleware sibling of {@link installPlugins}.
|
|
1723
|
+
* **Ordering is the guarantee.** Memory tables are cleared, then every init hook
|
|
1724
|
+
* runs to completion, and only then does the dispatch that triggered the cold
|
|
1725
|
+
* start proceed. No handler, subscription refresh, alarm, or shape poke can
|
|
1726
|
+
* observe a memory table in the gap. Hooks run sequentially in manifest order,
|
|
1727
|
+
* so one may depend on state an earlier one wrote.
|
|
1558
1728
|
*
|
|
1559
|
-
*
|
|
1560
|
-
*
|
|
1561
|
-
*
|
|
1729
|
+
* **It is a mutation, and it runs on every cold start.** Keep it cheap and keep
|
|
1730
|
+
* it idempotent: it is on the latency path of the request that woke the shard,
|
|
1731
|
+
* and it will run again — many times — over the shard's life. Writing to durable
|
|
1732
|
+
* tables from here is legal and occasionally right, but remember it is a
|
|
1733
|
+
* rebuild, not a migration; use `defineMigration` for anything that should
|
|
1734
|
+
* happen once.
|
|
1735
|
+
*
|
|
1736
|
+
* **No caller identity.** The hook dispatches as a trusted system call with no
|
|
1737
|
+
* request identity — `ctx.auth` is anonymous and RLS does not apply even under
|
|
1738
|
+
* `.rls("required")`, exactly as for a cron tick or a migration. RLS scopes rows
|
|
1739
|
+
* to a user and an init hook has none, so `ctx.db` here sees every row: scope
|
|
1740
|
+
* your reads yourself. (`onConnect`/`onDisconnect` are the opposite case — they
|
|
1741
|
+
* carry the socket's verified identity and stay RLS-guarded.)
|
|
1742
|
+
*
|
|
1743
|
+
* A throw is logged and does NOT fail the dispatch that woke the shard — an init
|
|
1744
|
+
* hook that cannot rebuild presence must not take the whole shard down with it.
|
|
1745
|
+
* The table is then cleared but not refilled, so reads see nothing. A failure
|
|
1746
|
+
* EARLIER, before the framework's clear runs, instead leaves the previous
|
|
1747
|
+
* instance's rows in place: a memory table's rows live in SQLite until they are
|
|
1748
|
+
* deleted, so an eviction on its own does not remove them.
|
|
1562
1749
|
*/
|
|
1563
|
-
declare const
|
|
1564
|
-
/**
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
/** Logical index name; must match a `[[vectorize]]` binding in wrangler. */
|
|
1569
|
-
index: string;
|
|
1570
|
-
/** Fields mirrored into Vectorize metadata for filtering. */
|
|
1571
|
-
metadata?: ReadonlyArray<keyof Shape & string>;
|
|
1572
|
-
metric: VectorMetric;
|
|
1573
|
-
}
|
|
1574
|
-
/** A `one` (many-to-one) relation descriptor; phantom `Target` carries the target table name. */
|
|
1575
|
-
interface OneRelation<Target extends string = string> extends RelationDefinition {
|
|
1576
|
-
readonly __target?: Target;
|
|
1577
|
-
readonly kind: "one";
|
|
1578
|
-
}
|
|
1579
|
-
/** A `many` (one-to-many) relation descriptor; phantom `Target` carries the target table name. */
|
|
1580
|
-
interface ManyRelation<Target extends string = string> extends RelationDefinition {
|
|
1581
|
-
readonly __target?: Target;
|
|
1582
|
-
readonly kind: "many";
|
|
1583
|
-
}
|
|
1584
|
-
/** The `r` argument passed to `.relations((r) => …)`. */
|
|
1585
|
-
interface RelationBuilder {
|
|
1586
|
-
/** One-to-many: the FK `field` lives on the target table, matching this table's `references` (default `_id`). */
|
|
1587
|
-
many: <Target extends string>(table: Target, options: {
|
|
1588
|
-
field: string;
|
|
1589
|
-
references?: string;
|
|
1590
|
-
}) => ManyRelation<Target>;
|
|
1591
|
-
/** Many-to-one: the FK `field` lives on this table, pointing at `table`.`references` (default `_id`). */
|
|
1592
|
-
one: <Target extends string>(table: Target, options: {
|
|
1593
|
-
field: string;
|
|
1594
|
-
onDelete?: OnDeleteAction;
|
|
1595
|
-
references?: string;
|
|
1596
|
-
}) => OneRelation<Target>;
|
|
1597
|
-
}
|
|
1750
|
+
declare const onShardInit: (handler: ShardInitHandler) => RegisteredLifecycleHook;
|
|
1751
|
+
/** Default `limit` when the caller doesn't ask for one. */
|
|
1752
|
+
declare const DEFAULT_LIMIT = 25;
|
|
1753
|
+
/** Default ceiling on `limit`, so one request can't ask for an unbounded page. */
|
|
1754
|
+
declare const DEFAULT_MAX_LIMIT = 100;
|
|
1598
1755
|
/**
|
|
1599
|
-
*
|
|
1600
|
-
* `
|
|
1601
|
-
*
|
|
1756
|
+
* Per-field predicate accepted for a declared filter column. An alias of the
|
|
1757
|
+
* `ctx.db` `where` DSL's own operator type rather than a copy, so the two cannot
|
|
1758
|
+
* drift as operators are added.
|
|
1602
1759
|
*/
|
|
1603
|
-
|
|
1604
|
-
/** Group keys; counter rows are one per distinct tuple. Omitted = single-row aggregate over the whole table. */
|
|
1605
|
-
by?: ReadonlyArray<keyof Shape & string>;
|
|
1606
|
-
/** The column the reducer applies to. Required for `sum`/`min`/`max`/`avg`; ignored for `count`. */
|
|
1607
|
-
field?: keyof Shape & string;
|
|
1608
|
-
/** Reducer (default `count`). */
|
|
1609
|
-
op?: AggregateOp;
|
|
1610
|
-
/** Static predicate baked into the counter — only matching rows are aggregated. */
|
|
1611
|
-
where?: Record<string, unknown>;
|
|
1612
|
-
}
|
|
1760
|
+
type ListFilterOperators<T> = WhereOperators<T>;
|
|
1613
1761
|
/**
|
|
1614
|
-
*
|
|
1615
|
-
*
|
|
1616
|
-
* `
|
|
1617
|
-
*
|
|
1762
|
+
* The filter allow-list a caller may declare: a subset of the document's own
|
|
1763
|
+
* columns, each with a validator for that column's type. Constraining the KEYS to
|
|
1764
|
+
* `keyof Doc` is what turns a typo'd or renamed column into a compile error
|
|
1765
|
+
* instead of a predicate that silently never matches.
|
|
1618
1766
|
*/
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
}>;
|
|
1627
|
-
/** Static predicate baked into the index; only matching rows enter. */
|
|
1628
|
-
where?: Record<string, unknown>;
|
|
1767
|
+
type ListFilterShape<TDocument> = { [K in keyof TDocument & string]?: Validator<TDocument[K]>; };
|
|
1768
|
+
/** The `where` argument: each declared filter column, optionally, as a bare value or an operator object. */
|
|
1769
|
+
type ListWhere<F> = { [K in keyof F]?: Infer<NonNullable<F[K]>> | ListFilterOperators<Infer<NonNullable<F[K]>>>; };
|
|
1770
|
+
/** One `orderBy` entry. `direction` defaults to `"asc"`. */
|
|
1771
|
+
interface ListOrderByEntry<O extends string> {
|
|
1772
|
+
direction?: "asc" | "desc";
|
|
1773
|
+
field: O;
|
|
1629
1774
|
}
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
* clock is read when the handler runs, the write lands when the transaction
|
|
1641
|
-
* commits, and nothing ties those instants together. A changefeed paging on
|
|
1642
|
-
* `_creationTime` can skip a row permanently. Paging on `_commitSeq`
|
|
1643
|
-
* (`where: { _commitSeq: { gt: cursor } }, orderBy: ["_commitSeq"]`) cannot.
|
|
1644
|
-
*
|
|
1645
|
-
* It orders COMMITS, not rows: one mutation's rows share a value. A bounded
|
|
1646
|
-
* page can therefore end mid-group, so a consumer must checkpoint at a
|
|
1647
|
-
* sequence it has seen the whole of, never at the last row of a full page.
|
|
1648
|
-
* An action's writes are the exception to the grouping — they commit
|
|
1649
|
-
* independently, so each gets its own sequence.
|
|
1650
|
-
*
|
|
1651
|
-
* Ordered, not contiguous — read a gap as "nothing to see", never as loss.
|
|
1652
|
-
* Per-shard, not global: two shards allocate independently, so a cursor is
|
|
1653
|
-
* only meaningful against the shard it came from. Rejected on `.global()`
|
|
1654
|
-
* tables, which have no shard-local transaction to allocate inside.
|
|
1655
|
-
*
|
|
1656
|
-
* **A hard delete is invisible to the feed.** The sequence lives on the row,
|
|
1657
|
-
* so a physically removed row takes it along: the row stops appearing, but
|
|
1658
|
-
* no event says it went away. Pair `.commitOrdered()` with `.softDelete()`
|
|
1659
|
-
* when the feed must observe deletes — the tombstone flip is an UPDATE, so
|
|
1660
|
-
* it advances the sequence and pages through like any other change.
|
|
1661
|
-
*/
|
|
1662
|
-
commitOrdered: () => TableBuilder<Shape>;
|
|
1663
|
-
/**
|
|
1664
|
-
* Mark this table as written outside Lunora's discoverable insert path —
|
|
1665
|
-
* by an adapter, a migration, or framework middleware (e.g. `@lunora/auth`'s
|
|
1666
|
-
* better-auth tables, `@lunora/ratelimit`'s store). Advisor insert-path lints
|
|
1667
|
-
* (`table_without_insert`) then skip it instead of flagging the absent
|
|
1668
|
-
* `ctx.db.insert(...)`.
|
|
1669
|
-
*/
|
|
1670
|
-
externallyManaged: () => TableBuilder<Shape>;
|
|
1671
|
-
/**
|
|
1672
|
-
* Declare a geospatial index over a `v.geoPoint()` column. The runtime keeps
|
|
1673
|
-
* a geohash companion so `withGeoIndex(name, q => q.near(point, radius))` and
|
|
1674
|
-
* `.within(bbox)` resolve as a geohash-prefix range scan + Haversine
|
|
1675
|
-
* refine/sort. `options.precision` tunes the geohash length (default 9).
|
|
1676
|
-
*/
|
|
1677
|
-
geoIndex: (name: string, options: {
|
|
1678
|
-
field: keyof Shape & string;
|
|
1679
|
-
precision?: number;
|
|
1680
|
-
}) => TableBuilder<Shape>;
|
|
1681
|
-
/**
|
|
1682
|
-
* Mark this table as global (cross-shard). Backed by **D1** by default;
|
|
1683
|
-
* pass `{ backend: "hyperdrive" }` to store it in a Postgres/MySQL database
|
|
1684
|
-
* via Cloudflare Hyperdrive (PlanetScale, Neon, …) instead. Either way the
|
|
1685
|
-
* table stays reactive — live queries re-run on write.
|
|
1686
|
-
*/
|
|
1687
|
-
global: (options?: {
|
|
1688
|
-
backend?: GlobalBackend;
|
|
1689
|
-
}) => TableBuilder<Shape>;
|
|
1690
|
-
/** Add a secondary index. */
|
|
1691
|
-
index: (name: string, fields: ReadonlyArray<(keyof Shape & string) | (typeof SYSTEM_INDEX_FIELDS)[number]>, options?: {
|
|
1692
|
-
unique?: boolean;
|
|
1693
|
-
}) => TableBuilder<Shape>;
|
|
1775
|
+
/** The decoded arguments a {@link defineListArgs} endpoint receives. */
|
|
1776
|
+
interface ListArgsValue<F, O extends string> {
|
|
1777
|
+
cursor?: null | number | string;
|
|
1778
|
+
limit?: number;
|
|
1779
|
+
orderBy?: ListOrderByEntry<O>[];
|
|
1780
|
+
where?: ListWhere<F>;
|
|
1781
|
+
}
|
|
1782
|
+
interface DefineListArgsConfig<F, O extends string> {
|
|
1783
|
+
/** `limit` applied when the caller omits one. Defaults to 25. */
|
|
1784
|
+
readonly defaultLimit?: number;
|
|
1694
1785
|
/**
|
|
1695
|
-
*
|
|
1696
|
-
*
|
|
1697
|
-
*
|
|
1698
|
-
* A memory table is a full `ctx.db` table: indexes, `where`, `orderBy`,
|
|
1699
|
-
* pagination, relations, live queries. What it is not is durable. Its rows
|
|
1700
|
-
* are wiped the moment the Durable Object is reconstructed — which happens
|
|
1701
|
-
* on every eviction, and a WebSocket-hibernating shard is evicted often — so
|
|
1702
|
-
* a memory table holds only what can be derived again: presence and cursors,
|
|
1703
|
-
* a live participant list, a rate-limit window, an actor's scratch state.
|
|
1704
|
-
*
|
|
1705
|
-
* Pair it with `onShardInit` to rebuild whatever the app needs present.
|
|
1706
|
-
* The framework guarantees the ordering: every memory table is cleared, and
|
|
1707
|
-
* every init hook has run, before any handler can read one. Without a hook a
|
|
1708
|
-
* memory table simply comes back empty, which is a correct state for
|
|
1709
|
-
* presence and a wrong one for a cache someone is treating as authoritative.
|
|
1710
|
-
*
|
|
1711
|
-
* **On Cloudflare the rows still transit the DO's SQLite.** workerd exposes
|
|
1712
|
-
* exactly one SQL handle and no memory-backed database, so `.memory()` buys
|
|
1713
|
-
* the LIFETIME (and skips the CDC changelog, so an append-heavy presence
|
|
1714
|
-
* table does not grow the op-log), not the write. Treat it as "state I am
|
|
1715
|
-
* happy to lose", not as "state that is free to write" — see
|
|
1716
|
-
* `PlatformCapabilities.memoryTables`, rated `emulated` for exactly this
|
|
1717
|
-
* reason.
|
|
1718
|
-
*
|
|
1719
|
-
* Rejected alongside `.global()` (a D1 table is not this shard's to clear),
|
|
1720
|
-
* `.commitOrdered()` (a sequence that resets is not a sequence), and
|
|
1721
|
-
* `.source()` (an externally-materialized table is not ours to wipe).
|
|
1786
|
+
* Allow-list of filterable columns. Publish only columns an index can serve;
|
|
1787
|
+
* anything absent here is unreachable from the client.
|
|
1722
1788
|
*/
|
|
1723
|
-
|
|
1789
|
+
readonly filter: F;
|
|
1790
|
+
/** Ceiling on `in` / `notIn` array length — one bound parameter each. Defaults to 100. */
|
|
1791
|
+
readonly maxInValues?: number;
|
|
1792
|
+
/** Ceiling on `limit`; a larger request is clamped down, not rejected. Defaults to 100. */
|
|
1793
|
+
readonly maxLimit?: number;
|
|
1794
|
+
/** Ceiling on how many `orderBy` entries a request may ask for. Defaults to 8. */
|
|
1795
|
+
readonly maxOrderBy?: number;
|
|
1796
|
+
/** Allow-list of sortable columns. Pass `[]` to fix the order server-side. */
|
|
1797
|
+
readonly orderBy: ReadonlyArray<O>;
|
|
1798
|
+
}
|
|
1799
|
+
/** The validator map handed to `.input()`. Typed precisely so `args` infers end-to-end. */
|
|
1800
|
+
interface ListArgsValidators<F, O extends string> {
|
|
1801
|
+
cursor: ColumnValidator<null | number | string | undefined, null | number | string | undefined>;
|
|
1802
|
+
limit: ColumnValidator<number | undefined, number | undefined>;
|
|
1803
|
+
orderBy: ColumnValidator<ListOrderByEntry<O>[] | undefined, ListOrderByEntry<O>[] | undefined>;
|
|
1804
|
+
where: ColumnValidator<ListWhere<F> | undefined, ListWhere<F> | undefined>;
|
|
1805
|
+
}
|
|
1806
|
+
interface ListArgsSpec<TDocument, F, O extends string> {
|
|
1807
|
+
/** Spread into `.input(...)` — `{ cursor, limit, orderBy, where }`. */
|
|
1808
|
+
readonly args: ListArgsValidators<F, O>;
|
|
1724
1809
|
/**
|
|
1725
|
-
*
|
|
1726
|
-
*
|
|
1727
|
-
*
|
|
1728
|
-
* A `defineShape({ table, owner: true })` over this table derives its predicate
|
|
1729
|
-
* from the field: the subscriber's verified `ctx.auth.userId` must match, and an
|
|
1730
|
-
* anonymous subscriber is denied. Pairs naturally with `.shardBy(field)` on the
|
|
1731
|
-
* same column — the shard key routes the storage, `ownedBy` states who the rows
|
|
1732
|
-
* belong to — but the two are independent and either can be used alone.
|
|
1810
|
+
* Translate the decoded arguments into the `findMany` options object:
|
|
1811
|
+
* `limit` clamped into `[1, maxLimit]`, `orderBy` reshaped from
|
|
1812
|
+
* `{ field, direction }[]` into `ctx.db`'s `{ column: direction }[]`.
|
|
1733
1813
|
*
|
|
1734
|
-
*
|
|
1735
|
-
*
|
|
1814
|
+
* Returns `QueryArgs<Doc>` — bound to the table, not free — so a mismatch
|
|
1815
|
+
* between what this helper declares and what the table actually holds is a
|
|
1816
|
+
* compile error at the `findMany` call site.
|
|
1736
1817
|
*/
|
|
1737
|
-
|
|
1818
|
+
readonly toQueryArgs: (args: ListArgsValue<F, O>) => QueryArgs$2<TDocument>;
|
|
1819
|
+
}
|
|
1820
|
+
/** Clamp a caller-supplied `limit` into `[1, maxLimit]`; a non-finite value falls back to `fallback`. */
|
|
1821
|
+
declare const clampLimit: (limit: number | undefined, fallback: number, maxLimit: number) => number;
|
|
1822
|
+
/**
|
|
1823
|
+
* Declare the filter / sort / page arguments for a list endpoint, plus the
|
|
1824
|
+
* translation into `ctx.db.<table>.findMany(...)` options. See the module docs
|
|
1825
|
+
* for the shape and the reasoning behind it.
|
|
1826
|
+
*
|
|
1827
|
+
* Curried on the document type: `defineListArgs<Doc<"messages">>()({ … })`. The
|
|
1828
|
+
* extra `()` buys the thing that matters — with `Doc` bound, `filter` keys and
|
|
1829
|
+
* `orderBy` entries are checked against the table's real columns, so a typo or a
|
|
1830
|
+
* column renamed out from under the endpoint is a COMPILE error instead of a
|
|
1831
|
+
* predicate that silently matches nothing. TypeScript has no partial type-argument
|
|
1832
|
+
* inference, so binding `Doc` explicitly while still inferring `F` and `O` from
|
|
1833
|
+
* the config requires the second call.
|
|
1834
|
+
*/
|
|
1835
|
+
declare const defineListArgs: <TDocument>() => <F extends ListFilterShape<TDocument>, O extends keyof TDocument & string>(config: DefineListArgsConfig<F, O>) => ListArgsSpec<TDocument, F, O>;
|
|
1836
|
+
/**
|
|
1837
|
+
* Structural mirrors of `@lunora/shard-engine`'s rank-page-row shapes
|
|
1838
|
+
* (`RankPageRowKey` / `RankPageRow` / `ShardRankPageResult`) — the return type
|
|
1839
|
+
* of the writer's `rankPageRows` seam, the cross-shard companion to
|
|
1840
|
+
* `rankPage`.
|
|
1841
|
+
*
|
|
1842
|
+
* Shared by `../rls/middleware` and `../mask/middleware`: both wrap
|
|
1843
|
+
* `rankPageRows` structurally (no `@lunora/shard-engine` import, mirroring how
|
|
1844
|
+
* every other method on their `DatabaseWriterLike`/`MaskDatabase` projections
|
|
1845
|
+
* is hand-mirrored rather than imported) and both need the exact same result
|
|
1846
|
+
* shape to type their overrides. A single copy here means the two wrappers
|
|
1847
|
+
* can't drift out of lockstep with each other — see AGENTS.md's platform
|
|
1848
|
+
* parity note on `ShardSqlExec` and the canonical binding `*Like` projections
|
|
1849
|
+
* shipping wrong for exactly this reason (two hand-maintained mirrors of one
|
|
1850
|
+
* upstream type).
|
|
1851
|
+
*/
|
|
1852
|
+
/** Structural mirror of `@lunora/shard-engine`'s `RankPageRowKey`. */
|
|
1853
|
+
interface RankPageRowKeyLike {
|
|
1854
|
+
partitionKey: string;
|
|
1855
|
+
rowId: string;
|
|
1856
|
+
sortValues: ReadonlyArray<unknown>;
|
|
1857
|
+
}
|
|
1858
|
+
/** Structural mirror of `@lunora/shard-engine`'s `RankPageRow`. */
|
|
1859
|
+
interface RankPageRowLike {
|
|
1860
|
+
doc: Record<string, unknown>;
|
|
1861
|
+
key: RankPageRowKeyLike;
|
|
1862
|
+
}
|
|
1863
|
+
/** Structural mirror of `@lunora/shard-engine`'s `ShardRankPageResult` — the `rankPageRows` return shape. */
|
|
1864
|
+
interface ShardRankPageResultLike {
|
|
1865
|
+
directions: ReadonlyArray<"asc" | "desc">;
|
|
1866
|
+
hasMore: boolean;
|
|
1867
|
+
rows: ReadonlyArray<RankPageRowLike>;
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Structural mirror of `@lunora/do`'s `QueryArgs` and `CountArgs`. The
|
|
1871
|
+
* runtime ORM in `@lunora/do`/`@lunora/d1` reads `baseWhere` /
|
|
1872
|
+
* `restrictsCounts` straight off these option objects, so as long as the
|
|
1873
|
+
* fields here stay name-compatible the wrapper is portable across the two
|
|
1874
|
+
* dialects without an inter-package dependency.
|
|
1875
|
+
*/
|
|
1876
|
+
interface QueryArgs$1 {
|
|
1877
|
+
baseWhere?: WhereInput;
|
|
1878
|
+
cursor?: null | string;
|
|
1879
|
+
limit?: number;
|
|
1880
|
+
orderBy?: ReadonlyArray<unknown>;
|
|
1738
1881
|
/**
|
|
1739
|
-
*
|
|
1740
|
-
*
|
|
1741
|
-
*
|
|
1742
|
-
* plain `query`/`mutation` may read/write it without an RLS policy. No effect
|
|
1743
|
-
* when the schema does not require RLS.
|
|
1882
|
+
* Per-target-table read filter the RLS wrapper attaches so a `with` relation
|
|
1883
|
+
* is policy-filtered on its own hop (see `@lunora/do`'s `QueryArgs`). Mirrors
|
|
1884
|
+
* the top-level read: `(table) => readBase(table).baseWhere`.
|
|
1744
1885
|
*/
|
|
1745
|
-
|
|
1886
|
+
relationBaseWhere?: (table: string) => undefined | WhereInput;
|
|
1887
|
+
restrictsCounts?: boolean;
|
|
1888
|
+
where?: WhereInput;
|
|
1889
|
+
with?: Record<string, unknown>;
|
|
1890
|
+
}
|
|
1891
|
+
interface CountArgs {
|
|
1892
|
+
baseWhere?: WhereInput;
|
|
1893
|
+
relationBaseWhere?: (table: string) => undefined | WhereInput;
|
|
1894
|
+
restrictsCounts?: boolean;
|
|
1895
|
+
where?: WhereInput;
|
|
1896
|
+
}
|
|
1897
|
+
/** Structural mirror of `@lunora/do`'s `AggregateOptions` — only the fields the wrapper touches. */
|
|
1898
|
+
interface AggregateArgs$1 {
|
|
1899
|
+
baseWhere?: WhereInput;
|
|
1900
|
+
field?: string;
|
|
1901
|
+
op: string;
|
|
1902
|
+
relationBaseWhere?: (table: string) => undefined | WhereInput;
|
|
1903
|
+
restrictsCounts?: boolean;
|
|
1904
|
+
where?: WhereInput;
|
|
1905
|
+
}
|
|
1906
|
+
/** Structural mirror of `@lunora/do`'s `GroupByOptions`. */
|
|
1907
|
+
interface GroupByArgs$1 {
|
|
1908
|
+
agg?: {
|
|
1909
|
+
field?: string;
|
|
1910
|
+
op: string;
|
|
1911
|
+
};
|
|
1912
|
+
baseWhere?: WhereInput;
|
|
1913
|
+
by: ReadonlyArray<string>;
|
|
1914
|
+
relationBaseWhere?: (table: string) => undefined | WhereInput;
|
|
1915
|
+
restrictsCounts?: boolean;
|
|
1916
|
+
where?: WhereInput;
|
|
1917
|
+
}
|
|
1918
|
+
/** Structural mirror of `@lunora/do`'s `RankOptions`. */
|
|
1919
|
+
interface RankArgs {
|
|
1920
|
+
baseWhere?: WhereInput;
|
|
1921
|
+
restrictsCounts?: boolean;
|
|
1922
|
+
row: Record<string, unknown> | string;
|
|
1923
|
+
where?: WhereInput;
|
|
1924
|
+
}
|
|
1925
|
+
/** Structural mirror of `@lunora/do`'s `RankBeforeOptions`. */
|
|
1926
|
+
interface RankBeforeArgs {
|
|
1927
|
+
partitionKey: string;
|
|
1928
|
+
restrictsCounts?: boolean;
|
|
1929
|
+
rowId: string;
|
|
1930
|
+
sortValues: ReadonlyArray<unknown>;
|
|
1931
|
+
}
|
|
1932
|
+
/** Structural mirror of `@lunora/do`'s `RankPageOptions`. */
|
|
1933
|
+
interface RankPageArgs {
|
|
1934
|
+
baseWhere?: WhereInput;
|
|
1935
|
+
cursor?: null | string;
|
|
1936
|
+
restrictsCounts?: boolean;
|
|
1937
|
+
take?: number;
|
|
1938
|
+
where?: WhereInput;
|
|
1939
|
+
}
|
|
1940
|
+
interface QueryPage$1 {
|
|
1941
|
+
continueCursor: null | string;
|
|
1942
|
+
isDone: boolean;
|
|
1943
|
+
page: Record<string, unknown>[];
|
|
1944
|
+
}
|
|
1945
|
+
interface TableReaderLike$1 {
|
|
1946
|
+
collect: () => Promise<Record<string, unknown>[]>;
|
|
1947
|
+
filter: (predicate: (document: Record<string, unknown>) => boolean) => TableReaderLike$1;
|
|
1948
|
+
first: () => Promise<Record<string, unknown> | null>;
|
|
1949
|
+
paginate: (options: {
|
|
1950
|
+
cursor?: null | string;
|
|
1951
|
+
numItems: number;
|
|
1952
|
+
}) => Promise<QueryPage$1>;
|
|
1953
|
+
take: (limit: number) => Promise<Record<string, unknown>[]>;
|
|
1954
|
+
withGeoIndex: (indexName: string, build: (q: unknown) => unknown) => TableReaderLike$1;
|
|
1955
|
+
withIndex: (indexName: string, range?: (q: unknown) => unknown) => TableReaderLike$1;
|
|
1956
|
+
withSearchIndex: (indexName: string, search: (q: unknown) => unknown) => TableReaderLike$1;
|
|
1957
|
+
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Structural projection of the runtime ORM writer. The wrapper relies only
|
|
1960
|
+
* on these fields, so it's interchangeable between `@lunora/do`'s
|
|
1961
|
+
* `DatabaseWriterLike` and `@lunora/d1`'s `DatabaseWriterLike`.
|
|
1962
|
+
*/
|
|
1963
|
+
interface DatabaseWriterLike {
|
|
1746
1964
|
/**
|
|
1747
|
-
*
|
|
1748
|
-
* `
|
|
1965
|
+
* Reduce matching rows to a scalar. The RLS wrapper AND-merges the read
|
|
1966
|
+
* `baseWhere` into `options` so the reduction only sees policy-visible rows
|
|
1967
|
+
* (safe: an aggregate scoped to `where` never reveals a hidden row — see
|
|
1968
|
+
* `@lunora/do`'s `RestrictableQueryOptions`). Required: the only writer ever
|
|
1969
|
+
* wrapped is `@lunora/do`'s `createShardCtxDb`, which always implements it.
|
|
1749
1970
|
*/
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1971
|
+
aggregate: (tableName: string, options: AggregateArgs$1) => Promise<null | number>;
|
|
1972
|
+
count: (tableName: string, whereOrArgs?: CountArgs | WhereInput) => Promise<number>;
|
|
1973
|
+
delete: (id: string, expectedTable?: string, options?: {
|
|
1974
|
+
hard?: boolean;
|
|
1975
|
+
}) => Promise<void>;
|
|
1976
|
+
/** Uncapped, chunked erase of a whole table. The RLS wrapper gates each row like a single delete. */
|
|
1977
|
+
deleteAll?: (tableName: string, options?: {
|
|
1978
|
+
chunkSize?: number;
|
|
1979
|
+
hard?: boolean;
|
|
1980
|
+
}) => Promise<{
|
|
1981
|
+
deleted: number;
|
|
1982
|
+
}>;
|
|
1983
|
+
deleteMany: (ids: ReadonlyArray<string>, options?: {
|
|
1984
|
+
limit?: number;
|
|
1985
|
+
}, expectedTable?: string) => Promise<{
|
|
1986
|
+
deleted: number;
|
|
1987
|
+
}>;
|
|
1988
|
+
deleteWhere?: (tableName: string, where: WhereInput, options?: {
|
|
1989
|
+
limit?: number;
|
|
1990
|
+
}) => Promise<{
|
|
1991
|
+
deleted: number;
|
|
1992
|
+
}>;
|
|
1993
|
+
findFirst: (tableName: string, args?: QueryArgs$1) => Promise<Record<string, unknown> | null>;
|
|
1994
|
+
findFirstOrThrow: (tableName: string, args?: QueryArgs$1) => Promise<Record<string, unknown>>;
|
|
1995
|
+
findMany: (tableName: string, args?: QueryArgs$1) => Promise<QueryPage$1>;
|
|
1996
|
+
get: (id: string, expectedTable?: string) => Promise<Record<string, unknown> | null>;
|
|
1753
1997
|
/**
|
|
1754
|
-
*
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
1757
|
-
* `filterFields` (at most 16) lists the columns `.eq()` may narrow by inside
|
|
1758
|
-
* the search. `language` selects the text analysis (accent folding always,
|
|
1759
|
-
* plus that language's stopwords). `staged: true` skips the migration-time
|
|
1760
|
-
* backfill on a large existing table — pre-existing rows stay unsearchable
|
|
1761
|
-
* until `__lunora_admin__:backfillSearch` is run against the deployment. `strategy: "native"` uses the engine's
|
|
1762
|
-
* own full-text index where it has one (Postgres) — faster on large corpora,
|
|
1763
|
-
* at the cost of the engine ranking rather than the shared scorer.
|
|
1998
|
+
* Group + reduce. Same `baseWhere` injection as `aggregate`: the per-group
|
|
1999
|
+
* reduction is scoped to policy-visible rows, so a group count tallies only
|
|
2000
|
+
* rows the caller may read. Required for the same reason as `aggregate`.
|
|
1764
2001
|
*/
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
2002
|
+
groupBy: (tableName: string, options: GroupByArgs$1) => Promise<ReadonlyArray<{
|
|
2003
|
+
key: Record<string, unknown>;
|
|
2004
|
+
value: null | number;
|
|
2005
|
+
}>>;
|
|
2006
|
+
insert: (tableName: string, document: Record<string, unknown>) => Promise<string>;
|
|
2007
|
+
insertMany: (tableName: string, documents: ReadonlyArray<Record<string, unknown>>, options?: {
|
|
2008
|
+
limit?: number;
|
|
2009
|
+
skipDuplicates?: boolean;
|
|
2010
|
+
}) => Promise<(string | null)[]>;
|
|
2011
|
+
insertManyUnsafe: (tableName: string, documents: ReadonlyArray<Record<string, unknown>>, options?: {
|
|
2012
|
+
allowExplicitId?: boolean;
|
|
2013
|
+
limit?: number;
|
|
2014
|
+
}) => Promise<string[]>;
|
|
1774
2015
|
/**
|
|
1775
|
-
*
|
|
1776
|
-
*
|
|
1777
|
-
*
|
|
1778
|
-
*
|
|
1779
|
-
* `
|
|
1780
|
-
*
|
|
1781
|
-
* `restore()` still address the row directly. `hardDelete()` physically
|
|
1782
|
-
* removes it (cascading as a real delete). Note: `includeDeleted` is a read
|
|
1783
|
-
* scope, not access control — anyone who can run the read can set it; a unique
|
|
1784
|
-
* index still rejects a new row that collides with a soft-deleted one (the row
|
|
1785
|
-
* physically persists).
|
|
2016
|
+
* Optional table-aware lookup. The underlying writer (e.g. `@lunora/do`)
|
|
2017
|
+
* already knows the owning table of an id internally, so it can return
|
|
2018
|
+
* `{ row, tableName }` in a single round-trip. When present, the RLS wrapper
|
|
2019
|
+
* uses it to collapse the per-call membership-probe fan-out (1 `get` + N
|
|
2020
|
+
* `findFirst` across every policy table) down to one lookup. Writers that
|
|
2021
|
+
* don't implement it fall back to the probe path.
|
|
1786
2022
|
*/
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
2023
|
+
lookupById?: (id: string, expectedTable?: string) => Promise<null | {
|
|
2024
|
+
row: Record<string, unknown>;
|
|
2025
|
+
tableName: string;
|
|
2026
|
+
}>;
|
|
2027
|
+
patch: (id: string, patch: Record<string, unknown>, expectedTable?: string) => Promise<void>;
|
|
2028
|
+
patchMany: (patches: ReadonlyArray<{
|
|
2029
|
+
id: string;
|
|
2030
|
+
patch: Record<string, unknown>;
|
|
2031
|
+
}>, options?: {
|
|
2032
|
+
limit?: number;
|
|
2033
|
+
}, expectedTable?: string) => Promise<{
|
|
2034
|
+
patched: number;
|
|
2035
|
+
}>;
|
|
2036
|
+
patchWhere?: (tableName: string, args: {
|
|
2037
|
+
patch: Record<string, unknown>;
|
|
2038
|
+
where: WhereInput;
|
|
2039
|
+
}, options?: {
|
|
2040
|
+
limit?: number;
|
|
2041
|
+
}) => Promise<{
|
|
2042
|
+
patched: number;
|
|
2043
|
+
}>;
|
|
2044
|
+
query: (tableName: string) => TableReaderLike$1;
|
|
1790
2045
|
/**
|
|
1791
|
-
*
|
|
1792
|
-
*
|
|
1793
|
-
*
|
|
1794
|
-
*
|
|
1795
|
-
* `.externallyManaged()` (rows come from the ingest loop, not user mutations).
|
|
1796
|
-
*
|
|
1797
|
-
* Orthogonal to `.shardBy()` — combine them for per-tenant DOs. **Under
|
|
1798
|
-
* `.shardBy()` `tenantBy` is mandatory** (the tenant-isolation boundary); the
|
|
1799
|
-
* `external_source_unscoped` advisor lint fails the build when it is absent, and
|
|
1800
|
-
* `external_source_on_global` rejects combining `.source()` with `.global()`.
|
|
2046
|
+
* Rank a row within its partition. A position is a count-of-rows-before, so
|
|
2047
|
+
* — exactly like `count()` — it can't be trusted in an RLS-restricted
|
|
2048
|
+
* reader: the wrapper fails it closed with `COUNT_RLS_UNSUPPORTED`. Required
|
|
2049
|
+
* for the same reason as `aggregate`.
|
|
1801
2050
|
*/
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
2051
|
+
rank: (tableName: string, indexName: string, options: RankArgs) => Promise<null | {
|
|
2052
|
+
position: number;
|
|
2053
|
+
total: number;
|
|
2054
|
+
}>;
|
|
2055
|
+
/** Cross-shard rank primitive — same count-of-before RLS hazard as `rank`; failed closed under a read policy. */
|
|
2056
|
+
rankBefore?: (tableName: string, indexName: string, options: RankBeforeArgs) => Promise<{
|
|
2057
|
+
before: number;
|
|
2058
|
+
total: number;
|
|
2059
|
+
}>;
|
|
1805
2060
|
/**
|
|
1806
|
-
*
|
|
1807
|
-
*
|
|
1808
|
-
*
|
|
1809
|
-
*
|
|
1810
|
-
*
|
|
1811
|
-
* table-level — for per-row schedules use `@lunora/scheduler`.
|
|
2061
|
+
* Sorted pagination over a rank companion. The companion stores only the
|
|
2062
|
+
* partition + sort keys + id, so an arbitrary read `baseWhere` can't be
|
|
2063
|
+
* enforced against it (and re-filtering the fetched rows would break page
|
|
2064
|
+
* sizing). RLS therefore fails it closed rather than leak hidden rows.
|
|
2065
|
+
* Required for the same reason as `aggregate`.
|
|
1812
2066
|
*/
|
|
1813
|
-
|
|
1814
|
-
after?: number;
|
|
1815
|
-
}) => TableBuilder<Shape>;
|
|
1816
|
-
/** Declare a vector index over a single text field on this table. */
|
|
1817
|
-
vectorize: (field: keyof Shape & string, options: VectorizeOptions<Shape>) => TableBuilder<Shape>;
|
|
1818
|
-
}
|
|
1819
|
-
/** Options for `defineVectorIndex(...)` (DSL Shape B). */
|
|
1820
|
-
interface VectorIndexOptions {
|
|
1821
|
-
dimensions: number;
|
|
1822
|
-
embed: VectorEmbedder;
|
|
1823
|
-
/** Optional projection of the source row into Vectorize metadata. */
|
|
1824
|
-
metadata?: (row: Record<string, unknown>) => Record<string, unknown>;
|
|
1825
|
-
metric: VectorMetric;
|
|
1826
|
-
/** The vector source: which table, and how to derive the embedded text. */
|
|
1827
|
-
source: {
|
|
1828
|
-
select: (row: Record<string, unknown>) => string;
|
|
1829
|
-
table: string;
|
|
1830
|
-
};
|
|
1831
|
-
}
|
|
1832
|
-
/**
|
|
1833
|
-
* Build a table definition. Returned object is both the table definition (for
|
|
1834
|
-
* `defineSchema`) and a fluent builder for indexes + sharding metadata.
|
|
1835
|
-
*/
|
|
1836
|
-
declare const defineTable: <Shape extends Record<string, Validator>>(inputShape: Shape) => TableBuilder<Shape>;
|
|
1837
|
-
/**
|
|
1838
|
-
* Declare a standalone vector index (DSL Shape B). Pass the returned value in
|
|
1839
|
-
* the `vectorIndexes` map of {@link defineSchema} when the source is derived
|
|
1840
|
-
* from multiple fields or a computation rather than a single column.
|
|
1841
|
-
*/
|
|
1842
|
-
declare const defineVectorIndex: (options: VectorIndexOptions) => VectorIndexDefinition;
|
|
1843
|
-
/**
|
|
1844
|
-
* Options for the standalone `defineAggregateIndex(name, opts)` helper (DSL
|
|
1845
|
-
* Shape B). Unlike the inline `.aggregateIndex(...)` builder, this form takes
|
|
1846
|
-
* the owning table explicitly via `on` — handy when a single counter wants to
|
|
1847
|
-
* live next to the schema map rather than inside a table chain.
|
|
1848
|
-
*/
|
|
1849
|
-
interface AggregateIndexOptions {
|
|
1850
|
-
by?: ReadonlyArray<string>;
|
|
1851
|
-
field?: string;
|
|
1852
|
-
on: string;
|
|
1853
|
-
op?: AggregateOp;
|
|
1854
|
-
where?: Record<string, unknown>;
|
|
1855
|
-
}
|
|
1856
|
-
/**
|
|
1857
|
-
* Declare a standalone aggregate index. Pass the returned value to
|
|
1858
|
-
* `defineSchema(tables, vectorIndexes, aggregateIndexes)` keyed by index name —
|
|
1859
|
-
* the schema attaches it to `tables[on].aggregateIndexes` so runtime consumers
|
|
1860
|
-
* (DO + D1) read every index uniformly off the table definition.
|
|
1861
|
-
*/
|
|
1862
|
-
declare const defineAggregateIndex: (name: string, options: AggregateIndexOptions) => AggregateIndexDefinition;
|
|
1863
|
-
/**
|
|
1864
|
-
* Options for the standalone `defineRankIndex(name, opts)` helper (DSL Shape B).
|
|
1865
|
-
* Mirrors the inline `.rankIndex(...)` builder but takes the owning table via
|
|
1866
|
-
* `table` so it can sit next to the schema map.
|
|
1867
|
-
*/
|
|
1868
|
-
interface RankIndexOptions {
|
|
1869
|
-
partitionBy?: ReadonlyArray<string>;
|
|
1870
|
-
sortBy: ReadonlyArray<{
|
|
1871
|
-
direction?: "asc" | "desc";
|
|
1872
|
-
field: string;
|
|
1873
|
-
}>;
|
|
1874
|
-
table: string;
|
|
1875
|
-
where?: Record<string, unknown>;
|
|
1876
|
-
}
|
|
1877
|
-
/**
|
|
1878
|
-
* Declare a standalone rank index. Pass the returned value to
|
|
1879
|
-
* `defineSchema(tables, vectorIndexes, aggregateIndexes, rankIndexes)` keyed
|
|
1880
|
-
* by index name — the schema attaches it to `tables[on].rankIndexes`.
|
|
1881
|
-
*/
|
|
1882
|
-
declare const defineRankIndex: (name: string, options: RankIndexOptions) => RankIndexDefinition;
|
|
1883
|
-
/**
|
|
1884
|
-
* Build the application schema. The first argument is the table map; the
|
|
1885
|
-
* optional second argument registers standalone `defineVectorIndex(...)`
|
|
1886
|
-
* declarations (DSL Shape B) keyed by index name. The optional third argument
|
|
1887
|
-
* registers standalone `defineAggregateIndex(...)` declarations (DSL Shape B);
|
|
1888
|
-
* the optional fourth argument registers standalone `defineRankIndex(...)`
|
|
1889
|
-
* declarations. Both are folded into the matching `tables[on].*Indexes` array
|
|
1890
|
-
* so runtime backends read every index uniformly off the table definition.
|
|
1891
|
-
*/
|
|
1892
|
-
/**
|
|
1893
|
-
* Schema with an in-place `.extend(plugin.extension)` method. Used so apps
|
|
1894
|
-
* can compose plugin schemas: `defineSchema({...}).extend(authPlugin.extension)`.
|
|
1895
|
-
*
|
|
1896
|
-
* `extend` is non-mutating — returns a fresh `ExtendableSchema` containing
|
|
1897
|
-
* the merged tables. Extension tables are auto-namespaced by the extension
|
|
1898
|
-
* `key` (`buckets` → `ratelimit_buckets`), so the merged type carries the
|
|
1899
|
-
* prefixed names via {@link PrefixedTables}. Chains:
|
|
1900
|
-
* `defineSchema(...).extend(a).extend(b)` is the typed equivalent of merging
|
|
1901
|
-
* `a`'s prefixed tables then `b`'s.
|
|
1902
|
-
*/
|
|
1903
|
-
type ExtendableSchema<T extends Record<string, TableDefinition>> = {
|
|
1904
|
-
extend: <X extends Record<string, TableDefinition>, Key extends string>(extension: SchemaExtension<X> & {
|
|
1905
|
-
readonly key: Key;
|
|
1906
|
-
}) => ExtendableSchema<PrefixedTables<X, Key> & T>;
|
|
2067
|
+
rankPage: (tableName: string, indexName: string, options?: RankPageArgs) => Promise<QueryPage$1>;
|
|
1907
2068
|
/**
|
|
1908
|
-
*
|
|
1909
|
-
*
|
|
1910
|
-
*
|
|
1911
|
-
*
|
|
1912
|
-
* (and `ctx.scheduler` / `ctx.containers`). Non-mutating: returns a fresh
|
|
1913
|
-
* `ExtendableSchema`, so it composes with `.rls(...)` / `.extend(...)` in any order.
|
|
1914
|
-
*
|
|
1915
|
-
* ⚠️ **Set this once, before your first deploy — changing or removing it
|
|
1916
|
-
* strands data.** A Durable Object name maps to a *different* ID in each
|
|
1917
|
-
* jurisdiction, so toggling this on an existing app makes every shard, scheduler
|
|
1918
|
-
* job, and session DO resolve to a NEW, empty DO; the previous data stays in the
|
|
1919
|
-
* old jurisdiction's DOs and is no longer reachable. There is no in-place
|
|
1920
|
-
* migration — you would have to export from the old jurisdiction and import
|
|
1921
|
-
* into the new one.
|
|
1922
|
-
*
|
|
1923
|
-
* Note: this pins **DO-backed** state only. D1-backed state — `.global()`
|
|
1924
|
-
* tables and `@lunora/auth` sessions alike — is governed by D1's own location
|
|
1925
|
-
* settings, not this option.
|
|
1926
|
-
* @see https://developers.cloudflare.com/durable-objects/reference/data-location/
|
|
2069
|
+
* Cross-shard companion to `rankPage`: same ranked slice, but each row
|
|
2070
|
+
* keeps its rank-key tuple for the query coordinator's k-way merge. Same
|
|
2071
|
+
* count-of-partition RLS hazard as `rankPage` — failed closed under a read
|
|
2072
|
+
* policy for the identical reason (see `rankPage` above).
|
|
1927
2073
|
*/
|
|
1928
|
-
|
|
2074
|
+
rankPageRows?: (tableName: string, indexName: string, options?: RankPageArgs) => Promise<ShardRankPageResultLike>;
|
|
2075
|
+
replace: (id: string, document: Record<string, unknown>, expectedTable?: string) => Promise<void>;
|
|
2076
|
+
restore?: (id: string, expectedTable?: string) => Promise<void>;
|
|
1929
2077
|
/**
|
|
1930
|
-
*
|
|
1931
|
-
*
|
|
1932
|
-
* procedure that forgets `.use(rls(...))` fails closed. Opt a table out with
|
|
1933
|
-
* `.public()`. Non-mutating: returns a fresh `ExtendableSchema` carrying the
|
|
1934
|
-
* mode, so `.rls("required")` composes with `.extend(...)` either order.
|
|
2078
|
+
* Whole-shard erase. The RLS wrapper deliberately **fails this closed** rather
|
|
2079
|
+
* than wrapping it — see the wrapper's `wipeShard`.
|
|
1935
2080
|
*/
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
*/
|
|
1946
|
-
declare const SYSTEM_INDEX_FIELDS: readonly ["_commitSeq", "_creationTime", "_id"];
|
|
2081
|
+
wipeShard?: (options?: {
|
|
2082
|
+
chunkSize?: number;
|
|
2083
|
+
exclude?: ReadonlyArray<string>;
|
|
2084
|
+
tables?: ReadonlyArray<string>;
|
|
2085
|
+
}) => Promise<{
|
|
2086
|
+
deleted: number;
|
|
2087
|
+
tables: Record<string, number>;
|
|
2088
|
+
}>;
|
|
2089
|
+
}
|
|
1947
2090
|
/**
|
|
1948
|
-
*
|
|
1949
|
-
*
|
|
1950
|
-
*
|
|
1951
|
-
*
|
|
1952
|
-
*
|
|
1953
|
-
*
|
|
1954
|
-
* map rather than mapped to `{}`, and a kind with no declared indexes on a
|
|
1955
|
-
* table that HAS other kinds is simply absent from that table's entry.
|
|
1956
|
-
*
|
|
1957
|
-
* Kept per kind (rather than one flat name→fields record) because the engine
|
|
1958
|
-
* resolves `withIndex`/`withGeoIndex`/rank reads in THREE separate
|
|
1959
|
-
* namespaces (`tableDefinition.indexes` / `.geoIndexes` / `.rankIndexes` —
|
|
1960
|
-
* see `@lunora/shard-engine`'s `ctx-db.ts`), so the same name can legally and
|
|
1961
|
-
* unambiguously denote a different index per kind. A flat map would let one
|
|
1962
|
-
* kind's fields silently shadow another's for a colliding name, producing a
|
|
1963
|
-
* wrong-namespace answer from the mask guard (checking the wrong index's
|
|
1964
|
-
* fields) instead of the documented fail-open (missing lookup) — see plan 258.
|
|
2091
|
+
* What a procedure's `ctx.db` must structurally satisfy for the middleware
|
|
2092
|
+
* to wrap it. We deliberately mirror `@lunora/do`'s `DatabaseWriterLike`
|
|
2093
|
+
* rather than `@lunora/server`'s nominal `DatabaseWriter`/`DatabaseReader`:
|
|
2094
|
+
* the runtime adapter that flows in is the `DatabaseWriterLike`-shaped one,
|
|
2095
|
+
* and structural matching keeps this module free of an `@lunora/do`-typed
|
|
2096
|
+
* `ctx`.
|
|
1965
2097
|
*/
|
|
1966
|
-
type
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
2098
|
+
type RlsDatabase = DatabaseWriterLike;
|
|
2099
|
+
/** Roles list source on the context. Tolerant of older auth states. */
|
|
2100
|
+
type AuthLike = {
|
|
2101
|
+
getIdentity?: () => Promise<Record<string, unknown> | null>;
|
|
2102
|
+
roles?: ReadonlyArray<string>;
|
|
2103
|
+
userId?: null | string;
|
|
2104
|
+
};
|
|
2105
|
+
/** Minimal shape the middleware needs on the incoming ctx. */
|
|
2106
|
+
interface RlsContextIn {
|
|
2107
|
+
auth?: AuthLike;
|
|
2108
|
+
db: RlsDatabase;
|
|
2109
|
+
}
|
|
2110
|
+
declare const rls: <Context extends RlsContextIn = RlsContextIn>(policies: ReadonlyArray<Policy<Context>>, options?: RlsOptions) => Middleware<Context, Context>;
|
|
1973
2111
|
/**
|
|
1974
2112
|
* Context handed to a {@link MaskFn} (and to {@link MaskOptions.bypass}). The
|
|
1975
2113
|
* `auth` shape mirrors RLS's `PolicyContext.auth` one-for-one — same identity
|
|
@@ -2826,4 +2964,4 @@ interface StorageContextIn {
|
|
|
2826
2964
|
}
|
|
2827
2965
|
declare const storageRules: <Context extends StorageContextIn = StorageContextIn>(rules: ReadonlyArray<StorageRule<Context>>, options?: StorageRulesOptions) => Middleware<Context, Context>;
|
|
2828
2966
|
declare const VERSION = "0.0.0";
|
|
2829
|
-
export { type ActionBuilder, type ActionCtx, type AggregateIndexDefinition, type AggregateIndexOptions, type AggregateOp, type ArgsValidator, type Component, type ComponentFunctions, type CreateOptions, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, type DataModelInit, type DefineComponentOptions, type DefineIdentityOptions, type DefineListArgsConfig, type DefinePluginOptions, type DefinePolicyInput, type DefinePresenceOptions, type DefineStorageRuleInput, type DurableObjectJurisdiction, type DurableStreamOptions, type EmptyArgs, type EnvAccessor, type EnvKeyFailure, type EnvShape, type ExposeConfig, type ExtendableSchema, type FacadeEntry, type FacadeWriterLike, type FunctionKind, type HttpActionCtx, type HttpActionHandler, type HttpMethod, type HttpRoute, type HttpRouteBuilder, type HttpRouteFactory, type HttpRouteHandlerOptions, type HttpStreamHandlerOptions, type IdentityContract, type IdentityRejectMode, type IdentityValidation, type IndexFieldsByTable, type InferArgs, type InferEnv, type InferIdentity, type InlineAggregateIndexOptions, type InlineRankIndexOptions, type InternalActionBuilder, type InternalMutationBuilder, type InternalQueryBuilder, type LifecycleEvent, type LifecycleHandler, type ListArgsSpec, type ListArgsValidators, type ListArgsValue, type ListFilterOperators, type ListOrderByEntry, type ListWhere, type LunoraBuilders, LunoraEnvError, LunoraError, type LunoraHttpApp, type LunoraHttpEnv, type LunoraRouteHandler, type ManyRelation, type MaskColumns, type MaskContext, type MaskFn, type MaskOptions, type MaskPolicies, type MaskRegistry, type MaskStrategy, type Middleware, type MiddlewareNext, type MigrationCtx, type MigrationDefinition, type MigrationDocument, type MigrationReader, type MigrationTransform, type MutationBuilder, type MutationCtx, type MutatorDefinition, type OnDeleteAction, type OneRelation, type OrmLike, DEFAULT_TTL_MS as PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, type Permission, type Plugin, type Policy, type PrefixedTables, type PresenceComponent, type PresenceFunctions, type PresenceMember, type ProtectPublicOptions, type QueryBuilder, type QueryCtx, type RankIndexDefinition, type RankIndexOptions, type ReactorHandler, type ReactorOutcome, type ReactorSelect, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMigration, type RegisteredMutation, type RegisteredMutator, type RegisteredQuery, type RegisteredReactor, type RegisteredShape, type RegisteredStream, type RelationBuilder, type RelationDefinition, type RlsOptions, type RlsReadRegistry, type Role, type Schema, type SchemaExtension, type ShapeDefinition, type ShapeReadWhereRequest, type ShardInitEvent, type ShardInitHandler, type StorageOperation, type StorageRule, type StorageRuleContext, type StorageRuleDecision, type StorageRulesOptions, type TableBuilder, type TableDefinition, type TerminalKind, type TriggerBuilder, type TriggerDefinition, type TypedDefinePolicyInput, VERSION, type VectorEmbedder, type VectorIndexDefinition, type VectorIndexOptions, type VectorMetric, type VectorizeOptions, type WhereInput, allowAll, asBucketStorage, bindOrm, bindTableFacade, buildMaskRegistry, buildRlsReadRegistry, clampLimit, composePluginMiddleware, composeShapeReadWhere, createPolicyDsl, createSecrets, defineAggregateIndex, defineComponent, defineEnv, defineIdentity, defineListArgs, defineMigration, defineMutator, definePermission, definePlugin, definePolicies, definePolicy, definePresence, defineRankIndex, defineRole, defineSchema, defineSchemaExtension, defineShape, defineStorageRule, defineStorageRules, defineTable, defineVectorIndex, deny, httpAction, httpRoute, httpRouter, indexFieldsFromSchema, initLunora, installPlugins, isDeny, isSafeHeaderValue, mask, mergeSchemaExtension, onConnect, onDisconnect, onQueryChange, onShardInit, presenceExtension, protectPublic, redactSecrets, rls, serveStorageObject, storageRules, toWhereInput };
|
|
2967
|
+
export { type ActionBuilder, type ActionCtx, type AggregateIndexDefinition, type AggregateIndexOptions, type AggregateOp, type ArgsValidator, type Component, type ComponentFunctions, type CreateOptions, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_REDACTED_FIELDS as DOCUMENT_HISTORY_REDACTED_FIELDS, DOCUMENT_HISTORY_TABLE, type DataModelInit, type DeferredDeleteFlushResult, type DefineComponentOptions, type DefineDocumentHistoryOptions, type DefineIdentityOptions, type DefineListArgsConfig, type DefinePluginOptions, type DefinePolicyInput, type DefinePresenceOptions, type DefineStorageRuleInput, type DocumentHistoryComponent, type DocumentHistoryEntry, type DocumentHistoryFunctions, type DurableObjectJurisdiction, type DurableStreamOptions, type EmptyArgs, type EnvAccessor, type EnvKeyFailure, type EnvShape, type ExposeConfig, type ExtendableSchema, type FacadeEntry, type FacadeWriterLike, type FunctionKind, type HttpActionCtx, type HttpActionHandler, type HttpMethod, type HttpRoute, type HttpRouteBuilder, type HttpRouteFactory, type HttpRouteHandlerOptions, type HttpStreamHandlerOptions, type IdentityContract, type IdentityRejectMode, type IdentityValidation, type IndexFieldsByTable, type InferArgs, type InferEnv, type InferIdentity, type InlineAggregateIndexOptions, type InlineRankIndexOptions, type InternalActionBuilder, type InternalMutationBuilder, type InternalQueryBuilder, type LifecycleEvent, type LifecycleHandler, type ListArgsSpec, type ListArgsValidators, type ListArgsValue, type ListFilterOperators, type ListOrderByEntry, type ListWhere, type LunoraBuilders, LunoraEnvError, LunoraError, type LunoraHttpApp, type LunoraHttpEnv, type LunoraRouteHandler, type ManyRelation, type MaskColumns, type MaskContext, type MaskFn, type MaskOptions, type MaskPolicies, type MaskRegistry, type MaskStrategy, type Middleware, type MiddlewareNext, type MigrationCtx, type MigrationDefinition, type MigrationDocument, type MigrationReader, type MigrationTransform, type MutationBuilder, type MutationCtx, type MutatorDefinition, type OnDeleteAction, type OneRelation, type OrmLike, DEFAULT_TTL_MS as PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, type Permission, type Plugin, type Policy, type PrefixedTables, type PresenceComponent, type PresenceFunctions, type PresenceMember, type ProtectPublicOptions, type QueryBuilder, type QueryCtx, type RankIndexDefinition, type RankIndexOptions, type ReactorHandler, type ReactorOutcome, type ReactorSelect, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMigration, type RegisteredMutation, type RegisteredMutator, type RegisteredQuery, type RegisteredReactor, type RegisteredShape, type RegisteredStream, type RelationBuilder, type RelationDefinition, type RlsOptions, type RlsReadRegistry, type Role, type Schema, type SchemaExtension, type ShapeDefinition, type ShapeReadWhereRequest, type ShardInitEvent, type ShardInitHandler, type StorageOperation, type StorageRule, type StorageRuleContext, type StorageRuleDecision, type StorageRulesOptions, type TableBuilder, type TableDefinition, type TerminalKind, type TriggerBuilder, type TriggerDefinition, type TypedDefinePolicyInput, VERSION, type VectorEmbedder, type VectorIndexDefinition, type VectorIndexOptions, type VectorMetric, type VectorizeOptions, type WhereInput, allowAll, asBucketStorage, bindOrm, bindTableFacade, buildMaskRegistry, buildRlsReadRegistry, clampLimit, composePluginMiddleware, composeShapeReadWhere, createPolicyDsl, createSecrets, defineAggregateIndex, defineComponent, defineDocumentHistory, defineEnv, defineIdentity, defineListArgs, defineMigration, defineMutator, definePermission, definePlugin, definePolicies, definePolicy, definePresence, defineRankIndex, defineRole, defineSchema, defineSchemaExtension, defineShape, defineStorageRule, defineStorageRules, defineTable, defineVectorIndex, deny, documentHistoryExtension, flushDeferredDeletes, httpAction, httpRoute, httpRouter, indexFieldsFromSchema, initLunora, installPlugins, isDeny, isSafeHeaderValue, mask, mergeSchemaExtension, onConnect, onDisconnect, onQueryChange, onShardInit, presenceExtension, protectPublic, redactSecrets, rls, serveStorageObject, storageRules, toWhereInput, withDeferredDeletes };
|