@lunora/server 1.0.0-alpha.78 → 1.0.0-alpha.79

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Validator, Infer, ValidatorMap, InferValidatorMap, ColumnValidator, v } 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, LifecycleEvent, RegisteredLifecycleHook, TableDefinition, RegisteredFunction, VectorIndexDefinition, Schema, AggregateOp, DurableObjectJurisdiction, RelationDefinition, GlobalBackend, OnDeleteAction, SearchLanguage, SearchStrategy, ExternalSourceDefinition, TriggerBuilder, TriggerDefinition, VectorEmbedder, VectorMetric, AggregateIndexDefinition, RankIndexDefinition } 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 PaginationOptions, type PaginationResult, type RankSortKey, type ReadOnlyStorage, type RestCacheConfig, 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 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, LifecycleEvent, ShardInitEvent, RegisteredLifecycleHook, TableDefinition, RegisteredFunction, VectorIndexDefinition, Schema, AggregateOp, DurableObjectJurisdiction, RelationDefinition, GlobalBackend, OnDeleteAction, SearchLanguage, SearchStrategy, ExternalSourceDefinition, TriggerBuilder, TriggerDefinition, VectorEmbedder, VectorMetric, AggregateIndexDefinition, RankIndexDefinition } 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 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 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';
@@ -903,10 +903,63 @@ declare const defineIdentity: <A extends ValidatorMap>(claims: InferValidatorMap
903
903
  }>;
904
904
  /** Handler for a connection-lifecycle hook. */
905
905
  type LifecycleHandler = (context: MutationCtx, event: LifecycleEvent) => Promise<void> | void;
906
+ /** Handler for a shard-init hook. */
907
+ type ShardInitHandler = (context: MutationCtx, event: ShardInitEvent) => Promise<void> | void;
906
908
  /** Register a hook that fires once when a client's WebSocket connects. */
907
909
  declare const onConnect: (handler: LifecycleHandler) => RegisteredLifecycleHook;
908
910
  /** Register a hook that fires once when a client's WebSocket disconnects. */
909
911
  declare const onDisconnect: (handler: LifecycleHandler) => RegisteredLifecycleHook;
912
+ /**
913
+ * Register a hook that fires ONCE per Durable Object instance, before any
914
+ * handler on that instance can run — the re-init half of `.memory()` tables.
915
+ *
916
+ * A shard is not a process that stays up. Cloudflare reconstructs the Durable
917
+ * Object after every eviction, and a shard whose sockets are hibernating is
918
+ * evicted routinely, so "cold start" is a steady-state event rather than a rare
919
+ * one. Everything the shard held in memory is gone at that moment: the JS heap,
920
+ * and every `.memory()` table, which the framework has already cleared by the
921
+ * time this hook runs.
922
+ *
923
+ * ```ts
924
+ * // lunora/init.ts
925
+ * import { onShardInit } from "@lunora/server";
926
+ *
927
+ * export const warm = onShardInit(async (ctx, event) => {
928
+ * // Rebuild ephemeral state from the durable tables that outlived us.
929
+ * for await (const member of ctx.db.roomMembers.iterate({ where: { roomId: event.shardKey } })) {
930
+ * await ctx.db.presence.insert({ userId: member.userId, status: "away" });
931
+ * }
932
+ * });
933
+ * ```
934
+ *
935
+ * **Ordering is the guarantee.** Memory tables are cleared, then every init hook
936
+ * runs to completion, and only then does the dispatch that triggered the cold
937
+ * start proceed. No handler, subscription refresh, alarm, or shape poke can
938
+ * observe a memory table in the gap. Hooks run sequentially in manifest order,
939
+ * so one may depend on state an earlier one wrote.
940
+ *
941
+ * **It is a mutation, and it runs on every cold start.** Keep it cheap and keep
942
+ * it idempotent: it is on the latency path of the request that woke the shard,
943
+ * and it will run again — many times — over the shard's life. Writing to durable
944
+ * tables from here is legal and occasionally right, but remember it is a
945
+ * rebuild, not a migration; use `defineMigration` for anything that should
946
+ * happen once.
947
+ *
948
+ * **No caller identity.** The hook dispatches as a trusted system call with no
949
+ * request identity — `ctx.auth` is anonymous and RLS does not apply even under
950
+ * `.rls("required")`, exactly as for a cron tick or a migration. RLS scopes rows
951
+ * to a user and an init hook has none, so `ctx.db` here sees every row: scope
952
+ * your reads yourself. (`onConnect`/`onDisconnect` are the opposite case — they
953
+ * carry the socket's verified identity and stay RLS-guarded.)
954
+ *
955
+ * A throw is logged and does NOT fail the dispatch that woke the shard — an init
956
+ * hook that cannot rebuild presence must not take the whole shard down with it.
957
+ * The table is then cleared but not refilled, so reads see nothing. A failure
958
+ * EARLIER, before the framework's clear runs, instead leaves the previous
959
+ * instance's rows in place: a memory table's rows live in SQLite until they are
960
+ * deleted, so an eviction on its own does not remove them.
961
+ */
962
+ declare const onShardInit: (handler: ShardInitHandler) => RegisteredLifecycleHook;
910
963
  /** Default `limit` when the caller doesn't ask for one. */
911
964
  declare const DEFAULT_LIMIT = 25;
912
965
  /** Default ceiling on `limit`, so one request can't ask for an unbounded page. */
@@ -1561,6 +1614,36 @@ interface InlineRankIndexOptions<Shape extends Record<string, Validator> = Recor
1561
1614
  interface TableBuilder<Shape extends Record<string, Validator> = Record<string, Validator>> extends TableDefinition<Shape> {
1562
1615
  /** Declare an aggregate (counter/sum/…) maintained by triggers for O(1) reads. */
1563
1616
  aggregateIndex: (name: string, options?: InlineAggregateIndexOptions<Shape>) => TableBuilder<Shape>;
1617
+ /**
1618
+ * Stamp every row with `_commitSeq` — a per-shard integer, allocated once
1619
+ * per mutation and strictly increasing in **commit order**, refreshed on
1620
+ * every write to the row (insert, patch, replace, and the marker flip a
1621
+ * `.softDelete()` performs).
1622
+ *
1623
+ * `_creationTime` is wall-clock and therefore cannot order commits: the
1624
+ * clock is read when the handler runs, the write lands when the transaction
1625
+ * commits, and nothing ties those instants together. A changefeed paging on
1626
+ * `_creationTime` can skip a row permanently. Paging on `_commitSeq`
1627
+ * (`where: { _commitSeq: { gt: cursor } }, orderBy: ["_commitSeq"]`) cannot.
1628
+ *
1629
+ * It orders COMMITS, not rows: one mutation's rows share a value. A bounded
1630
+ * page can therefore end mid-group, so a consumer must checkpoint at a
1631
+ * sequence it has seen the whole of, never at the last row of a full page.
1632
+ * An action's writes are the exception to the grouping — they commit
1633
+ * independently, so each gets its own sequence.
1634
+ *
1635
+ * Ordered, not contiguous — read a gap as "nothing to see", never as loss.
1636
+ * Per-shard, not global: two shards allocate independently, so a cursor is
1637
+ * only meaningful against the shard it came from. Rejected on `.global()`
1638
+ * tables, which have no shard-local transaction to allocate inside.
1639
+ *
1640
+ * **A hard delete is invisible to the feed.** The sequence lives on the row,
1641
+ * so a physically removed row takes it along: the row stops appearing, but
1642
+ * no event says it went away. Pair `.commitOrdered()` with `.softDelete()`
1643
+ * when the feed must observe deletes — the tombstone flip is an UPDATE, so
1644
+ * it advances the sequence and pages through like any other change.
1645
+ */
1646
+ commitOrdered: () => TableBuilder<Shape>;
1564
1647
  /**
1565
1648
  * Mark this table as written outside Lunora's discoverable insert path —
1566
1649
  * by an adapter, a migration, or framework middleware (e.g. `@lunora/auth`'s
@@ -1592,6 +1675,36 @@ interface TableBuilder<Shape extends Record<string, Validator> = Record<string,
1592
1675
  index: (name: string, fields: ReadonlyArray<(keyof Shape & string) | (typeof SYSTEM_INDEX_FIELDS)[number]>, options?: {
1593
1676
  unique?: boolean;
1594
1677
  }) => TableBuilder<Shape>;
1678
+ /**
1679
+ * Declare this table EPHEMERAL — state the shard rebuilds rather than
1680
+ * remembers.
1681
+ *
1682
+ * A memory table is a full `ctx.db` table: indexes, `where`, `orderBy`,
1683
+ * pagination, relations, live queries. What it is not is durable. Its rows
1684
+ * are wiped the moment the Durable Object is reconstructed — which happens
1685
+ * on every eviction, and a WebSocket-hibernating shard is evicted often — so
1686
+ * a memory table holds only what can be derived again: presence and cursors,
1687
+ * a live participant list, a rate-limit window, an actor's scratch state.
1688
+ *
1689
+ * Pair it with `onShardInit` to rebuild whatever the app needs present.
1690
+ * The framework guarantees the ordering: every memory table is cleared, and
1691
+ * every init hook has run, before any handler can read one. Without a hook a
1692
+ * memory table simply comes back empty, which is a correct state for
1693
+ * presence and a wrong one for a cache someone is treating as authoritative.
1694
+ *
1695
+ * **On Cloudflare the rows still transit the DO's SQLite.** workerd exposes
1696
+ * exactly one SQL handle and no memory-backed database, so `.memory()` buys
1697
+ * the LIFETIME (and skips the CDC changelog, so an append-heavy presence
1698
+ * table does not grow the op-log), not the write. Treat it as "state I am
1699
+ * happy to lose", not as "state that is free to write" — see
1700
+ * `PlatformCapabilities.memoryTables`, rated `emulated` for exactly this
1701
+ * reason.
1702
+ *
1703
+ * Rejected alongside `.global()` (a D1 table is not this shard's to clear),
1704
+ * `.commitOrdered()` (a sequence that resets is not a sequence), and
1705
+ * `.source()` (an externally-materialized table is not ours to wipe).
1706
+ */
1707
+ memory: () => TableBuilder<Shape>;
1595
1708
  /**
1596
1709
  * Name the column holding the owning user's id, so "only the owner sees these
1597
1710
  * rows" is declared once here rather than restated in every shape.
@@ -1813,7 +1926,7 @@ type ExtendableSchema<T extends Record<string, TableDefinition>> = {
1813
1926
  * below (via `SYSTEM_INDEX_FIELDS_SET`) — declared once so the two can't
1814
1927
  * drift apart.
1815
1928
  */
1816
- declare const SYSTEM_INDEX_FIELDS: readonly ["_creationTime", "_id"];
1929
+ declare const SYSTEM_INDEX_FIELDS: readonly ["_commitSeq", "_creationTime", "_id"];
1817
1930
  /**
1818
1931
  * Per-table, per-KIND index→declared-fields map: for each table, each index
1819
1932
  * KIND (`index` | `rank` | `geo`) that has at least one declared index maps
@@ -2361,6 +2474,48 @@ interface ProtectPublicOptions<Context> {
2361
2474
  * transparent pass-through.
2362
2475
  */
2363
2476
  declare const protectPublic: <Context>(options: ProtectPublicOptions<Context>) => Middleware<Context, Context>;
2477
+ /**
2478
+ * The read a reactor watches. Runs on every flush that touched a table it read
2479
+ * last time, so keep it indexed and bounded — this is the reactor's steady-state
2480
+ * cost, paid whether or not the handler ends up running.
2481
+ */
2482
+ type ReactorSelect<T> = (context: QueryCtx) => Promise<T> | T;
2483
+ /** What runs when {@link ReactorSelect}'s result changes. Receives the current result. */
2484
+ type ReactorHandler<T> = (context: MutationCtx, result: T) => Promise<void> | void;
2485
+ /**
2486
+ * A registered reactor — an internal mutation tagged `lifecycle: "reactor"`.
2487
+ *
2488
+ * Typed separately from `RegisteredLifecycleHook` (which returns `void`) because
2489
+ * a reactor dispatch reports a {@link ReactorOutcome} back to the shard: the
2490
+ * digest becomes the next baseline, and the shard cannot compute it itself
2491
+ * without re-running the read.
2492
+ */
2493
+ type RegisteredReactor = RegisteredFunction<Record<string, never>, ReactorOutcome, "mutation"> & {
2494
+ readonly lifecycle: "reactor";
2495
+ };
2496
+ /** What a reactor dispatch reports back so the shard can update the baseline. */
2497
+ interface ReactorOutcome {
2498
+ /** Digest of the result the handler was given (or would have been). */
2499
+ digest: string;
2500
+ /** Whether the handler actually ran — `false` when the result was unchanged. */
2501
+ ran: boolean;
2502
+ }
2503
+ /**
2504
+ * Register a reactor: run `handler` after a write flush, whenever `select`'s
2505
+ * result differs from the last result the handler saw. See the module docblock
2506
+ * for the trigger comparison and the convergence contract.
2507
+ *
2508
+ * The read is an inline callback rather than a reference to a registered query
2509
+ * on purpose. It keeps the declaration statically discoverable with no import
2510
+ * resolution and no dependency on `_generated/api`, and it lets the read and the
2511
+ * handler share one transaction and one read footprint.
2512
+ *
2513
+ * That shared footprint is deliberately conservative: it is the union of what
2514
+ * `select` AND `handler` read, so a table the handler merely consults can wake
2515
+ * the reactor later. The cost of that is one extra `select` — whose digest then
2516
+ * matches and suppresses the handler — never a missed reaction.
2517
+ */
2518
+ declare const onQueryChange: <T>(select: ReactorSelect<T>, handler: ReactorHandler<T>) => RegisteredReactor;
2364
2519
  declare const definePolicy: <Context = unknown>(input: DefinePolicyInput<Context>) => Policy<Context>;
2365
2520
  /**
2366
2521
  * Build a project-bound, relation-aware `definePolicy` typed against the
@@ -2638,4 +2793,4 @@ interface StorageContextIn {
2638
2793
  }
2639
2794
  declare const storageRules: <Context extends StorageContextIn = StorageContextIn>(rules: ReadonlyArray<StorageRule<Context>>, options?: StorageRulesOptions) => Middleware<Context, Context>;
2640
2795
  declare const VERSION = "0.0.0";
2641
- 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 RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMigration, type RegisteredMutation, type RegisteredMutator, type RegisteredQuery, type RegisteredShape, type RegisteredStream, type RelationBuilder, type RelationDefinition, type RlsOptions, type RlsReadRegistry, type Role, type Schema, type SchemaExtension, type ShapeDefinition, type ShapeReadWhereRequest, type StorageOperation, type StorageRule, type StorageRuleContext, type StorageRuleDecision, type StorageRulesOptions, type TableBuilder, type TableDefinition, type TerminalKind, type TriggerBuilder, type TriggerDefinition, type TypedDefinePolicyInput, VERSION, type VectorEmbedder, type VectorIndexDefinition, type VectorIndexOptions, type VectorMetric, type VectorizeOptions, type WhereInput, allowAll, asBucketStorage, bindOrm, bindTableFacade, 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, presenceExtension, protectPublic, redactSecrets, rls, serveStorageObject, storageRules, toWhereInput };
2796
+ 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 };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Validator, Infer, ValidatorMap, InferValidatorMap, ColumnValidator, v } 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, LifecycleEvent, RegisteredLifecycleHook, TableDefinition, RegisteredFunction, VectorIndexDefinition, Schema, AggregateOp, DurableObjectJurisdiction, RelationDefinition, GlobalBackend, OnDeleteAction, SearchLanguage, SearchStrategy, ExternalSourceDefinition, TriggerBuilder, TriggerDefinition, VectorEmbedder, VectorMetric, AggregateIndexDefinition, RankIndexDefinition } from "./types.js";
4
- export { type AnyApi, type AuthState, type CachePurge, type DatabaseReader, type DatabaseWriter, type FunctionVisibility, type GeoBoundingBox, type GeoFilterBuilder, type GeoIndexDefinition, type GeoPointInput, type IndexDefinition, type IndexRangeBuilder, type LifecycleEventKind, type LogFields, type LunoraLogMethod, type LunoraLogger, type LunoraMetrics, type LunoraTracer, type LunoraWideEvent, type PaginationOptions, type PaginationResult, type RankSortKey, type ReadOnlyStorage, type RestCacheConfig, 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 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.js";
3
+ import { ArgsValidator, InferArgs, RegisteredAction, ExposeConfig, X402ProcedureConfig, ActionCtx, MutationCtx, RegisteredMutation, QueryCtx, RegisteredQuery, DurableStreamOptions, RegisteredStream, FunctionKind, Secrets, LifecycleEvent, ShardInitEvent, RegisteredLifecycleHook, TableDefinition, RegisteredFunction, VectorIndexDefinition, Schema, AggregateOp, DurableObjectJurisdiction, RelationDefinition, GlobalBackend, OnDeleteAction, SearchLanguage, SearchStrategy, ExternalSourceDefinition, TriggerBuilder, TriggerDefinition, VectorEmbedder, VectorMetric, AggregateIndexDefinition, RankIndexDefinition } from "./types.js";
4
+ export { type AnyApi, type AuthState, type CachePurge, type DatabaseReader, type DatabaseWriter, type FunctionVisibility, type GeoBoundingBox, type GeoFilterBuilder, type GeoIndexDefinition, type GeoPointInput, type IndexDefinition, type IndexRangeBuilder, type LifecycleEventKind, type LogFields, type LunoraLogMethod, type LunoraLogger, type LunoraMetrics, type LunoraTracer, type LunoraWideEvent, type PaginationOptions, type PaginationResult, type RankSortKey, type ReadOnlyStorage, type 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 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.js";
5
5
  import { LunoraError as LunoraError$1, LunoraErrorCode } from '@lunora/errors';
6
6
  export type { LunoraErrorCode } from '@lunora/errors';
7
7
  import { Context, Hono } from 'hono';
@@ -903,10 +903,63 @@ declare const defineIdentity: <A extends ValidatorMap>(claims: InferValidatorMap
903
903
  }>;
904
904
  /** Handler for a connection-lifecycle hook. */
905
905
  type LifecycleHandler = (context: MutationCtx, event: LifecycleEvent) => Promise<void> | void;
906
+ /** Handler for a shard-init hook. */
907
+ type ShardInitHandler = (context: MutationCtx, event: ShardInitEvent) => Promise<void> | void;
906
908
  /** Register a hook that fires once when a client's WebSocket connects. */
907
909
  declare const onConnect: (handler: LifecycleHandler) => RegisteredLifecycleHook;
908
910
  /** Register a hook that fires once when a client's WebSocket disconnects. */
909
911
  declare const onDisconnect: (handler: LifecycleHandler) => RegisteredLifecycleHook;
912
+ /**
913
+ * Register a hook that fires ONCE per Durable Object instance, before any
914
+ * handler on that instance can run — the re-init half of `.memory()` tables.
915
+ *
916
+ * A shard is not a process that stays up. Cloudflare reconstructs the Durable
917
+ * Object after every eviction, and a shard whose sockets are hibernating is
918
+ * evicted routinely, so "cold start" is a steady-state event rather than a rare
919
+ * one. Everything the shard held in memory is gone at that moment: the JS heap,
920
+ * and every `.memory()` table, which the framework has already cleared by the
921
+ * time this hook runs.
922
+ *
923
+ * ```ts
924
+ * // lunora/init.ts
925
+ * import { onShardInit } from "@lunora/server";
926
+ *
927
+ * export const warm = onShardInit(async (ctx, event) => {
928
+ * // Rebuild ephemeral state from the durable tables that outlived us.
929
+ * for await (const member of ctx.db.roomMembers.iterate({ where: { roomId: event.shardKey } })) {
930
+ * await ctx.db.presence.insert({ userId: member.userId, status: "away" });
931
+ * }
932
+ * });
933
+ * ```
934
+ *
935
+ * **Ordering is the guarantee.** Memory tables are cleared, then every init hook
936
+ * runs to completion, and only then does the dispatch that triggered the cold
937
+ * start proceed. No handler, subscription refresh, alarm, or shape poke can
938
+ * observe a memory table in the gap. Hooks run sequentially in manifest order,
939
+ * so one may depend on state an earlier one wrote.
940
+ *
941
+ * **It is a mutation, and it runs on every cold start.** Keep it cheap and keep
942
+ * it idempotent: it is on the latency path of the request that woke the shard,
943
+ * and it will run again — many times — over the shard's life. Writing to durable
944
+ * tables from here is legal and occasionally right, but remember it is a
945
+ * rebuild, not a migration; use `defineMigration` for anything that should
946
+ * happen once.
947
+ *
948
+ * **No caller identity.** The hook dispatches as a trusted system call with no
949
+ * request identity — `ctx.auth` is anonymous and RLS does not apply even under
950
+ * `.rls("required")`, exactly as for a cron tick or a migration. RLS scopes rows
951
+ * to a user and an init hook has none, so `ctx.db` here sees every row: scope
952
+ * your reads yourself. (`onConnect`/`onDisconnect` are the opposite case — they
953
+ * carry the socket's verified identity and stay RLS-guarded.)
954
+ *
955
+ * A throw is logged and does NOT fail the dispatch that woke the shard — an init
956
+ * hook that cannot rebuild presence must not take the whole shard down with it.
957
+ * The table is then cleared but not refilled, so reads see nothing. A failure
958
+ * EARLIER, before the framework's clear runs, instead leaves the previous
959
+ * instance's rows in place: a memory table's rows live in SQLite until they are
960
+ * deleted, so an eviction on its own does not remove them.
961
+ */
962
+ declare const onShardInit: (handler: ShardInitHandler) => RegisteredLifecycleHook;
910
963
  /** Default `limit` when the caller doesn't ask for one. */
911
964
  declare const DEFAULT_LIMIT = 25;
912
965
  /** Default ceiling on `limit`, so one request can't ask for an unbounded page. */
@@ -1561,6 +1614,36 @@ interface InlineRankIndexOptions<Shape extends Record<string, Validator> = Recor
1561
1614
  interface TableBuilder<Shape extends Record<string, Validator> = Record<string, Validator>> extends TableDefinition<Shape> {
1562
1615
  /** Declare an aggregate (counter/sum/…) maintained by triggers for O(1) reads. */
1563
1616
  aggregateIndex: (name: string, options?: InlineAggregateIndexOptions<Shape>) => TableBuilder<Shape>;
1617
+ /**
1618
+ * Stamp every row with `_commitSeq` — a per-shard integer, allocated once
1619
+ * per mutation and strictly increasing in **commit order**, refreshed on
1620
+ * every write to the row (insert, patch, replace, and the marker flip a
1621
+ * `.softDelete()` performs).
1622
+ *
1623
+ * `_creationTime` is wall-clock and therefore cannot order commits: the
1624
+ * clock is read when the handler runs, the write lands when the transaction
1625
+ * commits, and nothing ties those instants together. A changefeed paging on
1626
+ * `_creationTime` can skip a row permanently. Paging on `_commitSeq`
1627
+ * (`where: { _commitSeq: { gt: cursor } }, orderBy: ["_commitSeq"]`) cannot.
1628
+ *
1629
+ * It orders COMMITS, not rows: one mutation's rows share a value. A bounded
1630
+ * page can therefore end mid-group, so a consumer must checkpoint at a
1631
+ * sequence it has seen the whole of, never at the last row of a full page.
1632
+ * An action's writes are the exception to the grouping — they commit
1633
+ * independently, so each gets its own sequence.
1634
+ *
1635
+ * Ordered, not contiguous — read a gap as "nothing to see", never as loss.
1636
+ * Per-shard, not global: two shards allocate independently, so a cursor is
1637
+ * only meaningful against the shard it came from. Rejected on `.global()`
1638
+ * tables, which have no shard-local transaction to allocate inside.
1639
+ *
1640
+ * **A hard delete is invisible to the feed.** The sequence lives on the row,
1641
+ * so a physically removed row takes it along: the row stops appearing, but
1642
+ * no event says it went away. Pair `.commitOrdered()` with `.softDelete()`
1643
+ * when the feed must observe deletes — the tombstone flip is an UPDATE, so
1644
+ * it advances the sequence and pages through like any other change.
1645
+ */
1646
+ commitOrdered: () => TableBuilder<Shape>;
1564
1647
  /**
1565
1648
  * Mark this table as written outside Lunora's discoverable insert path —
1566
1649
  * by an adapter, a migration, or framework middleware (e.g. `@lunora/auth`'s
@@ -1592,6 +1675,36 @@ interface TableBuilder<Shape extends Record<string, Validator> = Record<string,
1592
1675
  index: (name: string, fields: ReadonlyArray<(keyof Shape & string) | (typeof SYSTEM_INDEX_FIELDS)[number]>, options?: {
1593
1676
  unique?: boolean;
1594
1677
  }) => TableBuilder<Shape>;
1678
+ /**
1679
+ * Declare this table EPHEMERAL — state the shard rebuilds rather than
1680
+ * remembers.
1681
+ *
1682
+ * A memory table is a full `ctx.db` table: indexes, `where`, `orderBy`,
1683
+ * pagination, relations, live queries. What it is not is durable. Its rows
1684
+ * are wiped the moment the Durable Object is reconstructed — which happens
1685
+ * on every eviction, and a WebSocket-hibernating shard is evicted often — so
1686
+ * a memory table holds only what can be derived again: presence and cursors,
1687
+ * a live participant list, a rate-limit window, an actor's scratch state.
1688
+ *
1689
+ * Pair it with `onShardInit` to rebuild whatever the app needs present.
1690
+ * The framework guarantees the ordering: every memory table is cleared, and
1691
+ * every init hook has run, before any handler can read one. Without a hook a
1692
+ * memory table simply comes back empty, which is a correct state for
1693
+ * presence and a wrong one for a cache someone is treating as authoritative.
1694
+ *
1695
+ * **On Cloudflare the rows still transit the DO's SQLite.** workerd exposes
1696
+ * exactly one SQL handle and no memory-backed database, so `.memory()` buys
1697
+ * the LIFETIME (and skips the CDC changelog, so an append-heavy presence
1698
+ * table does not grow the op-log), not the write. Treat it as "state I am
1699
+ * happy to lose", not as "state that is free to write" — see
1700
+ * `PlatformCapabilities.memoryTables`, rated `emulated` for exactly this
1701
+ * reason.
1702
+ *
1703
+ * Rejected alongside `.global()` (a D1 table is not this shard's to clear),
1704
+ * `.commitOrdered()` (a sequence that resets is not a sequence), and
1705
+ * `.source()` (an externally-materialized table is not ours to wipe).
1706
+ */
1707
+ memory: () => TableBuilder<Shape>;
1595
1708
  /**
1596
1709
  * Name the column holding the owning user's id, so "only the owner sees these
1597
1710
  * rows" is declared once here rather than restated in every shape.
@@ -1813,7 +1926,7 @@ type ExtendableSchema<T extends Record<string, TableDefinition>> = {
1813
1926
  * below (via `SYSTEM_INDEX_FIELDS_SET`) — declared once so the two can't
1814
1927
  * drift apart.
1815
1928
  */
1816
- declare const SYSTEM_INDEX_FIELDS: readonly ["_creationTime", "_id"];
1929
+ declare const SYSTEM_INDEX_FIELDS: readonly ["_commitSeq", "_creationTime", "_id"];
1817
1930
  /**
1818
1931
  * Per-table, per-KIND index→declared-fields map: for each table, each index
1819
1932
  * KIND (`index` | `rank` | `geo`) that has at least one declared index maps
@@ -2361,6 +2474,48 @@ interface ProtectPublicOptions<Context> {
2361
2474
  * transparent pass-through.
2362
2475
  */
2363
2476
  declare const protectPublic: <Context>(options: ProtectPublicOptions<Context>) => Middleware<Context, Context>;
2477
+ /**
2478
+ * The read a reactor watches. Runs on every flush that touched a table it read
2479
+ * last time, so keep it indexed and bounded — this is the reactor's steady-state
2480
+ * cost, paid whether or not the handler ends up running.
2481
+ */
2482
+ type ReactorSelect<T> = (context: QueryCtx) => Promise<T> | T;
2483
+ /** What runs when {@link ReactorSelect}'s result changes. Receives the current result. */
2484
+ type ReactorHandler<T> = (context: MutationCtx, result: T) => Promise<void> | void;
2485
+ /**
2486
+ * A registered reactor — an internal mutation tagged `lifecycle: "reactor"`.
2487
+ *
2488
+ * Typed separately from `RegisteredLifecycleHook` (which returns `void`) because
2489
+ * a reactor dispatch reports a {@link ReactorOutcome} back to the shard: the
2490
+ * digest becomes the next baseline, and the shard cannot compute it itself
2491
+ * without re-running the read.
2492
+ */
2493
+ type RegisteredReactor = RegisteredFunction<Record<string, never>, ReactorOutcome, "mutation"> & {
2494
+ readonly lifecycle: "reactor";
2495
+ };
2496
+ /** What a reactor dispatch reports back so the shard can update the baseline. */
2497
+ interface ReactorOutcome {
2498
+ /** Digest of the result the handler was given (or would have been). */
2499
+ digest: string;
2500
+ /** Whether the handler actually ran — `false` when the result was unchanged. */
2501
+ ran: boolean;
2502
+ }
2503
+ /**
2504
+ * Register a reactor: run `handler` after a write flush, whenever `select`'s
2505
+ * result differs from the last result the handler saw. See the module docblock
2506
+ * for the trigger comparison and the convergence contract.
2507
+ *
2508
+ * The read is an inline callback rather than a reference to a registered query
2509
+ * on purpose. It keeps the declaration statically discoverable with no import
2510
+ * resolution and no dependency on `_generated/api`, and it lets the read and the
2511
+ * handler share one transaction and one read footprint.
2512
+ *
2513
+ * That shared footprint is deliberately conservative: it is the union of what
2514
+ * `select` AND `handler` read, so a table the handler merely consults can wake
2515
+ * the reactor later. The cost of that is one extra `select` — whose digest then
2516
+ * matches and suppresses the handler — never a missed reaction.
2517
+ */
2518
+ declare const onQueryChange: <T>(select: ReactorSelect<T>, handler: ReactorHandler<T>) => RegisteredReactor;
2364
2519
  declare const definePolicy: <Context = unknown>(input: DefinePolicyInput<Context>) => Policy<Context>;
2365
2520
  /**
2366
2521
  * Build a project-bound, relation-aware `definePolicy` typed against the
@@ -2638,4 +2793,4 @@ interface StorageContextIn {
2638
2793
  }
2639
2794
  declare const storageRules: <Context extends StorageContextIn = StorageContextIn>(rules: ReadonlyArray<StorageRule<Context>>, options?: StorageRulesOptions) => Middleware<Context, Context>;
2640
2795
  declare const VERSION = "0.0.0";
2641
- 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 RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMigration, type RegisteredMutation, type RegisteredMutator, type RegisteredQuery, type RegisteredShape, type RegisteredStream, type RelationBuilder, type RelationDefinition, type RlsOptions, type RlsReadRegistry, type Role, type Schema, type SchemaExtension, type ShapeDefinition, type ShapeReadWhereRequest, type StorageOperation, type StorageRule, type StorageRuleContext, type StorageRuleDecision, type StorageRulesOptions, type TableBuilder, type TableDefinition, type TerminalKind, type TriggerBuilder, type TriggerDefinition, type TypedDefinePolicyInput, VERSION, type VectorEmbedder, type VectorIndexDefinition, type VectorIndexOptions, type VectorMetric, type VectorizeOptions, type WhereInput, allowAll, asBucketStorage, bindOrm, bindTableFacade, 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, presenceExtension, protectPublic, redactSecrets, rls, serveStorageObject, storageRules, toWhereInput };
2796
+ 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 };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{initLunora as t}from"./packem_shared/initLunora-CRypyq1P.mjs";import{createSecrets as i}from"./packem_shared/createSecrets-CyrEddTF.mjs";import{LunoraEnvError as f,defineEnv as s,redactSecrets as m}from"./packem_shared/LunoraEnvError-CjFbbR9t.mjs";import{LunoraError as p}from"./packem_shared/LunoraError-DcKdk9Ti.mjs";import{bindOrm as c,bindTableFacade as l}from"./packem_shared/bindOrm-lWdeDi9P.mjs";import{httpAction as u,httpRoute as g,httpRouter as S,isSafeHeaderValue as R,serveStorageObject as h}from"./packem_shared/httpAction-Ddi7f0t6.mjs";import{defineIdentity as P}from"./packem_shared/defineIdentity-DwkNKwYa.mjs";import{onConnect as A,onDisconnect as I}from"./packem_shared/onConnect-DI23ecHU.mjs";import{DEFAULT_LIMIT as y,DEFAULT_MAX_LIMIT as M,clampLimit as _,defineListArgs as D}from"./packem_shared/DEFAULT_LIMIT-B947Pbij.mjs";import{defineMigration as k}from"./packem_shared/defineMigration-7GI9Qbtd.mjs";import{defineMutator as C}from"./packem_shared/defineMutator-DKy8UtbB.mjs";import{c as N,d as O,a as U,b as j,e as w,f as B,g as W,h as H,i as J,j as X,k as q,m as z}from"./packem_shared/plugin-CV2Fae9w.mjs";import{PRESENCE_DEFAULT_TTL_MS as K,PRESENCE_TABLE as Q,definePresence as Y,presenceExtension as Z}from"./packem_shared/PRESENCE_DEFAULT_TTL_MS-CEc-TxVw.mjs";import{protectPublic as ee}from"./packem_shared/protectPublic-Bo4G9xaV.mjs";import{defineShape as oe}from"./packem_shared/defineShape-DYxbNl5W.mjs";import{anyApi as ne}from"./types.mjs";import{cronJobs as ae}from"@lunora/scheduler";import{ValidationError as se,v as me}from"@lunora/values";import{allowAll as pe,deny as xe,isDeny as ce,toWhereInput as le}from"./packem_shared/allowAll-DkRAgItV.mjs";import{asBucketStorage as ue}from"./packem_shared/asBucketStorage-BthCnWop.mjs";import{buildMaskRegistry as Se}from"./packem_shared/buildMaskRegistry-72SL8fJn.mjs";import{buildRlsReadRegistry as he,composeShapeReadWhere as Le}from"./packem_shared/buildRlsReadRegistry-Deq8inqS.mjs";import{createPolicyDsl as be,definePermission as Ae,definePolicies as Ie,definePolicy as Te,defineRole as ye}from"./packem_shared/createPolicyDsl-DNfByAUX.mjs";import{defineStorageRule as _e,defineStorageRules as De}from"./packem_shared/defineStorageRule-BvxHVq3b.mjs";import{mask as ke}from"./packem_shared/mask-CfHNITgg.mjs";import{r as Ce}from"./packem_shared/middleware-DEW0yLvU.mjs";import{storageRules as Ne}from"./packem_shared/storageRules-CTnby6M1.mjs";const e="0.0.0";export{y as DEFAULT_LIMIT,M as DEFAULT_MAX_LIMIT,f as LunoraEnvError,p as LunoraError,K as PRESENCE_DEFAULT_TTL_MS,Q as PRESENCE_TABLE,e as VERSION,se as ValidationError,pe as allowAll,ne as anyApi,ue as asBucketStorage,c as bindOrm,l as bindTableFacade,Se as buildMaskRegistry,he as buildRlsReadRegistry,_ as clampLimit,N as composePluginMiddleware,Le as composeShapeReadWhere,be as createPolicyDsl,i as createSecrets,ae as cronJobs,O as defineAggregateIndex,U as defineComponent,s as defineEnv,P as defineIdentity,D as defineListArgs,k as defineMigration,C as defineMutator,Ae as definePermission,j as definePlugin,Ie as definePolicies,Te as definePolicy,Y as definePresence,w as defineRankIndex,ye as defineRole,B as defineSchema,W as defineSchemaExtension,oe as defineShape,_e as defineStorageRule,De as defineStorageRules,H as defineTable,J as defineVectorIndex,xe as deny,u as httpAction,g as httpRoute,S as httpRouter,X as indexFieldsFromSchema,t as initLunora,q as installPlugins,ce as isDeny,R as isSafeHeaderValue,ke as mask,z as mergeSchemaExtension,A as onConnect,I as onDisconnect,Z as presenceExtension,ee as protectPublic,m as redactSecrets,Ce as rls,h as serveStorageObject,Ne as storageRules,le as toWhereInput,me as v};
1
+ import{initLunora as t}from"./packem_shared/initLunora-CRypyq1P.mjs";import{createSecrets as i}from"./packem_shared/createSecrets-CyrEddTF.mjs";import{LunoraEnvError as f,defineEnv as s,redactSecrets as m}from"./packem_shared/LunoraEnvError-CjFbbR9t.mjs";import{LunoraError as p}from"./packem_shared/LunoraError-DcKdk9Ti.mjs";import{bindOrm as c,bindTableFacade as l}from"./packem_shared/bindOrm-lWdeDi9P.mjs";import{httpAction as E,httpRoute as g,httpRouter as S,isSafeHeaderValue as R,serveStorageObject as h}from"./packem_shared/httpAction-Ddi7f0t6.mjs";import{defineIdentity as I}from"./packem_shared/defineIdentity-DwkNKwYa.mjs";import{onConnect as b,onDisconnect as A,onShardInit as T}from"./packem_shared/onConnect-BLRoOpv2.mjs";import{DEFAULT_LIMIT as M,DEFAULT_MAX_LIMIT as _,clampLimit as D,defineListArgs as F}from"./packem_shared/DEFAULT_LIMIT-B947Pbij.mjs";import{defineMigration as C}from"./packem_shared/defineMigration-7GI9Qbtd.mjs";import{defineMutator as V}from"./packem_shared/defineMutator-DKy8UtbB.mjs";import{c as O,d as U,a as j,b as w,e as B,f as W,g as H,h as J,i as Q,j as X,k as q,m as z}from"./packem_shared/plugin-DQcLxTx1.mjs";import{PRESENCE_DEFAULT_TTL_MS as K,PRESENCE_TABLE as Y,definePresence as Z,presenceExtension as $}from"./packem_shared/PRESENCE_DEFAULT_TTL_MS-C-McIltl.mjs";import{protectPublic as re}from"./packem_shared/protectPublic-Bo4G9xaV.mjs";import{onQueryChange as te}from"./packem_shared/onQueryChange-gepYD6KC.mjs";import{defineShape as ie}from"./packem_shared/defineShape-DYxbNl5W.mjs";import{anyApi as fe}from"./types.mjs";import{cronJobs as me}from"@lunora/scheduler";import{ValidationError as pe,v as xe}from"@lunora/values";import{allowAll as le,deny as ue,isDeny as Ee,toWhereInput as ge}from"./packem_shared/allowAll-DkRAgItV.mjs";import{asBucketStorage as Re}from"./packem_shared/asBucketStorage-BthCnWop.mjs";import{buildMaskRegistry as Le}from"./packem_shared/buildMaskRegistry-72SL8fJn.mjs";import{buildRlsReadRegistry as Pe,composeShapeReadWhere as be}from"./packem_shared/buildRlsReadRegistry-Deq8inqS.mjs";import{createPolicyDsl as Te,definePermission as ye,definePolicies as Me,definePolicy as _e,defineRole as De}from"./packem_shared/createPolicyDsl-DNfByAUX.mjs";import{defineStorageRule as ke,defineStorageRules as Ce}from"./packem_shared/defineStorageRule-BvxHVq3b.mjs";import{mask as Ve}from"./packem_shared/mask-CfHNITgg.mjs";import{r as Oe}from"./packem_shared/middleware-DEW0yLvU.mjs";import{storageRules as je}from"./packem_shared/storageRules-CTnby6M1.mjs";const e="0.0.0";export{M as DEFAULT_LIMIT,_ as DEFAULT_MAX_LIMIT,f as LunoraEnvError,p as LunoraError,K as PRESENCE_DEFAULT_TTL_MS,Y as PRESENCE_TABLE,e as VERSION,pe as ValidationError,le as allowAll,fe as anyApi,Re as asBucketStorage,c as bindOrm,l as bindTableFacade,Le as buildMaskRegistry,Pe as buildRlsReadRegistry,D as clampLimit,O as composePluginMiddleware,be as composeShapeReadWhere,Te as createPolicyDsl,i as createSecrets,me as cronJobs,U as defineAggregateIndex,j as defineComponent,s as defineEnv,I as defineIdentity,F as defineListArgs,C as defineMigration,V as defineMutator,ye as definePermission,w as definePlugin,Me as definePolicies,_e as definePolicy,Z as definePresence,B as defineRankIndex,De as defineRole,W as defineSchema,H as defineSchemaExtension,ie as defineShape,ke as defineStorageRule,Ce as defineStorageRules,J as defineTable,Q as defineVectorIndex,ue as deny,E as httpAction,g as httpRoute,S as httpRouter,X as indexFieldsFromSchema,t as initLunora,q as installPlugins,Ee as isDeny,R as isSafeHeaderValue,Ve as mask,z as mergeSchemaExtension,b as onConnect,A as onDisconnect,te as onQueryChange,T as onShardInit,$ as presenceExtension,re as protectPublic,m as redactSecrets,Oe as rls,h as serveStorageObject,je as storageRules,ge as toWhereInput,xe as v};
@@ -1 +1 @@
1
- import{v as t}from"@lunora/values";import{initLunora as R}from"./initLunora-CRypyq1P.mjs";import{LunoraError as S}from"./LunoraError-DcKdk9Ti.mjs";import{onDisconnect as T}from"./onConnect-DI23ecHU.mjs";import{g,h as q,a as v}from"./plugin-CV2Fae9w.mjs";const D=3e4,p=4096,u="presence",b="present",m=`${u}_${b}`,M=g(u,{tables:{[b]:q({data:t.optional(t.record(t.string(),t.any())),lastSeen:t.number(),roomId:t.string(),sessionId:t.string(),userId:t.optional(t.string())}).index("byRoomSession",["roomId","sessionId"]).index("byRoom",["roomId"])}}),{mutation:E,query:A}=R.dataModel().create(),F=(f={})=>{const I=f.ttlMs??D,w=Math.max(0,Math.min(f.disconnectGraceMs??0,I)),y=E.input({data:t.optional(t.record(t.string(),t.any())),roomId:t.string(),sessionId:t.string()}).mutation(async({args:o,ctx:n})=>{const r=Date.now(),i=n.auth.userId??void 0;if(o.data!==void 0&&new TextEncoder().encode(JSON.stringify(o.data)).length>p)throw new S("BAD_REQUEST",`presence data exceeds the ${String(p)}-byte limit`);const e=await n.db.query(m).withIndex("byRoomSession",c=>c.eq("roomId",o.roomId).eq("sessionId",o.sessionId)).first();if(e&&(e.userId??void 0)!==i)throw new S("FORBIDDEN","presence heartbeat denied: this (roomId, sessionId) is held by another identity");const d={lastSeen:r,roomId:o.roomId,sessionId:o.sessionId,...o.data===void 0?{}:{data:o.data},...i===void 0?{}:{userId:i}};return await(e?n.db.patch(e._id,d):n.db.insert(m,d)),{lastSeen:r}}),h=A.input({roomId:t.string()}).query(async({args:o,ctx:n})=>{const r=Date.now()-I,e=(await n.db.query(m).withIndex("byRoom",s=>s.eq("roomId",o.roomId)).collect()).filter(s=>s.lastSeen>r).toSorted((s,a)=>a.lastSeen-s.lastSeen),d=new Set,c=[];for(const s of e){const a=s.userId;if(a!==void 0){if(d.has(a))continue;d.add(a)}const l={lastSeen:s.lastSeen,roomId:s.roomId};a!==void 0&&(l.userId=a),s.data!==void 0&&(l.data=s.data),c.push(l)}return c}),_={...E.input({roomId:t.string()}).mutation(async({args:o,ctx:n})=>{const r=Date.now()-I,i=await n.db.query(m).withIndex("byRoom",e=>e.eq("roomId",o.roomId)).filter(e=>e.lastSeen<=r).collect();return await Promise.all(i.map(e=>n.db.delete(e._id))),{deleted:i.length}}),visibility:"internal"},x=T(async(o,n)=>{const r=n.context?.roomId,i=n.context?.sessionId;if(typeof r!="string"||typeof i!="string")return;const e=await o.db.query(m).withIndex("byRoomSession",s=>s.eq("roomId",r).eq("sessionId",i)).first();if(!e)return;const d=n.userId??void 0;if((e.userId??void 0)!==d)return;if(w===0){await o.db.delete(e._id);return}const c=Math.min(e.lastSeen,Date.now()+w-I);await o.db.patch(e._id,{lastSeen:c})});return v(u,{extension:M,functions:{disconnect:x,heartbeat:y,listPresent:h,sweep:_}})};export{D as PRESENCE_DEFAULT_TTL_MS,m as PRESENCE_TABLE,F as definePresence,M as presenceExtension};
1
+ import{v as t}from"@lunora/values";import{initLunora as R}from"./initLunora-CRypyq1P.mjs";import{LunoraError as S}from"./LunoraError-DcKdk9Ti.mjs";import{onDisconnect as T}from"./onConnect-BLRoOpv2.mjs";import{g,h as q,a as v}from"./plugin-DQcLxTx1.mjs";const D=3e4,p=4096,u="presence",b="present",m=`${u}_${b}`,M=g(u,{tables:{[b]:q({data:t.optional(t.record(t.string(),t.any())),lastSeen:t.number(),roomId:t.string(),sessionId:t.string(),userId:t.optional(t.string())}).index("byRoomSession",["roomId","sessionId"]).index("byRoom",["roomId"])}}),{mutation:E,query:A}=R.dataModel().create(),F=(f={})=>{const I=f.ttlMs??D,w=Math.max(0,Math.min(f.disconnectGraceMs??0,I)),y=E.input({data:t.optional(t.record(t.string(),t.any())),roomId:t.string(),sessionId:t.string()}).mutation(async({args:o,ctx:n})=>{const r=Date.now(),i=n.auth.userId??void 0;if(o.data!==void 0&&new TextEncoder().encode(JSON.stringify(o.data)).length>p)throw new S("BAD_REQUEST",`presence data exceeds the ${String(p)}-byte limit`);const e=await n.db.query(m).withIndex("byRoomSession",c=>c.eq("roomId",o.roomId).eq("sessionId",o.sessionId)).first();if(e&&(e.userId??void 0)!==i)throw new S("FORBIDDEN","presence heartbeat denied: this (roomId, sessionId) is held by another identity");const d={lastSeen:r,roomId:o.roomId,sessionId:o.sessionId,...o.data===void 0?{}:{data:o.data},...i===void 0?{}:{userId:i}};return await(e?n.db.patch(e._id,d):n.db.insert(m,d)),{lastSeen:r}}),h=A.input({roomId:t.string()}).query(async({args:o,ctx:n})=>{const r=Date.now()-I,e=(await n.db.query(m).withIndex("byRoom",s=>s.eq("roomId",o.roomId)).collect()).filter(s=>s.lastSeen>r).toSorted((s,a)=>a.lastSeen-s.lastSeen),d=new Set,c=[];for(const s of e){const a=s.userId;if(a!==void 0){if(d.has(a))continue;d.add(a)}const l={lastSeen:s.lastSeen,roomId:s.roomId};a!==void 0&&(l.userId=a),s.data!==void 0&&(l.data=s.data),c.push(l)}return c}),_={...E.input({roomId:t.string()}).mutation(async({args:o,ctx:n})=>{const r=Date.now()-I,i=await n.db.query(m).withIndex("byRoom",e=>e.eq("roomId",o.roomId)).filter(e=>e.lastSeen<=r).collect();return await Promise.all(i.map(e=>n.db.delete(e._id))),{deleted:i.length}}),visibility:"internal"},x=T(async(o,n)=>{const r=n.context?.roomId,i=n.context?.sessionId;if(typeof r!="string"||typeof i!="string")return;const e=await o.db.query(m).withIndex("byRoomSession",s=>s.eq("roomId",r).eq("sessionId",i)).first();if(!e)return;const d=n.userId??void 0;if((e.userId??void 0)!==d)return;if(w===0){await o.db.delete(e._id);return}const c=Math.min(e.lastSeen,Date.now()+w-I);await o.db.patch(e._id,{lastSeen:c})});return v(u,{extension:M,functions:{disconnect:x,heartbeat:y,listPresent:h,sweep:_}})};export{D as PRESENCE_DEFAULT_TTL_MS,m as PRESENCE_TABLE,F as definePresence,M as presenceExtension};
@@ -1 +1 @@
1
- import"./run-middleware-B-JeGGp9.mjs";import{c as i,a as s,b as o,g as m,k as l,m as t}from"./plugin-CV2Fae9w.mjs";export{i as composePluginMiddleware,s as defineComponent,o as definePlugin,m as defineSchemaExtension,l as installPlugins,t as mergeSchemaExtension};
1
+ import"./run-middleware-B-JeGGp9.mjs";import{c as i,a as s,b as o,g as m,k as l,m as t}from"./plugin-DQcLxTx1.mjs";export{i as composePluginMiddleware,s as defineComponent,o as definePlugin,m as defineSchemaExtension,l as installPlugins,t as mergeSchemaExtension};
@@ -1 +1 @@
1
- import{d,e as n,f as i,h as s,i as f,j as x,v as r}from"./plugin-CV2Fae9w.mjs";export{d as defineAggregateIndex,n as defineRankIndex,i as defineSchema,s as defineTable,f as defineVectorIndex,x as indexFieldsFromSchema,r as validateIndexFields};
1
+ import{d,e as n,f as i,h as s,i as f,j as x,v as r}from"./plugin-DQcLxTx1.mjs";export{d as defineAggregateIndex,n as defineRankIndex,i as defineSchema,s as defineTable,f as defineVectorIndex,x as indexFieldsFromSchema,r as validateIndexFields};
@@ -0,0 +1 @@
1
+ const t=(n,c)=>({args:{},handler:(i,o)=>c(i,o),kind:"mutation",lifecycle:n,visibility:"internal"}),e=n=>t("connect",n),r=n=>t("disconnect",n),s=n=>t("init",n);export{e as onConnect,r as onDisconnect,s as onShardInit};
@@ -0,0 +1 @@
1
+ const c=(t,e)=>{let r=e;for(let i=0;i<t.length;i+=1)r^=t.codePointAt(i)??0,r=Math.imul(r,16777619)>>>0;return r.toString(16).padStart(8,"0")},f=t=>`${c(t,2166136261)}${c(t,16777619)}`,y=(t,e)=>t<e?-1:t>e?1:0,s=t=>{if(t===void 0)return"null";if(typeof t=="bigint")throw new TypeError("stableStringify: cannot use a bigint in a stable JSON cache key — pass it as a string, or use stableWireKey");if(typeof t=="number"){if(Number.isNaN(t))return"nan";if(t===1/0)return"inf";if(t===-1/0)return"-inf";if(Object.is(t,-0))return"-0"}if(t===null||typeof t!="object")return JSON.stringify(t);if(Array.isArray(t))return`[${t.map(n=>s(n)).join(",")}]`;const e=Object.getPrototypeOf(t);if(e!==null&&e!==Object.prototype){const n=t.constructor?.name??"value";throw new TypeError(`stableStringify: cannot use a ${n} in a stable JSON cache key — only plain objects, arrays, and JSON primitives are supported (wire-typed values key via stableWireKey)`)}const r=t,i=Object.keys(r).toSorted(y),o=[];for(const n of i){const a=r[n];a!==void 0&&o.push(`${JSON.stringify(n)}:${s(a)}`)}return`{${o.join(",")}}`},p=(t,e)=>({args:{},handler:async(r,i)=>{const o=await t(r),n=f(s(o));return n===i?.previousDigest?{digest:n,ran:!1}:(await e(r,o),{digest:n,ran:!0})},kind:"mutation",lifecycle:"reactor",visibility:"internal"});export{p as onQueryChange};
@@ -0,0 +1 @@
1
+ import{LunoraError as s}from"@lunora/errors";import{r as P}from"./run-middleware-B-JeGGp9.mjs";import{v as O}from"@lunora/values";const j=["de","en","es","fr","it","nl","none","pt"],F=["native","portable"],K=e=>F.includes(e),z=e=>j.includes(e),B=16,H={many:(e,t)=>({field:t.field,kind:"many",references:t.references??"_id",table:e}),one:(e,t)=>({field:t.field,kind:"one",onDelete:t.onDelete,references:t.references??"_id",table:e})},g=(e,t,n)=>({handler:n,op:t,timing:e}),Q=()=>({afterDelete:e=>g("after","delete",e),afterInsert:e=>g("after","insert",e),afterUpdate:e=>g("after","update",e),beforeDelete:e=>g("before","delete",e),beforeInsert:e=>g("before","insert",e),beforeUpdate:e=>g("before","update",e)}),_=(e,t)=>{if(!t||t.length===0)throw new s("INTERNAL",`rankIndex "${e}": "sortBy" is required and must list at least one key`);return t.map(n=>({direction:n.direction??"asc",field:n.field}))},se=e=>{const t={...e},n=[],r=[],o=[],d=[],u={},l=[],f={},m=Q(),b=[];let y={kind:"root"},$=!1,N=!1,k=!1,T=!1,L,M,A,R;const c={aggregateIndex(i,a){const h=a?.op??"count";if(h!=="count"&&!a?.field)throw new s("INTERNAL",`aggregateIndex "${i}": op "${h}" requires a "field"`);return n.push({by:a?.by,field:a?.field,name:i,on:"",op:h,where:a?.where}),c},get aggregateIndexes(){return n},get externalSource(){return R},commitOrdered(){return $=!0,c},get commitOrderedMode(){return $},externallyManaged(){return N=!0,c},geoIndex(i,a){return r.push({field:a.field,name:i,precision:a.precision}),c},get geoIndexes(){return r},global(i){return y={backend:i?.backend??"d1",kind:"global"},c},get isExternallyManaged(){return N},get isPublic(){return T},index(i,a,h){return o.push({fields:a,name:i,unique:h?.unique??!1}),c},get indexes(){return o},memory(){return k=!0,c},get memoryMode(){return k},ownedBy(i){return L=i,c},get ownerField(){return L},public(){return T=!0,c},rankIndex(i,a){return d.push({name:i,on:"",partitionBy:a.partitionBy,sortBy:_(i,a.sortBy),where:a.where}),c},get rankIndexes(){return d},get relationMap(){return u},relations(i){return Object.assign(u,i(H)),c},searchIndex(i,a){if(a.filterFields&&a.filterFields.length>B)throw new s("INTERNAL",`searchIndex "${i}": at most ${String(B)} filterFields are supported (got ${String(a.filterFields.length)})`);const{language:h}=a;if(h!==void 0&&!z(h))throw new s("INTERNAL",`searchIndex "${i}": unknown language "${h}" (supported: ${j.join(", ")})`);const{strategy:v}=a;if(v!==void 0&&!K(v))throw new s("INTERNAL",`searchIndex "${i}": unknown strategy "${v}" (supported: ${F.join(", ")})`);return l.push({field:a.field,filterFields:a.filterFields,language:a.language,name:i,staged:a.staged,strategy:a.strategy}),c},get searchIndexes(){return l},shape:t,shardBy(i){return y={field:i,kind:"shardBy"},c},get shardMode(){return y},get softDeleteMode(){return M},source(i){if(!i.binding)throw new s("INTERNAL","source: `binding` is required (the wrangler Hyperdrive binding name)");if(!i.query)throw new s("INTERNAL","source: `query` is required (the tenant-membership SQL)");return R=i,N=!0,c},softDelete(i){const a=i?.field??"deletedAt";return M={field:a},a in t||(t[a]=O.optional(O.number().nullable())),c},get triggerMap(){return f},triggers(i){return Object.assign(f,i(m)),c},ttl(i,a){return A={after:a?.after,field:i},c},get ttlPolicy(){return A},get vectorIndexes(){return b},vectorize(i,a){return b.push({dimensions:a.dimensions,embed:a.embed,field:i,metadata:a.metadata,metric:a.metric,name:a.index}),c}};return c},de=e=>({dimensions:e.dimensions,embed:e.embed,kind:"vectorIndex",metadata:e.metadata,metric:e.metric,select:e.source.select,table:e.source.table}),le=(e,t)=>{const n=t.op??"count";if(n!=="count"&&!t.field)throw new s("INTERNAL",`aggregateIndex "${e}": op "${n}" requires a "field"`);return{by:t.by,field:t.field,name:e,on:t.on,op:n,where:t.where}},ce=(e,t)=>({name:e,on:t.table,partitionBy:t.partitionBy,sortBy:_(e,t.sortBy),where:t.where}),x=e=>({...e,extend(t){return x(C(e,t))},jurisdiction(t){return x(e)},rls(t){return x({...e,rlsMode:t})}}),X=e=>{for(const[t,n]of Object.entries(e))for(const r of[...n.aggregateIndexes,...n.rankIndexes])r.on===""&&(r.on=t)},U=(e,t,n)=>{for(const r of Object.values(t)){const o=e[r.on];if(!o)throw new s("INTERNAL",`defineAggregateIndex "${r.name}": unknown table "${r.on}"`);o.aggregateIndexes.push(r)}for(const r of Object.values(n)){const o=e[r.on];if(!o)throw new s("INTERNAL",`defineRankIndex "${r.name}": unknown table "${r.on}"`);o.rankIndexes.push(r)}},V=e=>{if(e.cursor)return"cursor";if(e.reconcileEveryMs!==void 0)return"reconcileEveryMs";if(e.softDeleteColumn!==void 0)return"softDeleteColumn"},Y=(e,t)=>{const n=t.mode==="incremental";if(t.mode!==void 0&&t.mode!=="full-pull"&&!n)throw new s("INTERNAL",`defineSchema: table "${e}" uses \`mode: ${JSON.stringify(t.mode)}\` — supported modes are "full-pull" (default) and "incremental".`);if(!n){const r=V(t);if(r)throw new s("INTERNAL",`defineSchema: table "${e}" sets \`${r}\` but is not \`mode: "incremental"\` — that knob only applies to incremental ingest. Set \`mode: "incremental"\` or remove \`${r}\`.`);return}if(!t.cursor?.column||!t.cursor.query)throw new s("INTERNAL",`defineSchema: table "${e}" is \`mode: "incremental"\` but has no \`cursor\` — add \`cursor: { column: "updated_at", query: "… WHERE … > $N" }\` binding the watermark as the trailing parameter.`);if(t.reconcileEveryMs===void 0&&t.softDeleteColumn===void 0)throw new s("INTERNAL",`defineSchema: table "${e}" is \`mode: "incremental"\` with no delete-visibility path — an incremental pull never sees upstream deletes, so it would accumulate phantom rows. Add \`reconcileEveryMs\` (a periodic full-pull sweep) or \`softDeleteColumn\` (an upstream tombstone column).`)},J=e=>{for(const[t,n]of Object.entries(e)){const r=n.externalSource;if(r){if(n.shardMode.kind==="global")throw new s("INTERNAL",`defineSchema: table "${t}" cannot be both .source() and .global() — a sourced table materializes into a shard DO's SQLite, a global table lives in the external tier`);if(n.shardMode.kind==="shardBy"&&!r.tenantBy)throw new s("INTERNAL",`defineSchema: sourced + .shardBy() table "${t}" needs a \`tenantBy\` mapper — without it every tenant's DO would run the same unscoped query and replicate the whole multitenant table (a cross-tenant leak). Add \`tenantBy: (shardKey) => [shardKey]\` binding the shard key into the query's parameters.`);Y(t,r)}}},W=["_commitSeq","_creationTime","_id"],D="_commitSeq",G=new Set(W),w=(e,t,n,r)=>{if(!(G.has(n)||n in r.shape))throw new s("INTERNAL",`defineSchema: table "${e}" index "${t}" names column "${n}" which is not in the table's shape`)},p=(e,t,n,r)=>{const o=new Set;for(const d of n){if(o.has(d.name))throw new s("INTERNAL",`defineSchema: table "${e}" declares ${t} "${d.name}" more than once`);o.add(d.name),r(d)}},q=e=>{for(const[t,n]of Object.entries(e))p(t,"index",n.indexes,r=>{for(const o of r.fields)w(t,r.name,o,n)}),p(t,"rank index",n.rankIndexes,r=>{for(const o of r.sortBy)w(t,r.name,o.field,n);for(const o of r.partitionBy??[])w(t,r.name,o,n)}),p(t,"geo index",n.geoIndexes,r=>{w(t,r.name,r.field,n)})},E=(e,t,n)=>{if(e.length===0)return;const r={};for(const o of e)r[t(o)]=n(o);return r},Z=e=>{const t=new Set(e.sortBy.map(n=>n.field));for(const n of e.partitionBy??[])t.add(n);return[...t]},ue=e=>{const t={};for(const[n,r]of Object.entries(e.tables)){const o=E(r.geoIndexes,l=>l.name,l=>[l.field]),d=E(r.indexes,l=>l.name,l=>l.fields),u=E(r.rankIndexes,l=>l.name,Z);(o||d||u)&&(t[n]={...o?{geo:o}:{},...d?{index:d}:{},...u?{rank:u}:{}})}return t},ee=e=>{for(const[t,n]of Object.entries(e)){if(!n.memoryMode)continue;const o=[[n.shardMode.kind==="global",".global() — those rows live in D1, which this shard does not own and cannot clear"],[n.commitOrderedMode===!0,".commitOrdered() — the counter resets with the table, so the sequence would restart and a consumer's cursor would silently skip everything after it"],[n.externalSource!==void 0,".source() — the table is materialized by the ingest loop, so wiping it on every eviction would fight the puller"],[n.searchIndexes.length>0,".searchIndex() — the FTS shadow is a separate table that clearing the rows would leave stale"],[n.geoIndexes.length>0,".geoIndex() — the geohash companion is a separate table that clearing the rows would leave stale"],[n.aggregateIndexes.length>0,".aggregateIndex() — the counter companion is a separate table that clearing the rows would leave stale"],[n.rankIndexes.length>0,".rankIndex() — the rank companion is a separate table that clearing the rows would leave stale"],[n.vectorIndexes.length>0,".vectorize() — vectors live in an external index that a shard restart cannot clear"]].find(([d])=>d)?.[1];if(o!==void 0)throw new s("INTERNAL",`defineSchema: table "${t}" is both .memory() and ${o}. Plain .index() is supported; SQLite maintains it through the DELETE.`)}},te=e=>{for(const[t,n]of Object.entries(e)){if(n.commitOrderedMode){if(n.shardMode.kind==="global")throw new s("INTERNAL",`defineSchema: table "${t}" is both .global() and .commitOrdered(). \`_commitSeq\` is allocated inside the shard's write transaction, which a global (D1/Hyperdrive) table does not share — so the stamp would carry no commit-ordering guarantee. Drop one of the two.`);continue}for(const r of n.indexes)if(r.fields.includes(D))throw new s("INTERNAL",`defineSchema: table "${t}" index "${r.name}" names "${D}", but the table is not .commitOrdered() — no row carries that field, so the index would never match. Add .commitOrdered(), or drop the column from the index.`)}},fe=(e,t={},n={},r={})=>(X(e),U(e,n,r),J(e),te(e),ee(e),q(e),x({tables:e,vectorIndexes:t})),I=(e,t)=>`${e}_${t}`,S=(e,t,n)=>n.has(e)?I(t,e):e,ne=(e,t,n,r)=>{const o=I(t,r),d=m=>m===""?o:S(m,t,n),u={};for(const[m,b]of Object.entries(e.relationMap))u[m]={...b,table:S(b.table,t,n)};const l=e.aggregateIndexes.map(m=>({...m,on:d(m.on)})),f=e.rankIndexes.map(m=>({...m,on:d(m.on)}));return{...e,aggregateIndexes:l,rankIndexes:f,relationMap:u}},me=(e,t)=>{if(!e)throw new s("INTERNAL","defineSchemaExtension: `key` is required and must be a non-empty string");return{key:e,tables:t.tables,...t.vectorIndexes?{vectorIndexes:t.vectorIndexes}:{}}},re=((e,t)=>{if(!e)throw new s("INTERNAL","definePlugin: `key` is required and must be a non-empty string");if(t.extension&&t.extension.key!==e)throw new s("INTERNAL",`definePlugin("${e}"): extension key "${t.extension.key}" does not match plugin key`);return{key:e,...t.extension?{extension:t.extension}:{},...t.middleware?{middleware:t.middleware}:{}}}),he=(e,t)=>({...re(e,{...t.extension?{extension:t.extension}:{},...t.middleware?{middleware:t.middleware}:{}}),functions:t.functions??{}}),C=(e,t)=>{const{key:n}=t,r=new Set(Object.keys(t.tables)),o={...e.tables};for(const[u,l]of Object.entries(t.tables)){const f=I(n,u);if(Object.hasOwn(o,f))throw new s("INTERNAL",`defineSchema(...).extend("${n}"): table "${f}" already exists in the base schema — another extension with the same key already contributed it`);o[f]=ne(l,n,r,u)}const d={...e.vectorIndexes};if(t.vectorIndexes)for(const[u,l]of Object.entries(t.vectorIndexes)){const f=I(n,u);if(Object.hasOwn(d,f))throw new s("INTERNAL",`defineSchema(...).extend("${n}"): vector index "${f}" already exists in the base schema — another extension with the same key already contributed it`);d[f]={...l,table:S(l.table,n,r)}}return q(o),{rlsMode:e.rlsMode,tables:o,vectorIndexes:d}},ge=(e,t)=>{let n=e;for(const r of t)r.extension&&(n=C(n,r.extension));return n},be=e=>{const t=e.map(n=>n.middleware).filter(n=>n!==void 0);return(async({ctx:n,next:r})=>P(t,n,o=>r({ctx:o})))};export{he as a,re as b,be as c,le as d,ce as e,fe as f,me as g,se as h,de as i,ue as j,ge as k,C as m,q as v};
package/dist/types.d.mts CHANGED
@@ -352,6 +352,15 @@ interface TableDefinition<Shape extends Record<string, Validator> = Record<strin
352
352
  * without scanning the underlying table.
353
353
  */
354
354
  aggregateIndexes: ReadonlyArray<AggregateIndexDefinition>;
355
+ /**
356
+ * Set by `.commitOrdered()` (named `commitOrderedMode`, not `commitOrdered`,
357
+ * so the data field doesn't collide with the fluent `.commitOrdered()`
358
+ * builder method — same convention as `shardBy()`/`shardMode`). When `true`,
359
+ * every write to a row stamps `_commitSeq`: a per-shard integer allocated
360
+ * once per mutation and strictly increasing in commit order. Absent/`false`
361
+ * ⇒ rows carry no `_commitSeq`, as before.
362
+ */
363
+ commitOrderedMode?: boolean;
355
364
  /**
356
365
  * Set by `.source(...)` (named `externalSource`, not `source`, so the data
357
366
  * field doesn't collide with the fluent `.source()` builder method — same
@@ -385,6 +394,14 @@ interface TableDefinition<Shape extends Record<string, Validator> = Record<strin
385
394
  * Has no effect when the schema does not require RLS.
386
395
  */
387
396
  isPublic?: boolean;
397
+ /**
398
+ * Set by `.memory()` (named `memoryMode`, not `memory`, so the data field
399
+ * doesn't collide with the fluent `.memory()` builder method — same
400
+ * convention as `shardBy()`/`shardMode`). When `true`, the table's rows are
401
+ * cleared every time the Durable Object is reconstructed, and its writes
402
+ * never reach the CDC changelog. Absent/`false` ⇒ an ordinary durable table.
403
+ */
404
+ memoryMode?: boolean;
388
405
  /**
389
406
  * Set by `.ownedBy(field)` — the column holding the owning user's id (named
390
407
  * `ownerField`, a data field, rather than colliding with the fluent
@@ -597,9 +614,54 @@ interface FunctionHandle<Kind extends "action" | "mutation" | "query" | "stream"
597
614
  * `RegisteredFunction<ArgsValidator, …>` constraint, because `handler`'s args
598
615
  * are in a contravariant position. Two inference sites it is.
599
616
  */
617
+ /**
618
+ * Options for `ctx.runQuery`.
619
+ *
620
+ * Lunora's answer to Convex's `useStaleSnapshot`, and deliberately NOT a port of
621
+ * it. Convex needs that flag because a mutation there validates its whole read
622
+ * set at commit, so merely *reading* a hot append-only table manufactures OCC
623
+ * conflicts. Lunora has no such conflict class: its OCC is a compare-and-swap on
624
+ * the `__doc__` of each row a mutation actually WRITES (see `runGuardedWrite`),
625
+ * so a read costs a mutation nothing and there is nothing for a stale snapshot
626
+ * to relieve.
627
+ *
628
+ * What a read does cost here is REACTIVITY. Every table a live query touches
629
+ * enters its read footprint, and every write to any of those tables re-runs the
630
+ * subscription. A query that reads one hot table — an append-only audit log, a
631
+ * counter, a feed — therefore re-runs on every append even when the append
632
+ * cannot change its result. That is Lunora's version of the pressure Convex is
633
+ * relieving, and {@link RunQueryOptions.untracked} is the release valve.
634
+ */
635
+ interface RunQueryOptions {
636
+ /**
637
+ * Run the query without recording its reads in the CALLER's read footprint.
638
+ * The subscription therefore does not re-run when the tables that query
639
+ * touched change.
640
+ *
641
+ * The result is exactly as fresh as a tracked call — same SQLite, same
642
+ * instant, no snapshot involved. The only thing given up is the invalidation
643
+ * edge, so use it when the read genuinely should not wake the subscriber
644
+ * (a monotonic counter rendered once, a config row, an audit tail whose
645
+ * growth is irrelevant to the result) — and never for data whose change
646
+ * should reach the client, which is what makes the subscription stale
647
+ * indefinitely rather than merely late.
648
+ *
649
+ * Scoped to the sub-query and nothing else: the untracked call runs on its
650
+ * own context, so a read interleaved on the caller's `ctx.db` while it is in
651
+ * flight still tracks normally. That is why this is an option on
652
+ * `ctx.runQuery` rather than a `ctx.db.untracked(...)` scope — a
653
+ * writer-wide flag would silently swallow a concurrent read's dependency
654
+ * (compare `meterExempt`'s documented interleaving caveat, which is
655
+ * tolerable for a resource meter and would not be here).
656
+ *
657
+ * No effect outside a subscription: a one-shot query records no footprint,
658
+ * so there is nothing to opt out of.
659
+ */
660
+ untracked?: boolean;
661
+ }
600
662
  interface RunQuery {
601
- <A extends ArgsValidator, R>(reference: RegisteredQuery<A, R>, args: InferArgs<A>): Promise<R>;
602
- <Args, R>(reference: FunctionHandle<"query", Args, R>, args: Args): Promise<R>;
663
+ <A extends ArgsValidator, R>(reference: RegisteredQuery<A, R>, args: InferArgs<A>, options?: RunQueryOptions): Promise<R>;
664
+ <Args, R>(reference: FunctionHandle<"query", Args, R>, args: Args, options?: RunQueryOptions): Promise<R>;
603
665
  }
604
666
  /** `ctx.runMutation` — overloaded for the same reason as {@link RunQuery}. */
605
667
  interface RunMutation {
@@ -612,7 +674,15 @@ interface RunAction {
612
674
  <Args, R>(reference: FunctionHandle<"action", Args, R>, args: Args): Promise<R>;
613
675
  }
614
676
  /** Which side of the WebSocket lifecycle a hook fires on. */
615
- type LifecycleEventKind = "connect" | "disconnect";
677
+ /**
678
+ * Which lifecycle moment a hook fires on.
679
+ *
680
+ * `connect`/`disconnect` are per-SOCKET and fire many times over a shard's life.
681
+ * `init` is per-INSTANCE and fires once per cold start, before any handler runs
682
+ * — see {@link ShardInitEvent}. `reactor` is per-WRITE-FLUSH and fires only when
683
+ * a watched read's result changed — see `onQueryChange`.
684
+ */
685
+ type LifecycleEventKind = "connect" | "disconnect" | "init" | "reactor";
616
686
  /**
617
687
  * The event a connection-lifecycle hook receives as its second argument. It is
618
688
  * the JSON-serializable payload the DO forwards on socket connect/disconnect;
@@ -631,11 +701,27 @@ interface LifecycleEvent {
631
701
  }
632
702
  /**
633
703
  * A registered connection-lifecycle hook — an internal mutation tagged with the
634
- * lifecycle side it fires on. Produced by `onConnect` / `onDisconnect`.
704
+ * lifecycle side it fires on. Produced by `onConnect` / `onDisconnect` /
705
+ * `onShardInit`.
635
706
  */
636
707
  type RegisteredLifecycleHook = RegisteredFunction<Record<string, never>, void, "mutation"> & {
637
708
  readonly lifecycle: LifecycleEventKind;
638
709
  };
710
+ /**
711
+ * The event an `onShardInit` hook receives.
712
+ *
713
+ * Deliberately thin, and deliberately NOT a {@link LifecycleEvent}: there is no
714
+ * socket here. An init hook fires because the Durable Object was constructed,
715
+ * not because anyone connected — so there is no `connectionId` to report and no
716
+ * user to attribute it to. The hook runs as a trusted system dispatch with no
717
+ * request identity, exactly like a cron tick; `ctx.auth` is anonymous and RLS
718
+ * does not apply. Derive whatever you need from `shardKey` and the shard's own
719
+ * durable tables, never from an ambient caller — there isn't one.
720
+ */
721
+ interface ShardInitEvent {
722
+ /** The shard this Durable Object instance serves. */
723
+ readonly shardKey: string;
724
+ }
639
725
  /**
640
726
  * A streaming query registration. Unlike {@link RegisteredFunction} the handler
641
727
  * returns an `AsyncIterable<R>` synchronously (it does NOT `Promise<R>`); the
@@ -2114,4 +2200,4 @@ interface ActionCtx {
2114
2200
  */
2115
2201
  type AnyApi = Record<string, Record<string, RegisteredFunction<ArgsValidator, unknown, FunctionKind>>>;
2116
2202
  declare const anyApi: AnyApi;
2117
- export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type CachePurge, type DatabaseReader, type DatabaseWriter, type DurableObjectJurisdiction, type DurableStreamOptions, type ExposeConfig, type ExternalSourceCursor, type ExternalSourceDefinition, type ExternalSourceMode, type ExternalSourceRefresh, type FunctionKind, type FunctionVisibility, type GeoBoundingBox, type GeoFilterBuilder, type GeoIndexDefinition, type GeoPointInput, type GlobalBackend, type IndexDefinition, type IndexRangeBuilder, type InferArgs, type LifecycleEvent, type LifecycleEventKind, type LogFields, type LunoraLogMethod, type LunoraLogger, type LunoraMetrics, type LunoraTracer, type LunoraWideEvent, type MutationCtx, type OnDeleteAction, type PaginationOptions, type PaginationResult, type QueryCtx, type RankIndexDefinition, type RankSortKey, type ReadOnlyStorage, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationDefinition, type RestCacheConfig, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type Schema, type SearchFilterBuilder, type SearchIndexDefinition, type SearchLanguage, type SearchStrategy, type Secrets, type SecretsStoreSecretLike, type ShardMode, type SpanEvaluation, type SpanHandle, type SpanKind, type SpanLink, type SpanOptions, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableDefinition, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerBuilder, type TriggerCtx, type TriggerDatabase, type TriggerDefinition, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type TtlDefinition, type VectorEmbedder, type VectorIndexDefinition, type VectorMatch, type VectorMatches, type VectorMetric, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowEventDefinition, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, type X402ProcedureConfig, anyApi };
2203
+ export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type CachePurge, type DatabaseReader, type DatabaseWriter, type DurableObjectJurisdiction, type DurableStreamOptions, type ExposeConfig, type ExternalSourceCursor, type ExternalSourceDefinition, type ExternalSourceMode, type ExternalSourceRefresh, type FunctionKind, type FunctionVisibility, type GeoBoundingBox, type GeoFilterBuilder, type GeoIndexDefinition, type GeoPointInput, type GlobalBackend, type IndexDefinition, type IndexRangeBuilder, type InferArgs, type LifecycleEvent, type LifecycleEventKind, type LogFields, type LunoraLogMethod, type LunoraLogger, type LunoraMetrics, type LunoraTracer, type LunoraWideEvent, type MutationCtx, type OnDeleteAction, type PaginationOptions, type PaginationResult, type QueryCtx, type RankIndexDefinition, type RankSortKey, type ReadOnlyStorage, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationDefinition, type RestCacheConfig, type RunQueryOptions, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type Schema, type SearchFilterBuilder, type SearchIndexDefinition, type SearchLanguage, type SearchStrategy, type Secrets, type SecretsStoreSecretLike, type ShardInitEvent, type ShardMode, type SpanEvaluation, type SpanHandle, type SpanKind, type SpanLink, type SpanOptions, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableDefinition, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerBuilder, type TriggerCtx, type TriggerDatabase, type TriggerDefinition, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type TtlDefinition, type VectorEmbedder, type VectorIndexDefinition, type VectorMatch, type VectorMatches, type VectorMetric, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowEventDefinition, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, type X402ProcedureConfig, anyApi };
package/dist/types.d.ts CHANGED
@@ -352,6 +352,15 @@ interface TableDefinition<Shape extends Record<string, Validator> = Record<strin
352
352
  * without scanning the underlying table.
353
353
  */
354
354
  aggregateIndexes: ReadonlyArray<AggregateIndexDefinition>;
355
+ /**
356
+ * Set by `.commitOrdered()` (named `commitOrderedMode`, not `commitOrdered`,
357
+ * so the data field doesn't collide with the fluent `.commitOrdered()`
358
+ * builder method — same convention as `shardBy()`/`shardMode`). When `true`,
359
+ * every write to a row stamps `_commitSeq`: a per-shard integer allocated
360
+ * once per mutation and strictly increasing in commit order. Absent/`false`
361
+ * ⇒ rows carry no `_commitSeq`, as before.
362
+ */
363
+ commitOrderedMode?: boolean;
355
364
  /**
356
365
  * Set by `.source(...)` (named `externalSource`, not `source`, so the data
357
366
  * field doesn't collide with the fluent `.source()` builder method — same
@@ -385,6 +394,14 @@ interface TableDefinition<Shape extends Record<string, Validator> = Record<strin
385
394
  * Has no effect when the schema does not require RLS.
386
395
  */
387
396
  isPublic?: boolean;
397
+ /**
398
+ * Set by `.memory()` (named `memoryMode`, not `memory`, so the data field
399
+ * doesn't collide with the fluent `.memory()` builder method — same
400
+ * convention as `shardBy()`/`shardMode`). When `true`, the table's rows are
401
+ * cleared every time the Durable Object is reconstructed, and its writes
402
+ * never reach the CDC changelog. Absent/`false` ⇒ an ordinary durable table.
403
+ */
404
+ memoryMode?: boolean;
388
405
  /**
389
406
  * Set by `.ownedBy(field)` — the column holding the owning user's id (named
390
407
  * `ownerField`, a data field, rather than colliding with the fluent
@@ -597,9 +614,54 @@ interface FunctionHandle<Kind extends "action" | "mutation" | "query" | "stream"
597
614
  * `RegisteredFunction<ArgsValidator, …>` constraint, because `handler`'s args
598
615
  * are in a contravariant position. Two inference sites it is.
599
616
  */
617
+ /**
618
+ * Options for `ctx.runQuery`.
619
+ *
620
+ * Lunora's answer to Convex's `useStaleSnapshot`, and deliberately NOT a port of
621
+ * it. Convex needs that flag because a mutation there validates its whole read
622
+ * set at commit, so merely *reading* a hot append-only table manufactures OCC
623
+ * conflicts. Lunora has no such conflict class: its OCC is a compare-and-swap on
624
+ * the `__doc__` of each row a mutation actually WRITES (see `runGuardedWrite`),
625
+ * so a read costs a mutation nothing and there is nothing for a stale snapshot
626
+ * to relieve.
627
+ *
628
+ * What a read does cost here is REACTIVITY. Every table a live query touches
629
+ * enters its read footprint, and every write to any of those tables re-runs the
630
+ * subscription. A query that reads one hot table — an append-only audit log, a
631
+ * counter, a feed — therefore re-runs on every append even when the append
632
+ * cannot change its result. That is Lunora's version of the pressure Convex is
633
+ * relieving, and {@link RunQueryOptions.untracked} is the release valve.
634
+ */
635
+ interface RunQueryOptions {
636
+ /**
637
+ * Run the query without recording its reads in the CALLER's read footprint.
638
+ * The subscription therefore does not re-run when the tables that query
639
+ * touched change.
640
+ *
641
+ * The result is exactly as fresh as a tracked call — same SQLite, same
642
+ * instant, no snapshot involved. The only thing given up is the invalidation
643
+ * edge, so use it when the read genuinely should not wake the subscriber
644
+ * (a monotonic counter rendered once, a config row, an audit tail whose
645
+ * growth is irrelevant to the result) — and never for data whose change
646
+ * should reach the client, which is what makes the subscription stale
647
+ * indefinitely rather than merely late.
648
+ *
649
+ * Scoped to the sub-query and nothing else: the untracked call runs on its
650
+ * own context, so a read interleaved on the caller's `ctx.db` while it is in
651
+ * flight still tracks normally. That is why this is an option on
652
+ * `ctx.runQuery` rather than a `ctx.db.untracked(...)` scope — a
653
+ * writer-wide flag would silently swallow a concurrent read's dependency
654
+ * (compare `meterExempt`'s documented interleaving caveat, which is
655
+ * tolerable for a resource meter and would not be here).
656
+ *
657
+ * No effect outside a subscription: a one-shot query records no footprint,
658
+ * so there is nothing to opt out of.
659
+ */
660
+ untracked?: boolean;
661
+ }
600
662
  interface RunQuery {
601
- <A extends ArgsValidator, R>(reference: RegisteredQuery<A, R>, args: InferArgs<A>): Promise<R>;
602
- <Args, R>(reference: FunctionHandle<"query", Args, R>, args: Args): Promise<R>;
663
+ <A extends ArgsValidator, R>(reference: RegisteredQuery<A, R>, args: InferArgs<A>, options?: RunQueryOptions): Promise<R>;
664
+ <Args, R>(reference: FunctionHandle<"query", Args, R>, args: Args, options?: RunQueryOptions): Promise<R>;
603
665
  }
604
666
  /** `ctx.runMutation` — overloaded for the same reason as {@link RunQuery}. */
605
667
  interface RunMutation {
@@ -612,7 +674,15 @@ interface RunAction {
612
674
  <Args, R>(reference: FunctionHandle<"action", Args, R>, args: Args): Promise<R>;
613
675
  }
614
676
  /** Which side of the WebSocket lifecycle a hook fires on. */
615
- type LifecycleEventKind = "connect" | "disconnect";
677
+ /**
678
+ * Which lifecycle moment a hook fires on.
679
+ *
680
+ * `connect`/`disconnect` are per-SOCKET and fire many times over a shard's life.
681
+ * `init` is per-INSTANCE and fires once per cold start, before any handler runs
682
+ * — see {@link ShardInitEvent}. `reactor` is per-WRITE-FLUSH and fires only when
683
+ * a watched read's result changed — see `onQueryChange`.
684
+ */
685
+ type LifecycleEventKind = "connect" | "disconnect" | "init" | "reactor";
616
686
  /**
617
687
  * The event a connection-lifecycle hook receives as its second argument. It is
618
688
  * the JSON-serializable payload the DO forwards on socket connect/disconnect;
@@ -631,11 +701,27 @@ interface LifecycleEvent {
631
701
  }
632
702
  /**
633
703
  * A registered connection-lifecycle hook — an internal mutation tagged with the
634
- * lifecycle side it fires on. Produced by `onConnect` / `onDisconnect`.
704
+ * lifecycle side it fires on. Produced by `onConnect` / `onDisconnect` /
705
+ * `onShardInit`.
635
706
  */
636
707
  type RegisteredLifecycleHook = RegisteredFunction<Record<string, never>, void, "mutation"> & {
637
708
  readonly lifecycle: LifecycleEventKind;
638
709
  };
710
+ /**
711
+ * The event an `onShardInit` hook receives.
712
+ *
713
+ * Deliberately thin, and deliberately NOT a {@link LifecycleEvent}: there is no
714
+ * socket here. An init hook fires because the Durable Object was constructed,
715
+ * not because anyone connected — so there is no `connectionId` to report and no
716
+ * user to attribute it to. The hook runs as a trusted system dispatch with no
717
+ * request identity, exactly like a cron tick; `ctx.auth` is anonymous and RLS
718
+ * does not apply. Derive whatever you need from `shardKey` and the shard's own
719
+ * durable tables, never from an ambient caller — there isn't one.
720
+ */
721
+ interface ShardInitEvent {
722
+ /** The shard this Durable Object instance serves. */
723
+ readonly shardKey: string;
724
+ }
639
725
  /**
640
726
  * A streaming query registration. Unlike {@link RegisteredFunction} the handler
641
727
  * returns an `AsyncIterable<R>` synchronously (it does NOT `Promise<R>`); the
@@ -2114,4 +2200,4 @@ interface ActionCtx {
2114
2200
  */
2115
2201
  type AnyApi = Record<string, Record<string, RegisteredFunction<ArgsValidator, unknown, FunctionKind>>>;
2116
2202
  declare const anyApi: AnyApi;
2117
- export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type CachePurge, type DatabaseReader, type DatabaseWriter, type DurableObjectJurisdiction, type DurableStreamOptions, type ExposeConfig, type ExternalSourceCursor, type ExternalSourceDefinition, type ExternalSourceMode, type ExternalSourceRefresh, type FunctionKind, type FunctionVisibility, type GeoBoundingBox, type GeoFilterBuilder, type GeoIndexDefinition, type GeoPointInput, type GlobalBackend, type IndexDefinition, type IndexRangeBuilder, type InferArgs, type LifecycleEvent, type LifecycleEventKind, type LogFields, type LunoraLogMethod, type LunoraLogger, type LunoraMetrics, type LunoraTracer, type LunoraWideEvent, type MutationCtx, type OnDeleteAction, type PaginationOptions, type PaginationResult, type QueryCtx, type RankIndexDefinition, type RankSortKey, type ReadOnlyStorage, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationDefinition, type RestCacheConfig, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type Schema, type SearchFilterBuilder, type SearchIndexDefinition, type SearchLanguage, type SearchStrategy, type Secrets, type SecretsStoreSecretLike, type ShardMode, type SpanEvaluation, type SpanHandle, type SpanKind, type SpanLink, type SpanOptions, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableDefinition, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerBuilder, type TriggerCtx, type TriggerDatabase, type TriggerDefinition, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type TtlDefinition, type VectorEmbedder, type VectorIndexDefinition, type VectorMatch, type VectorMatches, type VectorMetric, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowEventDefinition, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, type X402ProcedureConfig, anyApi };
2203
+ export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type CachePurge, type DatabaseReader, type DatabaseWriter, type DurableObjectJurisdiction, type DurableStreamOptions, type ExposeConfig, type ExternalSourceCursor, type ExternalSourceDefinition, type ExternalSourceMode, type ExternalSourceRefresh, type FunctionKind, type FunctionVisibility, type GeoBoundingBox, type GeoFilterBuilder, type GeoIndexDefinition, type GeoPointInput, type GlobalBackend, type IndexDefinition, type IndexRangeBuilder, type InferArgs, type LifecycleEvent, type LifecycleEventKind, type LogFields, type LunoraLogMethod, type LunoraLogger, type LunoraMetrics, type LunoraTracer, type LunoraWideEvent, type MutationCtx, type OnDeleteAction, type PaginationOptions, type PaginationResult, type QueryCtx, type RankIndexDefinition, type RankSortKey, type ReadOnlyStorage, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationDefinition, type RestCacheConfig, type RunQueryOptions, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type Schema, type SearchFilterBuilder, type SearchIndexDefinition, type SearchLanguage, type SearchStrategy, type Secrets, type SecretsStoreSecretLike, type ShardInitEvent, type ShardMode, type SpanEvaluation, type SpanHandle, type SpanKind, type SpanLink, type SpanOptions, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableDefinition, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerBuilder, type TriggerCtx, type TriggerDatabase, type TriggerDefinition, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type TtlDefinition, type VectorEmbedder, type VectorIndexDefinition, type VectorMatch, type VectorMatches, type VectorMetric, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowEventDefinition, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, type X402ProcedureConfig, anyApi };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/server",
3
- "version": "1.0.0-alpha.78",
3
+ "version": "1.0.0-alpha.79",
4
4
  "description": "Server primitives for Lunora: defineSchema, defineTable, query, mutation, and action",
5
5
  "keywords": [
6
6
  "backend",
@@ -67,7 +67,7 @@
67
67
  },
68
68
  "dependencies": {
69
69
  "@lunora/errors": "1.0.0-alpha.22",
70
- "@lunora/scheduler": "1.0.0-alpha.34",
70
+ "@lunora/scheduler": "1.0.0-alpha.35",
71
71
  "@lunora/values": "1.0.0-alpha.27",
72
72
  "drizzle-orm": "^0.45.2",
73
73
  "hono": "^4.13.1"
@@ -1 +0,0 @@
1
- const t=(n,c)=>({args:{},handler:(o,e)=>c(o,e),kind:"mutation",lifecycle:n,visibility:"internal"}),i=n=>t("connect",n),r=n=>t("disconnect",n);export{i as onConnect,r as onDisconnect};
@@ -1 +0,0 @@
1
- import{LunoraError as d}from"@lunora/errors";import{r as q}from"./run-middleware-B-JeGGp9.mjs";import{v as A}from"@lunora/values";const B=["de","en","es","fr","it","nl","none","pt"],O=["native","portable"],_=n=>O.includes(n),P=n=>B.includes(n),M=16,C={many:(n,e)=>({field:e.field,kind:"many",references:e.references??"_id",table:n}),one:(n,e)=>({field:e.field,kind:"one",onDelete:e.onDelete,references:e.references??"_id",table:n})},g=(n,e,t)=>({handler:t,op:e,timing:n}),K=()=>({afterDelete:n=>g("after","delete",n),afterInsert:n=>g("after","insert",n),afterUpdate:n=>g("after","update",n),beforeDelete:n=>g("before","delete",n),beforeInsert:n=>g("before","insert",n),beforeUpdate:n=>g("before","update",n)}),j=(n,e)=>{if(!e||e.length===0)throw new d("INTERNAL",`rankIndex "${n}": "sortBy" is required and must list at least one key`);return e.map(t=>({direction:t.direction??"asc",field:t.field}))},te=n=>{const e={...n},t=[],r=[],i=[],l=[],u={},o=[],f={},m=K(),b=[];let y={kind:"root"},N=!1,p=!1,k,T,L,R;const c={aggregateIndex(s,a){const h=a?.op??"count";if(h!=="count"&&!a?.field)throw new d("INTERNAL",`aggregateIndex "${s}": op "${h}" requires a "field"`);return t.push({by:a?.by,field:a?.field,name:s,on:"",op:h,where:a?.where}),c},get aggregateIndexes(){return t},get externalSource(){return R},externallyManaged(){return N=!0,c},geoIndex(s,a){return r.push({field:a.field,name:s,precision:a.precision}),c},get geoIndexes(){return r},global(s){return y={backend:s?.backend??"d1",kind:"global"},c},get isExternallyManaged(){return N},get isPublic(){return p},index(s,a,h){return i.push({fields:a,name:s,unique:h?.unique??!1}),c},get indexes(){return i},ownedBy(s){return k=s,c},get ownerField(){return k},public(){return p=!0,c},rankIndex(s,a){return l.push({name:s,on:"",partitionBy:a.partitionBy,sortBy:j(s,a.sortBy),where:a.where}),c},get rankIndexes(){return l},get relationMap(){return u},relations(s){return Object.assign(u,s(C)),c},searchIndex(s,a){if(a.filterFields&&a.filterFields.length>M)throw new d("INTERNAL",`searchIndex "${s}": at most ${String(M)} filterFields are supported (got ${String(a.filterFields.length)})`);const{language:h}=a;if(h!==void 0&&!P(h))throw new d("INTERNAL",`searchIndex "${s}": unknown language "${h}" (supported: ${B.join(", ")})`);const{strategy:E}=a;if(E!==void 0&&!_(E))throw new d("INTERNAL",`searchIndex "${s}": unknown strategy "${E}" (supported: ${O.join(", ")})`);return o.push({field:a.field,filterFields:a.filterFields,language:a.language,name:s,staged:a.staged,strategy:a.strategy}),c},get searchIndexes(){return o},shape:e,shardBy(s){return y={field:s,kind:"shardBy"},c},get shardMode(){return y},get softDeleteMode(){return T},source(s){if(!s.binding)throw new d("INTERNAL","source: `binding` is required (the wrangler Hyperdrive binding name)");if(!s.query)throw new d("INTERNAL","source: `query` is required (the tenant-membership SQL)");return R=s,N=!0,c},softDelete(s){const a=s?.field??"deletedAt";return T={field:a},a in e||(e[a]=A.optional(A.number().nullable())),c},get triggerMap(){return f},triggers(s){return Object.assign(f,s(m)),c},ttl(s,a){return L={after:a?.after,field:s},c},get ttlPolicy(){return L},get vectorIndexes(){return b},vectorize(s,a){return b.push({dimensions:a.dimensions,embed:a.embed,field:s,metadata:a.metadata,metric:a.metric,name:a.index}),c}};return c},re=n=>({dimensions:n.dimensions,embed:n.embed,kind:"vectorIndex",metadata:n.metadata,metric:n.metric,select:n.source.select,table:n.source.table}),ae=(n,e)=>{const t=e.op??"count";if(t!=="count"&&!e.field)throw new d("INTERNAL",`aggregateIndex "${n}": op "${t}" requires a "field"`);return{by:e.by,field:e.field,name:n,on:e.on,op:t,where:e.where}},se=(n,e)=>({name:n,on:e.table,partitionBy:e.partitionBy,sortBy:j(n,e.sortBy),where:e.where}),w=n=>({...n,extend(e){return w(F(n,e))},jurisdiction(e){return w(n)},rls(e){return w({...n,rlsMode:e})}}),z=n=>{for(const[e,t]of Object.entries(n))for(const r of[...t.aggregateIndexes,...t.rankIndexes])r.on===""&&(r.on=e)},H=(n,e,t)=>{for(const r of Object.values(e)){const i=n[r.on];if(!i)throw new d("INTERNAL",`defineAggregateIndex "${r.name}": unknown table "${r.on}"`);i.aggregateIndexes.push(r)}for(const r of Object.values(t)){const i=n[r.on];if(!i)throw new d("INTERNAL",`defineRankIndex "${r.name}": unknown table "${r.on}"`);i.rankIndexes.push(r)}},X=n=>{if(n.cursor)return"cursor";if(n.reconcileEveryMs!==void 0)return"reconcileEveryMs";if(n.softDeleteColumn!==void 0)return"softDeleteColumn"},Q=(n,e)=>{const t=e.mode==="incremental";if(e.mode!==void 0&&e.mode!=="full-pull"&&!t)throw new d("INTERNAL",`defineSchema: table "${n}" uses \`mode: ${JSON.stringify(e.mode)}\` — supported modes are "full-pull" (default) and "incremental".`);if(!t){const r=X(e);if(r)throw new d("INTERNAL",`defineSchema: table "${n}" sets \`${r}\` but is not \`mode: "incremental"\` — that knob only applies to incremental ingest. Set \`mode: "incremental"\` or remove \`${r}\`.`);return}if(!e.cursor?.column||!e.cursor.query)throw new d("INTERNAL",`defineSchema: table "${n}" is \`mode: "incremental"\` but has no \`cursor\` — add \`cursor: { column: "updated_at", query: "… WHERE … > $N" }\` binding the watermark as the trailing parameter.`);if(e.reconcileEveryMs===void 0&&e.softDeleteColumn===void 0)throw new d("INTERNAL",`defineSchema: table "${n}" is \`mode: "incremental"\` with no delete-visibility path — an incremental pull never sees upstream deletes, so it would accumulate phantom rows. Add \`reconcileEveryMs\` (a periodic full-pull sweep) or \`softDeleteColumn\` (an upstream tombstone column).`)},U=n=>{for(const[e,t]of Object.entries(n)){const r=t.externalSource;if(r){if(t.shardMode.kind==="global")throw new d("INTERNAL",`defineSchema: table "${e}" cannot be both .source() and .global() — a sourced table materializes into a shard DO's SQLite, a global table lives in the external tier`);if(t.shardMode.kind==="shardBy"&&!r.tenantBy)throw new d("INTERNAL",`defineSchema: sourced + .shardBy() table "${e}" needs a \`tenantBy\` mapper — without it every tenant's DO would run the same unscoped query and replicate the whole multitenant table (a cross-tenant leak). Add \`tenantBy: (shardKey) => [shardKey]\` binding the shard key into the query's parameters.`);Q(e,r)}}},V=["_creationTime","_id"],Y=new Set(V),x=(n,e,t,r)=>{if(!(Y.has(t)||t in r.shape))throw new d("INTERNAL",`defineSchema: table "${n}" index "${e}" names column "${t}" which is not in the table's shape`)},$=(n,e,t,r)=>{const i=new Set;for(const l of t){if(i.has(l.name))throw new d("INTERNAL",`defineSchema: table "${n}" declares ${e} "${l.name}" more than once`);i.add(l.name),r(l)}},D=n=>{for(const[e,t]of Object.entries(n))$(e,"index",t.indexes,r=>{for(const i of r.fields)x(e,r.name,i,t)}),$(e,"rank index",t.rankIndexes,r=>{for(const i of r.sortBy)x(e,r.name,i.field,t);for(const i of r.partitionBy??[])x(e,r.name,i,t)}),$(e,"geo index",t.geoIndexes,r=>{x(e,r.name,r.field,t)})},S=(n,e,t)=>{if(n.length===0)return;const r={};for(const i of n)r[e(i)]=t(i);return r},J=n=>{const e=new Set(n.sortBy.map(t=>t.field));for(const t of n.partitionBy??[])e.add(t);return[...e]},ie=n=>{const e={};for(const[t,r]of Object.entries(n.tables)){const i=S(r.geoIndexes,o=>o.name,o=>[o.field]),l=S(r.indexes,o=>o.name,o=>o.fields),u=S(r.rankIndexes,o=>o.name,J);(i||l||u)&&(e[t]={...i?{geo:i}:{},...l?{index:l}:{},...u?{rank:u}:{}})}return e},de=(n,e={},t={},r={})=>(z(n),H(n,t,r),U(n),D(n),w({tables:n,vectorIndexes:e})),I=(n,e)=>`${n}_${e}`,v=(n,e,t)=>t.has(n)?I(e,n):n,W=(n,e,t,r)=>{const i=I(e,r),l=m=>m===""?i:v(m,e,t),u={};for(const[m,b]of Object.entries(n.relationMap))u[m]={...b,table:v(b.table,e,t)};const o=n.aggregateIndexes.map(m=>({...m,on:l(m.on)})),f=n.rankIndexes.map(m=>({...m,on:l(m.on)}));return{...n,aggregateIndexes:o,rankIndexes:f,relationMap:u}},oe=(n,e)=>{if(!n)throw new d("INTERNAL","defineSchemaExtension: `key` is required and must be a non-empty string");return{key:n,tables:e.tables,...e.vectorIndexes?{vectorIndexes:e.vectorIndexes}:{}}},G=((n,e)=>{if(!n)throw new d("INTERNAL","definePlugin: `key` is required and must be a non-empty string");if(e.extension&&e.extension.key!==n)throw new d("INTERNAL",`definePlugin("${n}"): extension key "${e.extension.key}" does not match plugin key`);return{key:n,...e.extension?{extension:e.extension}:{},...e.middleware?{middleware:e.middleware}:{}}}),le=(n,e)=>({...G(n,{...e.extension?{extension:e.extension}:{},...e.middleware?{middleware:e.middleware}:{}}),functions:e.functions??{}}),F=(n,e)=>{const{key:t}=e,r=new Set(Object.keys(e.tables)),i={...n.tables};for(const[u,o]of Object.entries(e.tables)){const f=I(t,u);if(Object.hasOwn(i,f))throw new d("INTERNAL",`defineSchema(...).extend("${t}"): table "${f}" already exists in the base schema — another extension with the same key already contributed it`);i[f]=W(o,t,r,u)}const l={...n.vectorIndexes};if(e.vectorIndexes)for(const[u,o]of Object.entries(e.vectorIndexes)){const f=I(t,u);if(Object.hasOwn(l,f))throw new d("INTERNAL",`defineSchema(...).extend("${t}"): vector index "${f}" already exists in the base schema — another extension with the same key already contributed it`);l[f]={...o,table:v(o.table,t,r)}}return D(i),{rlsMode:n.rlsMode,tables:i,vectorIndexes:l}},ce=(n,e)=>{let t=n;for(const r of e)r.extension&&(t=F(t,r.extension));return t},ue=n=>{const e=n.map(t=>t.middleware).filter(t=>t!==void 0);return(async({ctx:t,next:r})=>q(e,t,i=>r({ctx:i})))};export{le as a,G as b,ue as c,ae as d,se as e,de as f,oe as g,te as h,re as i,ie as j,ce as k,F as m,D as v};