@lunora/server 1.0.0-alpha.6 → 1.0.0-alpha.8

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/README.md CHANGED
@@ -93,6 +93,10 @@ The builder chain is `<builder>.input(validators).<kind>(handler)`, plus `.use(m
93
93
 
94
94
  > **Determinism:** `query` and `mutation` handlers must be deterministic — they may be re-run on OCC retry or subscription re-evaluation. Read the current time from **`ctx.now`** (epoch ms, captured once per execution — also on `ActionCtx`) instead of `Date.now()`; compute randomness and network results in an `action` (`crypto.randomUUID()`, `fetch`) and pass them into the mutation as arguments. The `nondeterministic_query_mutation` advisor flags `Date.now()`/`Math.random()`/`fetch` in query/mutation handlers.
95
95
 
96
+ ### Local-first sync engine
97
+
98
+ `defineShape` declares a **partial replication shape** (a named table view with a server-resolved `where` predicate, AND-composed with RLS) and `defineMutator` declares a **custom mutator** (an authoritative `server` impl plus an optional optimistic `client` twin). Both are discovered by codegen and drive the [local-first sync engine](https://lunora.sh/docs/concepts/local-first) — clients subscribe to shapes over the poke diff protocol and push optimistic writes rebased over the server's authoritative result. The client-side runtime lives in [`@lunora/db`](https://www.npmjs.com/package/@lunora/db).
99
+
96
100
  > This README covers the basics. For the full API, options, and guides, see the **[documentation](https://lunora.sh/docs/packages/server)**.
97
101
 
98
102
  ## Related
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { Validator, Infer, v } from '@lunora/values';
1
+ import { Validator, Infer, ValidatorMap, InferValidatorMap, v } from '@lunora/values';
2
2
  export { type ColumnValidator, type Id, type Infer, ValidationError, type Validator, type ValidatorKind, v } from '@lunora/values';
3
- import { ArgsValidator, InferArgs, RegisteredAction, ActionCtx, MutationCtx, RegisteredMutation, QueryCtx, RegisteredQuery, RegisteredStream, FunctionKind, Secrets, LifecycleEvent, RegisteredLifecycleHook, TableDefinition, RegisteredFunction, VectorIndexDefinition, Schema, AggregateOp, DurableObjectJurisdiction, RelationDefinition, GlobalBackend, OnDeleteAction, TriggerBuilder, TriggerDefinition, VectorEmbedder, VectorMetric, AggregateIndexDefinition, RankIndexDefinition } from "./types.mjs";
3
+ import { ArgsValidator, InferArgs, RegisteredAction, ActionCtx, MutationCtx, RegisteredMutation, QueryCtx, RegisteredQuery, RegisteredStream, FunctionKind, Secrets, LifecycleEvent, RegisteredLifecycleHook, TableDefinition, RegisteredFunction, VectorIndexDefinition, Schema, AggregateOp, DurableObjectJurisdiction, RelationDefinition, GlobalBackend, OnDeleteAction, ExternalSourceDefinition, TriggerBuilder, TriggerDefinition, VectorEmbedder, VectorMetric, AggregateIndexDefinition, RankIndexDefinition } from "./types.mjs";
4
4
  export { type AnyApi, type AuthState, type DatabaseReader, type DatabaseWriter, type FunctionVisibility, type IndexDefinition, type IndexRangeBuilder, type LifecycleEventKind, type LunoraLogger, type PaginationOptions, type PaginationResult, type RankSortKey, type ReadOnlyStorage, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type SearchFilterBuilder, type SearchIndexDefinition, type ShardMode, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerCtx, type TriggerDatabase, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type VectorMatch, type VectorMatches, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi } from "./types.mjs";
5
5
  import { Context, Hono } from 'hono';
6
6
  import { b as Permission, R as Role, T as TypedDefinePolicyInput, a as Policy, D as DefinePolicyInput, W as WhereInput, c as RlsOptions } from "./packem_shared/types.d-DmvyEMD6.mjs";
@@ -896,6 +896,55 @@ interface RegisteredMigration extends MigrationDefinition {
896
896
  /** Declare an online data migration. See the module docs for runtime semantics. */
897
897
  declare const defineMigration: (definition: MigrationDefinition) => RegisteredMigration;
898
898
  /**
899
+ * A mutator declaration. `server` is authoritative; `client` is the optimistic
900
+ * twin (optional — omit it to let the optimistic write fall through to the
901
+ * server round-trip with no local preview). Both receive the same validated
902
+ * `args`.
903
+ */
904
+ interface MutatorDefinition<Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown> {
905
+ /**
906
+ * Validator for the mutator's arguments. Validated on the DO before `server`
907
+ * runs and (when present) on the client before `client` runs, so both impls
908
+ * see the same parsed shape. Omit for a parameterless mutator.
909
+ */
910
+ readonly args?: Args;
911
+ /**
912
+ * Optimistic client implementation. Runs in a TanStack DB transaction
913
+ * against the local collections; its writes are applied immediately and
914
+ * automatically rolled back / rebased as the authoritative result syncs
915
+ * back. Pure and side-effect-free beyond the local store. Omit to skip the
916
+ * local preview.
917
+ */
918
+ readonly client?: (tx: ClientTx, args: InferValidatorMap<Args>) => Promise<void> | void;
919
+ /**
920
+ * Authoritative server implementation. Runs inside the shard DO with a full
921
+ * {@link MutationContext} (`ctx.db` writer); its writes append to `__cdc_log`
922
+ * and poke back to subscribers. This is the source of truth — the client
923
+ * impl is only a prediction of it.
924
+ */
925
+ readonly server: (context: ServerContext, args: InferValidatorMap<Args>) => Promise<R> | R;
926
+ }
927
+ /**
928
+ * A {@link MutatorDefinition} plus the codegen discovery marker and a
929
+ * dispatch-shaped `handler` (validates `args`, then runs `server`) so the DO
930
+ * invokes a mutator exactly like a registered procedure.
931
+ */
932
+ interface RegisteredMutator<Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown> extends MutatorDefinition<Args, ServerContext, ClientTx, R> {
933
+ readonly __lunoraMutator: true;
934
+ /** Validate `rawArgs`, then run the authoritative `server` impl. Used by the DO push path. */
935
+ readonly handler: (context: ServerContext, rawArgs: Record<string, unknown>) => Promise<R>;
936
+ /**
937
+ * Marks the dispatch kind so codegen can register the mutator in the same
938
+ * `LUNORA_FUNCTIONS` table queries/mutations use — the DO's `handleRpc`
939
+ * reads `kind === "mutation"` to wrap the authoritative `server` impl in the
940
+ * shard's BEGIN/COMMIT span (all-or-nothing writes), exactly like an
941
+ * ordinary `mutation`.
942
+ */
943
+ readonly kind: "mutation";
944
+ }
945
+ /** Declare a custom mutator. See the module docs for runtime semantics. */
946
+ declare const defineMutator: <Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown>(definition: MutatorDefinition<Args, ServerContext, ClientTx, R>) => RegisteredMutator<Args, ServerContext, ClientTx, R>;
947
+ /**
899
948
  * The prefixed tables a single plugin `P` contributes, or an empty map when it
900
949
  * ships no schema extension. Mirrors {@link PrefixedTables} at the plugin level
901
950
  * so {@link InstalledTables} can fold a tuple of plugins.
@@ -1222,6 +1271,19 @@ interface TableBuilder<Shape extends Record<string, Validator> = Record<string,
1222
1271
  softDelete: (options?: {
1223
1272
  field?: string;
1224
1273
  }) => TableBuilder<Shape>;
1274
+ /**
1275
+ * Materialize this table from an external Postgres/MySQL behind Cloudflare
1276
+ * Hyperdrive (plan 077). A system-driven poll loop reads the tenant slice
1277
+ * (`query`, with params bound from `tenantBy`) and lands it in the DO's SQLite,
1278
+ * after which `defineShape` carries it to clients unchanged. Implies
1279
+ * `.externallyManaged()` (rows come from the ingest loop, not user mutations).
1280
+ *
1281
+ * Orthogonal to `.shardBy()` — combine them for per-tenant DOs. **Under
1282
+ * `.shardBy()` `tenantBy` is mandatory** (the tenant-isolation boundary); the
1283
+ * `external_source_unscoped` advisor lint fails the build when it is absent, and
1284
+ * `external_source_on_global` rejects combining `.source()` with `.global()`.
1285
+ */
1286
+ source: (definition: ExternalSourceDefinition) => TableBuilder<Shape>;
1225
1287
  /** Declare named lifecycle triggers fired inline within the write path. */
1226
1288
  triggers: (build: (t: TriggerBuilder<Shape>) => Record<string, TriggerDefinition>) => TableBuilder<Shape>;
1227
1289
  /** Declare a vector index over a single text field on this table. */
@@ -1735,6 +1797,104 @@ interface RlsContextIn {
1735
1797
  }
1736
1798
  declare const rls: <Context extends RlsContextIn = RlsContextIn>(policies: ReadonlyArray<Policy<Context>>, options?: RlsOptions) => Middleware<Context, Context>;
1737
1799
  /**
1800
+ * One `rls()` tag's read policies for a table, paired with the role→permission
1801
+ * grants of that SAME middleware. Keeping the role map per-group is what lets a
1802
+ * policy's `auth.can(...)` resolve against its own middleware's roles — never a
1803
+ * permission registered on a different `rls()` step.
1804
+ */
1805
+ interface ScopedReadPolicies {
1806
+ readonly policies: ReadonlyArray<Policy>;
1807
+ readonly rolePermissions: ReadonlyMap<string, ReadonlySet<string>>;
1808
+ }
1809
+ /** Table-indexed read-policy groups, each scoped to the roles of the rls() middleware that declared it. */
1810
+ interface RlsReadRegistry {
1811
+ readonly byTable: ReadonlyMap<string, ReadonlyArray<ScopedReadPolicies>>;
1812
+ }
1813
+ /** The trusted, server-resolved facts a shape's RLS evaluation runs under. */
1814
+ interface ShapeReadWhereRequest {
1815
+ /** The shape ctx (the procedure context a policy `when` reads as `ctx`). */
1816
+ readonly ctx: unknown;
1817
+ /** Resolved identity claims (the socket's verified identity), or `null` when anonymous. */
1818
+ readonly identity: Record<string, unknown> | null;
1819
+ /** `true` when the schema is `.rls("required")` — gates the fail-closed branch. */
1820
+ readonly rlsRequired: boolean;
1821
+ /** Role labels the request carries (drives `auth.can(...)`). */
1822
+ readonly roles: ReadonlyArray<string>;
1823
+ /** The shape's own predicate (`where(ctx, args)`). */
1824
+ readonly shapeWhere: WhereInput;
1825
+ /** Logical table the shape replicates. */
1826
+ readonly table: string;
1827
+ /** `true` when the table is `.public()` (exempt from `.rls("required")` denial). */
1828
+ readonly tablePublic: boolean;
1829
+ /** Verified user id, or `null` when anonymous. */
1830
+ readonly userId: null | string;
1831
+ }
1832
+ /**
1833
+ * Build the read-policy registry from the registered functions (pass
1834
+ * `Object.values(LUNORA_FUNCTIONS)`). Only `on: "read"` policies are collected,
1835
+ * grouped per `rls()` middleware so each group keeps its own role→permission map
1836
+ * (a `(table, when)` pair is de-duplicated within a tag). A tag reused across
1837
+ * several procedures (a shared `const guard = rls(...)`) is folded once. This
1838
+ * mirrors the request-time `rls()` path exactly: a policy's `auth.can(...)`
1839
+ * resolves against the roles of the middleware that declared it, never a union.
1840
+ */
1841
+ declare const buildRlsReadRegistry: (functions: Iterable<unknown>) => RlsReadRegistry;
1842
+ /**
1843
+ * Compute the effective `where` a shape replicates: the table's RLS read
1844
+ * base-where AND the shape's own predicate. Returns the shape predicate
1845
+ * unchanged for a table with no read policy (a `.public()` or non-RLS table),
1846
+ * and the FALSE sentinel (replicate nothing) when a `.rls("required")` schema
1847
+ * exposes a protected, policy-less table.
1848
+ */
1849
+ declare const composeShapeReadWhere: (registry: RlsReadRegistry, request: ShapeReadWhereRequest) => WhereInput;
1850
+ /**
1851
+ * A shape declaration. `where` receives the trusted procedure context and the
1852
+ * validated client args and returns the same {@link WhereInput} shape the RLS
1853
+ * DSL uses, so the DO can AND-merge it with the table's read base-where via the
1854
+ * existing where-compiler (zero second predicate implementation).
1855
+ */
1856
+ interface ShapeDefinition<Args extends ValidatorMap = ValidatorMap, Context = QueryCtx> {
1857
+ /**
1858
+ * Validator for the client-supplied shape parameters. Validated on the DO
1859
+ * before `where` runs, so a malformed `args` envelope is rejected at the
1860
+ * subscription boundary rather than silently widening the partition. Omit
1861
+ * for a parameterless shape.
1862
+ */
1863
+ readonly args?: Args;
1864
+ /**
1865
+ * Project the replicated rows to these columns (the system columns `_id` and
1866
+ * `_creationTime` are always included). Omit to replicate every column. An
1867
+ * empty array is rejected — it would replicate no data, which is never the
1868
+ * intent.
1869
+ */
1870
+ readonly columns?: ReadonlyArray<string>;
1871
+ /** Logical table this shape replicates a partition of. */
1872
+ readonly table: string;
1873
+ /**
1874
+ * Predicate selecting the rows this shape replicates. AND-composed with the
1875
+ * table's RLS read base-where on the DO. Runs server-side with a trusted
1876
+ * `ctx` (identity/auth the client can't forge) and the validated client
1877
+ * `args`; returns a {@link WhereInput} using the same operator set as the
1878
+ * SQL compiler (`eq`/`in`/`lt`/… + `AND`/`OR`/`NOT`).
1879
+ */
1880
+ readonly where: (context: Context, args: InferValidatorMap<Args>) => WhereInput;
1881
+ }
1882
+ /** A {@link ShapeDefinition} plus the codegen discovery marker and a dispatch-shaped `compileWhere`. */
1883
+ interface RegisteredShape<Args extends ValidatorMap = ValidatorMap, Context = QueryCtx> extends ShapeDefinition<Args, Context> {
1884
+ readonly __lunoraShape: true;
1885
+ /**
1886
+ * Validate `rawArgs`, then evaluate `where` under the trusted `ctx` and
1887
+ * return its {@link WhereInput}. Used by the generated DO's `resolveShape`
1888
+ * override: `ctx` is erased to `unknown` at this dispatch boundary (the DO
1889
+ * builds it from the socket's verified identity and hands it back as the
1890
+ * concrete {@link QueryContext} the predicate expects), exactly like
1891
+ * `RegisteredLunoraFunction.handler` erases its context.
1892
+ */
1893
+ readonly compileWhere: (context: unknown, rawArgs: Record<string, unknown>) => WhereInput;
1894
+ }
1895
+ /** Declare a replication shape. See the module docs for runtime semantics. */
1896
+ declare const defineShape: <Args extends ValidatorMap = ValidatorMap, Context = QueryCtx>(definition: ShapeDefinition<Args, Context>) => RegisteredShape<Args, Context>;
1897
+ /**
1738
1898
  * Operations a storage rule can gate. `read` covers `download` / `getMetadata`
1739
1899
  * / `getSignedUrl` / `getUrl`; `write` covers `store` / `generateUploadUrl`;
1740
1900
  * `delete` is `delete`; `list` is a prefix listing (governed via the file
@@ -1817,4 +1977,4 @@ interface StorageContextIn {
1817
1977
  }
1818
1978
  declare const storageRules: <Context extends StorageContextIn = StorageContextIn>(rules: ReadonlyArray<StorageRule<Context>>, options?: StorageRulesOptions) => Middleware<Context, Context>;
1819
1979
  declare const VERSION = "0.0.0";
1820
- export { type ActionBuilder, type ActionCtx, type AggregateIndexDefinition, type AggregateIndexOptions, type AggregateOp, type ArgsValidator, type Component, type ComponentFunctions, type CreateOptions, type DataModelInit, type DefineComponentOptions, type DefinePluginOptions, type DefinePolicyInput, type DefinePresenceOptions, type DefineStorageRuleInput, type DurableObjectJurisdiction, type EmptyArgs, type EnvAccessor, type EnvKeyFailure, type EnvShape, type ExtendableSchema, type FacadeEntry, type FacadeWriterLike, type FunctionKind, type HttpActionCtx, type HttpActionHandler, type HttpMethod, type HttpRoute, type HttpRouteBuilder, type HttpRouteFactory, type HttpRouteHandlerOptions, type HttpStreamHandlerOptions, type InferArgs, type InferEnv, type InlineAggregateIndexOptions, type InlineRankIndexOptions, type InternalActionBuilder, type InternalMutationBuilder, type InternalQueryBuilder, type LifecycleEvent, type LifecycleHandler, type LunoraBuilders, LunoraEnvError, LunoraError, type LunoraErrorCode, type LunoraHttpApp, type LunoraHttpEnv, type LunoraRouteHandler, type ManyRelation, type MaskColumns, type MaskContext, type MaskFn, type MaskOptions, type MaskPolicies, type MaskStrategy, type Middleware, type MiddlewareNext, type MigrationDefinition, type MigrationDocument, type MigrationTransform, type MutationBuilder, type MutationCtx, type OnDeleteAction, type OneRelation, type OrmLike, DEFAULT_TTL_MS as PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, type Permission, type Plugin, type Policy, type PrefixedTables, type PresenceComponent, type PresenceFunctions, type PresenceMember, type ProtectPublicOptions, type QueryBuilder, type QueryCtx, type RankIndexDefinition, type RankIndexOptions, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMigration, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationBuilder, type RelationDefinition, type RlsOptions, type Role, type Schema, type SchemaExtension, type StorageOperation, type StorageRule, type StorageRuleContext, type StorageRuleDecision, type StorageRulesOptions, type TableBuilder, type TableDefinition, type TerminalKind, type TriggerBuilder, type TriggerDefinition, type TypedDefinePolicyInput, VERSION, type VectorEmbedder, type VectorIndexDefinition, type VectorIndexOptions, type VectorMetric, type VectorizeOptions, type WhereInput, asBucketStorage, bindOrm, bindTableFacade, composePluginMiddleware, createPolicyDsl, createSecrets, defineAggregateIndex, defineComponent, defineEnv, defineMigration, definePermission, definePlugin, definePolicies, definePolicy, definePresence, defineRankIndex, defineRole, defineSchema, defineSchemaExtension, defineStorageRule, defineStorageRules, defineTable, defineVectorIndex, httpAction, httpRoute, httpRouter, initLunora, installPlugins, mask, mergeSchemaExtension, onConnect, onDisconnect, presenceExtension, protectPublic, redactSecrets, rls, serveStorageObject, storageRules };
1980
+ export { type ActionBuilder, type ActionCtx, type AggregateIndexDefinition, type AggregateIndexOptions, type AggregateOp, type ArgsValidator, type Component, type ComponentFunctions, type CreateOptions, type DataModelInit, type DefineComponentOptions, type DefinePluginOptions, type DefinePolicyInput, type DefinePresenceOptions, type DefineStorageRuleInput, type DurableObjectJurisdiction, type EmptyArgs, type EnvAccessor, type EnvKeyFailure, type EnvShape, type ExtendableSchema, type FacadeEntry, type FacadeWriterLike, type FunctionKind, type HttpActionCtx, type HttpActionHandler, type HttpMethod, type HttpRoute, type HttpRouteBuilder, type HttpRouteFactory, type HttpRouteHandlerOptions, type HttpStreamHandlerOptions, type InferArgs, type InferEnv, type InlineAggregateIndexOptions, type InlineRankIndexOptions, type InternalActionBuilder, type InternalMutationBuilder, type InternalQueryBuilder, type LifecycleEvent, type LifecycleHandler, type LunoraBuilders, LunoraEnvError, LunoraError, type LunoraErrorCode, type LunoraHttpApp, type LunoraHttpEnv, type LunoraRouteHandler, type ManyRelation, type MaskColumns, type MaskContext, type MaskFn, type MaskOptions, type MaskPolicies, type MaskStrategy, type Middleware, type MiddlewareNext, type MigrationDefinition, type MigrationDocument, type MigrationTransform, type MutationBuilder, type MutationCtx, type 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, asBucketStorage, bindOrm, bindTableFacade, buildRlsReadRegistry, composePluginMiddleware, composeShapeReadWhere, createPolicyDsl, createSecrets, defineAggregateIndex, defineComponent, defineEnv, defineMigration, defineMutator, definePermission, definePlugin, definePolicies, definePolicy, definePresence, defineRankIndex, defineRole, defineSchema, defineSchemaExtension, defineShape, defineStorageRule, defineStorageRules, defineTable, defineVectorIndex, httpAction, httpRoute, httpRouter, initLunora, installPlugins, mask, mergeSchemaExtension, onConnect, onDisconnect, presenceExtension, protectPublic, redactSecrets, rls, serveStorageObject, storageRules };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { Validator, Infer, v } from '@lunora/values';
1
+ import { Validator, Infer, ValidatorMap, InferValidatorMap, v } from '@lunora/values';
2
2
  export { type ColumnValidator, type Id, type Infer, ValidationError, type Validator, type ValidatorKind, v } from '@lunora/values';
3
- import { ArgsValidator, InferArgs, RegisteredAction, ActionCtx, MutationCtx, RegisteredMutation, QueryCtx, RegisteredQuery, RegisteredStream, FunctionKind, Secrets, LifecycleEvent, RegisteredLifecycleHook, TableDefinition, RegisteredFunction, VectorIndexDefinition, Schema, AggregateOp, DurableObjectJurisdiction, RelationDefinition, GlobalBackend, OnDeleteAction, TriggerBuilder, TriggerDefinition, VectorEmbedder, VectorMetric, AggregateIndexDefinition, RankIndexDefinition } from "./types.js";
3
+ import { ArgsValidator, InferArgs, RegisteredAction, ActionCtx, MutationCtx, RegisteredMutation, QueryCtx, RegisteredQuery, RegisteredStream, FunctionKind, Secrets, LifecycleEvent, RegisteredLifecycleHook, TableDefinition, RegisteredFunction, VectorIndexDefinition, Schema, AggregateOp, DurableObjectJurisdiction, RelationDefinition, GlobalBackend, OnDeleteAction, ExternalSourceDefinition, TriggerBuilder, TriggerDefinition, VectorEmbedder, VectorMetric, AggregateIndexDefinition, RankIndexDefinition } from "./types.js";
4
4
  export { type AnyApi, type AuthState, type DatabaseReader, type DatabaseWriter, type FunctionVisibility, type IndexDefinition, type IndexRangeBuilder, type LifecycleEventKind, type LunoraLogger, type PaginationOptions, type PaginationResult, type RankSortKey, type ReadOnlyStorage, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type SearchFilterBuilder, type SearchIndexDefinition, type ShardMode, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerCtx, type TriggerDatabase, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type VectorMatch, type VectorMatches, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi } from "./types.js";
5
5
  import { Context, Hono } from 'hono';
6
6
  import { b as Permission, R as Role, T as TypedDefinePolicyInput, a as Policy, D as DefinePolicyInput, W as WhereInput, c as RlsOptions } from "./packem_shared/types.d-BDY0FYHK.js";
@@ -896,6 +896,55 @@ interface RegisteredMigration extends MigrationDefinition {
896
896
  /** Declare an online data migration. See the module docs for runtime semantics. */
897
897
  declare const defineMigration: (definition: MigrationDefinition) => RegisteredMigration;
898
898
  /**
899
+ * A mutator declaration. `server` is authoritative; `client` is the optimistic
900
+ * twin (optional — omit it to let the optimistic write fall through to the
901
+ * server round-trip with no local preview). Both receive the same validated
902
+ * `args`.
903
+ */
904
+ interface MutatorDefinition<Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown> {
905
+ /**
906
+ * Validator for the mutator's arguments. Validated on the DO before `server`
907
+ * runs and (when present) on the client before `client` runs, so both impls
908
+ * see the same parsed shape. Omit for a parameterless mutator.
909
+ */
910
+ readonly args?: Args;
911
+ /**
912
+ * Optimistic client implementation. Runs in a TanStack DB transaction
913
+ * against the local collections; its writes are applied immediately and
914
+ * automatically rolled back / rebased as the authoritative result syncs
915
+ * back. Pure and side-effect-free beyond the local store. Omit to skip the
916
+ * local preview.
917
+ */
918
+ readonly client?: (tx: ClientTx, args: InferValidatorMap<Args>) => Promise<void> | void;
919
+ /**
920
+ * Authoritative server implementation. Runs inside the shard DO with a full
921
+ * {@link MutationContext} (`ctx.db` writer); its writes append to `__cdc_log`
922
+ * and poke back to subscribers. This is the source of truth — the client
923
+ * impl is only a prediction of it.
924
+ */
925
+ readonly server: (context: ServerContext, args: InferValidatorMap<Args>) => Promise<R> | R;
926
+ }
927
+ /**
928
+ * A {@link MutatorDefinition} plus the codegen discovery marker and a
929
+ * dispatch-shaped `handler` (validates `args`, then runs `server`) so the DO
930
+ * invokes a mutator exactly like a registered procedure.
931
+ */
932
+ interface RegisteredMutator<Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown> extends MutatorDefinition<Args, ServerContext, ClientTx, R> {
933
+ readonly __lunoraMutator: true;
934
+ /** Validate `rawArgs`, then run the authoritative `server` impl. Used by the DO push path. */
935
+ readonly handler: (context: ServerContext, rawArgs: Record<string, unknown>) => Promise<R>;
936
+ /**
937
+ * Marks the dispatch kind so codegen can register the mutator in the same
938
+ * `LUNORA_FUNCTIONS` table queries/mutations use — the DO's `handleRpc`
939
+ * reads `kind === "mutation"` to wrap the authoritative `server` impl in the
940
+ * shard's BEGIN/COMMIT span (all-or-nothing writes), exactly like an
941
+ * ordinary `mutation`.
942
+ */
943
+ readonly kind: "mutation";
944
+ }
945
+ /** Declare a custom mutator. See the module docs for runtime semantics. */
946
+ declare const defineMutator: <Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown>(definition: MutatorDefinition<Args, ServerContext, ClientTx, R>) => RegisteredMutator<Args, ServerContext, ClientTx, R>;
947
+ /**
899
948
  * The prefixed tables a single plugin `P` contributes, or an empty map when it
900
949
  * ships no schema extension. Mirrors {@link PrefixedTables} at the plugin level
901
950
  * so {@link InstalledTables} can fold a tuple of plugins.
@@ -1222,6 +1271,19 @@ interface TableBuilder<Shape extends Record<string, Validator> = Record<string,
1222
1271
  softDelete: (options?: {
1223
1272
  field?: string;
1224
1273
  }) => TableBuilder<Shape>;
1274
+ /**
1275
+ * Materialize this table from an external Postgres/MySQL behind Cloudflare
1276
+ * Hyperdrive (plan 077). A system-driven poll loop reads the tenant slice
1277
+ * (`query`, with params bound from `tenantBy`) and lands it in the DO's SQLite,
1278
+ * after which `defineShape` carries it to clients unchanged. Implies
1279
+ * `.externallyManaged()` (rows come from the ingest loop, not user mutations).
1280
+ *
1281
+ * Orthogonal to `.shardBy()` — combine them for per-tenant DOs. **Under
1282
+ * `.shardBy()` `tenantBy` is mandatory** (the tenant-isolation boundary); the
1283
+ * `external_source_unscoped` advisor lint fails the build when it is absent, and
1284
+ * `external_source_on_global` rejects combining `.source()` with `.global()`.
1285
+ */
1286
+ source: (definition: ExternalSourceDefinition) => TableBuilder<Shape>;
1225
1287
  /** Declare named lifecycle triggers fired inline within the write path. */
1226
1288
  triggers: (build: (t: TriggerBuilder<Shape>) => Record<string, TriggerDefinition>) => TableBuilder<Shape>;
1227
1289
  /** Declare a vector index over a single text field on this table. */
@@ -1735,6 +1797,104 @@ interface RlsContextIn {
1735
1797
  }
1736
1798
  declare const rls: <Context extends RlsContextIn = RlsContextIn>(policies: ReadonlyArray<Policy<Context>>, options?: RlsOptions) => Middleware<Context, Context>;
1737
1799
  /**
1800
+ * One `rls()` tag's read policies for a table, paired with the role→permission
1801
+ * grants of that SAME middleware. Keeping the role map per-group is what lets a
1802
+ * policy's `auth.can(...)` resolve against its own middleware's roles — never a
1803
+ * permission registered on a different `rls()` step.
1804
+ */
1805
+ interface ScopedReadPolicies {
1806
+ readonly policies: ReadonlyArray<Policy>;
1807
+ readonly rolePermissions: ReadonlyMap<string, ReadonlySet<string>>;
1808
+ }
1809
+ /** Table-indexed read-policy groups, each scoped to the roles of the rls() middleware that declared it. */
1810
+ interface RlsReadRegistry {
1811
+ readonly byTable: ReadonlyMap<string, ReadonlyArray<ScopedReadPolicies>>;
1812
+ }
1813
+ /** The trusted, server-resolved facts a shape's RLS evaluation runs under. */
1814
+ interface ShapeReadWhereRequest {
1815
+ /** The shape ctx (the procedure context a policy `when` reads as `ctx`). */
1816
+ readonly ctx: unknown;
1817
+ /** Resolved identity claims (the socket's verified identity), or `null` when anonymous. */
1818
+ readonly identity: Record<string, unknown> | null;
1819
+ /** `true` when the schema is `.rls("required")` — gates the fail-closed branch. */
1820
+ readonly rlsRequired: boolean;
1821
+ /** Role labels the request carries (drives `auth.can(...)`). */
1822
+ readonly roles: ReadonlyArray<string>;
1823
+ /** The shape's own predicate (`where(ctx, args)`). */
1824
+ readonly shapeWhere: WhereInput;
1825
+ /** Logical table the shape replicates. */
1826
+ readonly table: string;
1827
+ /** `true` when the table is `.public()` (exempt from `.rls("required")` denial). */
1828
+ readonly tablePublic: boolean;
1829
+ /** Verified user id, or `null` when anonymous. */
1830
+ readonly userId: null | string;
1831
+ }
1832
+ /**
1833
+ * Build the read-policy registry from the registered functions (pass
1834
+ * `Object.values(LUNORA_FUNCTIONS)`). Only `on: "read"` policies are collected,
1835
+ * grouped per `rls()` middleware so each group keeps its own role→permission map
1836
+ * (a `(table, when)` pair is de-duplicated within a tag). A tag reused across
1837
+ * several procedures (a shared `const guard = rls(...)`) is folded once. This
1838
+ * mirrors the request-time `rls()` path exactly: a policy's `auth.can(...)`
1839
+ * resolves against the roles of the middleware that declared it, never a union.
1840
+ */
1841
+ declare const buildRlsReadRegistry: (functions: Iterable<unknown>) => RlsReadRegistry;
1842
+ /**
1843
+ * Compute the effective `where` a shape replicates: the table's RLS read
1844
+ * base-where AND the shape's own predicate. Returns the shape predicate
1845
+ * unchanged for a table with no read policy (a `.public()` or non-RLS table),
1846
+ * and the FALSE sentinel (replicate nothing) when a `.rls("required")` schema
1847
+ * exposes a protected, policy-less table.
1848
+ */
1849
+ declare const composeShapeReadWhere: (registry: RlsReadRegistry, request: ShapeReadWhereRequest) => WhereInput;
1850
+ /**
1851
+ * A shape declaration. `where` receives the trusted procedure context and the
1852
+ * validated client args and returns the same {@link WhereInput} shape the RLS
1853
+ * DSL uses, so the DO can AND-merge it with the table's read base-where via the
1854
+ * existing where-compiler (zero second predicate implementation).
1855
+ */
1856
+ interface ShapeDefinition<Args extends ValidatorMap = ValidatorMap, Context = QueryCtx> {
1857
+ /**
1858
+ * Validator for the client-supplied shape parameters. Validated on the DO
1859
+ * before `where` runs, so a malformed `args` envelope is rejected at the
1860
+ * subscription boundary rather than silently widening the partition. Omit
1861
+ * for a parameterless shape.
1862
+ */
1863
+ readonly args?: Args;
1864
+ /**
1865
+ * Project the replicated rows to these columns (the system columns `_id` and
1866
+ * `_creationTime` are always included). Omit to replicate every column. An
1867
+ * empty array is rejected — it would replicate no data, which is never the
1868
+ * intent.
1869
+ */
1870
+ readonly columns?: ReadonlyArray<string>;
1871
+ /** Logical table this shape replicates a partition of. */
1872
+ readonly table: string;
1873
+ /**
1874
+ * Predicate selecting the rows this shape replicates. AND-composed with the
1875
+ * table's RLS read base-where on the DO. Runs server-side with a trusted
1876
+ * `ctx` (identity/auth the client can't forge) and the validated client
1877
+ * `args`; returns a {@link WhereInput} using the same operator set as the
1878
+ * SQL compiler (`eq`/`in`/`lt`/… + `AND`/`OR`/`NOT`).
1879
+ */
1880
+ readonly where: (context: Context, args: InferValidatorMap<Args>) => WhereInput;
1881
+ }
1882
+ /** A {@link ShapeDefinition} plus the codegen discovery marker and a dispatch-shaped `compileWhere`. */
1883
+ interface RegisteredShape<Args extends ValidatorMap = ValidatorMap, Context = QueryCtx> extends ShapeDefinition<Args, Context> {
1884
+ readonly __lunoraShape: true;
1885
+ /**
1886
+ * Validate `rawArgs`, then evaluate `where` under the trusted `ctx` and
1887
+ * return its {@link WhereInput}. Used by the generated DO's `resolveShape`
1888
+ * override: `ctx` is erased to `unknown` at this dispatch boundary (the DO
1889
+ * builds it from the socket's verified identity and hands it back as the
1890
+ * concrete {@link QueryContext} the predicate expects), exactly like
1891
+ * `RegisteredLunoraFunction.handler` erases its context.
1892
+ */
1893
+ readonly compileWhere: (context: unknown, rawArgs: Record<string, unknown>) => WhereInput;
1894
+ }
1895
+ /** Declare a replication shape. See the module docs for runtime semantics. */
1896
+ declare const defineShape: <Args extends ValidatorMap = ValidatorMap, Context = QueryCtx>(definition: ShapeDefinition<Args, Context>) => RegisteredShape<Args, Context>;
1897
+ /**
1738
1898
  * Operations a storage rule can gate. `read` covers `download` / `getMetadata`
1739
1899
  * / `getSignedUrl` / `getUrl`; `write` covers `store` / `generateUploadUrl`;
1740
1900
  * `delete` is `delete`; `list` is a prefix listing (governed via the file
@@ -1817,4 +1977,4 @@ interface StorageContextIn {
1817
1977
  }
1818
1978
  declare const storageRules: <Context extends StorageContextIn = StorageContextIn>(rules: ReadonlyArray<StorageRule<Context>>, options?: StorageRulesOptions) => Middleware<Context, Context>;
1819
1979
  declare const VERSION = "0.0.0";
1820
- export { type ActionBuilder, type ActionCtx, type AggregateIndexDefinition, type AggregateIndexOptions, type AggregateOp, type ArgsValidator, type Component, type ComponentFunctions, type CreateOptions, type DataModelInit, type DefineComponentOptions, type DefinePluginOptions, type DefinePolicyInput, type DefinePresenceOptions, type DefineStorageRuleInput, type DurableObjectJurisdiction, type EmptyArgs, type EnvAccessor, type EnvKeyFailure, type EnvShape, type ExtendableSchema, type FacadeEntry, type FacadeWriterLike, type FunctionKind, type HttpActionCtx, type HttpActionHandler, type HttpMethod, type HttpRoute, type HttpRouteBuilder, type HttpRouteFactory, type HttpRouteHandlerOptions, type HttpStreamHandlerOptions, type InferArgs, type InferEnv, type InlineAggregateIndexOptions, type InlineRankIndexOptions, type InternalActionBuilder, type InternalMutationBuilder, type InternalQueryBuilder, type LifecycleEvent, type LifecycleHandler, type LunoraBuilders, LunoraEnvError, LunoraError, type LunoraErrorCode, type LunoraHttpApp, type LunoraHttpEnv, type LunoraRouteHandler, type ManyRelation, type MaskColumns, type MaskContext, type MaskFn, type MaskOptions, type MaskPolicies, type MaskStrategy, type Middleware, type MiddlewareNext, type MigrationDefinition, type MigrationDocument, type MigrationTransform, type MutationBuilder, type MutationCtx, type OnDeleteAction, type OneRelation, type OrmLike, DEFAULT_TTL_MS as PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, type Permission, type Plugin, type Policy, type PrefixedTables, type PresenceComponent, type PresenceFunctions, type PresenceMember, type ProtectPublicOptions, type QueryBuilder, type QueryCtx, type RankIndexDefinition, type RankIndexOptions, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMigration, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationBuilder, type RelationDefinition, type RlsOptions, type Role, type Schema, type SchemaExtension, type StorageOperation, type StorageRule, type StorageRuleContext, type StorageRuleDecision, type StorageRulesOptions, type TableBuilder, type TableDefinition, type TerminalKind, type TriggerBuilder, type TriggerDefinition, type TypedDefinePolicyInput, VERSION, type VectorEmbedder, type VectorIndexDefinition, type VectorIndexOptions, type VectorMetric, type VectorizeOptions, type WhereInput, asBucketStorage, bindOrm, bindTableFacade, composePluginMiddleware, createPolicyDsl, createSecrets, defineAggregateIndex, defineComponent, defineEnv, defineMigration, definePermission, definePlugin, definePolicies, definePolicy, definePresence, defineRankIndex, defineRole, defineSchema, defineSchemaExtension, defineStorageRule, defineStorageRules, defineTable, defineVectorIndex, httpAction, httpRoute, httpRouter, initLunora, installPlugins, mask, mergeSchemaExtension, onConnect, onDisconnect, presenceExtension, protectPublic, redactSecrets, rls, serveStorageObject, storageRules };
1980
+ export { type ActionBuilder, type ActionCtx, type AggregateIndexDefinition, type AggregateIndexOptions, type AggregateOp, type ArgsValidator, type Component, type ComponentFunctions, type CreateOptions, type DataModelInit, type DefineComponentOptions, type DefinePluginOptions, type DefinePolicyInput, type DefinePresenceOptions, type DefineStorageRuleInput, type DurableObjectJurisdiction, type EmptyArgs, type EnvAccessor, type EnvKeyFailure, type EnvShape, type ExtendableSchema, type FacadeEntry, type FacadeWriterLike, type FunctionKind, type HttpActionCtx, type HttpActionHandler, type HttpMethod, type HttpRoute, type HttpRouteBuilder, type HttpRouteFactory, type HttpRouteHandlerOptions, type HttpStreamHandlerOptions, type InferArgs, type InferEnv, type InlineAggregateIndexOptions, type InlineRankIndexOptions, type InternalActionBuilder, type InternalMutationBuilder, type InternalQueryBuilder, type LifecycleEvent, type LifecycleHandler, type LunoraBuilders, LunoraEnvError, LunoraError, type LunoraErrorCode, type LunoraHttpApp, type LunoraHttpEnv, type LunoraRouteHandler, type ManyRelation, type MaskColumns, type MaskContext, type MaskFn, type MaskOptions, type MaskPolicies, type MaskStrategy, type Middleware, type MiddlewareNext, type MigrationDefinition, type MigrationDocument, type MigrationTransform, type MutationBuilder, type MutationCtx, type 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, asBucketStorage, bindOrm, bindTableFacade, buildRlsReadRegistry, composePluginMiddleware, composeShapeReadWhere, createPolicyDsl, createSecrets, defineAggregateIndex, defineComponent, defineEnv, defineMigration, defineMutator, definePermission, definePlugin, definePolicies, definePolicy, definePresence, defineRankIndex, defineRole, defineSchema, defineSchemaExtension, defineShape, defineStorageRule, defineStorageRules, defineTable, defineVectorIndex, httpAction, httpRoute, httpRouter, initLunora, installPlugins, mask, mergeSchemaExtension, onConnect, onDisconnect, presenceExtension, protectPublic, redactSecrets, rls, serveStorageObject, storageRules };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  export { default as asBucketStorage } from './packem_shared/asBucketStorage-Cnxd9y2q.mjs';
2
- export { initLunora } from './packem_shared/initLunora-CATvPsVt.mjs';
2
+ export { initLunora } from './packem_shared/initLunora-lxwHTEV3.mjs';
3
3
  export { createSecrets } from './packem_shared/createSecrets-TsIP9lOa.mjs';
4
4
  export { LunoraEnvError, defineEnv, redactSecrets } from './packem_shared/LunoraEnvError-DjFkpkSP.mjs';
5
5
  export { LunoraError } from './packem_shared/LunoraError-DhggBJZF.mjs';
@@ -7,17 +7,20 @@ export { bindOrm, bindTableFacade } from './packem_shared/bindOrm-Ce57S3N9.mjs';
7
7
  export { httpAction, httpRoute, httpRouter, serveStorageObject } from './packem_shared/httpAction-B7FYUEgr.mjs';
8
8
  export { onConnect, onDisconnect } from './packem_shared/onConnect-CIPXKPyw.mjs';
9
9
  export { defineMigration } from './packem_shared/defineMigration-CAJLr6fx.mjs';
10
+ export { defineMutator } from './packem_shared/defineMutator-EIXAWhs9.mjs';
10
11
  export { composePluginMiddleware, defineComponent, definePlugin, defineSchemaExtension, installPlugins, mergeSchemaExtension } from './packem_shared/composePluginMiddleware-Ck5_TUO8.mjs';
11
- export { PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, definePresence, presenceExtension } from './packem_shared/PRESENCE_DEFAULT_TTL_MS-BgBQsqQ-.mjs';
12
+ export { PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, definePresence, presenceExtension } from './packem_shared/PRESENCE_DEFAULT_TTL_MS-C7j9gY_d.mjs';
12
13
  export { protectPublic } from './packem_shared/protectPublic-BjFkQ_Or.mjs';
13
- export { defineAggregateIndex, defineRankIndex, defineSchema, defineTable, defineVectorIndex } from './packem_shared/defineAggregateIndex-C2gT1GzM.mjs';
14
+ export { defineAggregateIndex, defineRankIndex, defineSchema, defineTable, defineVectorIndex } from './packem_shared/defineAggregateIndex-ZdyU78gh.mjs';
15
+ export { defineShape } from './packem_shared/defineShape-CJ27Wx7o.mjs';
14
16
  export { anyApi } from './types.mjs';
15
17
  export { cronJobs } from '@lunora/scheduler';
16
18
  export { ValidationError, v } from '@lunora/values';
19
+ export { buildRlsReadRegistry, composeShapeReadWhere } from './packem_shared/buildRlsReadRegistry-CkxuS69B.mjs';
17
20
  export { createPolicyDsl, definePermission, definePolicies, definePolicy, defineRole } from './packem_shared/createPolicyDsl-De67zPDS.mjs';
18
21
  export { defineStorageRule, defineStorageRules } from './packem_shared/defineStorageRule-qu0mpilX.mjs';
19
22
  export { mask } from './packem_shared/mask-eCUYOwhd.mjs';
20
- export { rls } from './packem_shared/rls-Bi9HiyDC.mjs';
23
+ export { rls } from './packem_shared/rls-ClmjkkxZ.mjs';
21
24
  export { storageRules } from './packem_shared/storageRules-4a30FSpI.mjs';
22
25
 
23
26
  const VERSION = "0.0.0";
@@ -1,9 +1,9 @@
1
1
  import { v } from '@lunora/values';
2
- import { initLunora } from './initLunora-CATvPsVt.mjs';
2
+ import { initLunora } from './initLunora-lxwHTEV3.mjs';
3
3
  import { LunoraError } from './LunoraError-DhggBJZF.mjs';
4
4
  import { onDisconnect } from './onConnect-CIPXKPyw.mjs';
5
5
  import { defineSchemaExtension, defineComponent } from './composePluginMiddleware-Ck5_TUO8.mjs';
6
- import { defineTable } from './defineAggregateIndex-C2gT1GzM.mjs';
6
+ import { defineTable } from './defineAggregateIndex-ZdyU78gh.mjs';
7
7
 
8
8
  const DEFAULT_TTL_MS = 3e4;
9
9
  const MAX_DATA_BYTES = 4096;
@@ -0,0 +1,107 @@
1
+ import { indexRolePermissions, computeReadBaseWhere, permissionName } from './rls-ClmjkkxZ.mjs';
2
+ import { r as readRlsTag } from './policy-tag-DvpVH2tv.mjs';
3
+
4
+ const FALSE_PREDICATE = { OR: [] };
5
+ const readEntryTags = (entry) => {
6
+ const hoisted = entry?.rls?.tags;
7
+ if (hoisted) {
8
+ return hoisted;
9
+ }
10
+ const tag = readRlsTag(entry);
11
+ return tag ? [tag] : [];
12
+ };
13
+ const addTagToRegistry = (byTable, tag) => {
14
+ const rolePermissions = indexRolePermissions(tag.roles);
15
+ const seenWhenByTable = /* @__PURE__ */ new Map();
16
+ const policiesByTable = /* @__PURE__ */ new Map();
17
+ for (const policy of tag.policies) {
18
+ if (policy.on !== "read") {
19
+ continue;
20
+ }
21
+ const seen = seenWhenByTable.get(policy.table) ?? /* @__PURE__ */ new Set();
22
+ if (seen.has(policy.when)) {
23
+ continue;
24
+ }
25
+ seen.add(policy.when);
26
+ seenWhenByTable.set(policy.table, seen);
27
+ const list = policiesByTable.get(policy.table) ?? [];
28
+ list.push(policy);
29
+ policiesByTable.set(policy.table, list);
30
+ }
31
+ for (const [table, policies] of policiesByTable) {
32
+ const groups = byTable.get(table) ?? [];
33
+ groups.push({ policies, rolePermissions });
34
+ byTable.set(table, groups);
35
+ }
36
+ };
37
+ const isFalsePredicate = (where) => {
38
+ const or = where.OR;
39
+ return Array.isArray(or) && or.length === 0 && Object.keys(where).length === 1;
40
+ };
41
+ const andMerge = (injected, caller) => {
42
+ if (!injected || Object.keys(injected).length === 0) {
43
+ return caller;
44
+ }
45
+ if (isFalsePredicate(injected)) {
46
+ return injected;
47
+ }
48
+ if (Object.keys(caller).length === 0) {
49
+ return injected;
50
+ }
51
+ return { AND: [injected, caller] };
52
+ };
53
+ const evaluateGroupBaseWhere = (group, request) => {
54
+ const granted = /* @__PURE__ */ new Set();
55
+ for (const roleName of request.roles) {
56
+ for (const name of group.rolePermissions.get(roleName) ?? []) {
57
+ granted.add(name);
58
+ }
59
+ }
60
+ return computeReadBaseWhere(group.policies, {
61
+ auth: {
62
+ can: (permission) => granted.has(permissionName(permission)),
63
+ identity: request.identity,
64
+ roles: request.roles,
65
+ userId: request.userId
66
+ },
67
+ ctx: request.ctx
68
+ });
69
+ };
70
+ const resolveReadBaseWhere = (registry, request) => {
71
+ const groups = registry.byTable.get(request.table);
72
+ if (!groups || groups.length === 0) {
73
+ return request.rlsRequired && !request.tablePublic ? FALSE_PREDICATE : void 0;
74
+ }
75
+ const predicates = [];
76
+ for (const group of groups) {
77
+ const base = evaluateGroupBaseWhere(group, request);
78
+ if (base === void 0) {
79
+ return void 0;
80
+ }
81
+ if (isFalsePredicate(base)) {
82
+ continue;
83
+ }
84
+ predicates.push(base);
85
+ }
86
+ if (predicates.length === 0) {
87
+ return FALSE_PREDICATE;
88
+ }
89
+ return predicates.length === 1 ? predicates[0] : { OR: predicates };
90
+ };
91
+ const buildRlsReadRegistry = (functions) => {
92
+ const byTable = /* @__PURE__ */ new Map();
93
+ const seenTags = /* @__PURE__ */ new Set();
94
+ for (const entry of functions) {
95
+ for (const tag of readEntryTags(entry)) {
96
+ if (seenTags.has(tag)) {
97
+ continue;
98
+ }
99
+ seenTags.add(tag);
100
+ addTagToRegistry(byTable, tag);
101
+ }
102
+ }
103
+ return { byTable };
104
+ };
105
+ const composeShapeReadWhere = (registry, request) => andMerge(resolveReadBaseWhere(registry, request), request.shapeWhere);
106
+
107
+ export { buildRlsReadRegistry, composeShapeReadWhere };
@@ -41,6 +41,7 @@ const defineTable = (inputShape) => {
41
41
  let isExternallyManaged = false;
42
42
  let isPublic = false;
43
43
  let softDelete;
44
+ let externalSource;
44
45
  const builder = {
45
46
  aggregateIndex(name, options) {
46
47
  const op = options?.op ?? "count";
@@ -63,6 +64,9 @@ const defineTable = (inputShape) => {
63
64
  get aggregateIndexes() {
64
65
  return aggregateIndexes;
65
66
  },
67
+ get externalSource() {
68
+ return externalSource;
69
+ },
66
70
  externallyManaged() {
67
71
  isExternallyManaged = true;
68
72
  return builder;
@@ -137,6 +141,17 @@ const defineTable = (inputShape) => {
137
141
  get softDeleteMode() {
138
142
  return softDelete;
139
143
  },
144
+ source(definition) {
145
+ if (!definition.binding) {
146
+ throw new Error("source: `binding` is required (the wrangler Hyperdrive binding name)");
147
+ }
148
+ if (!definition.query) {
149
+ throw new Error("source: `query` is required (the tenant-membership SQL)");
150
+ }
151
+ externalSource = definition;
152
+ isExternallyManaged = true;
153
+ return builder;
154
+ },
140
155
  softDelete(options) {
141
156
  const field = options?.field ?? "deletedAt";
142
157
  softDelete = { field };
@@ -243,9 +258,33 @@ const attachStandaloneIndexes = (tables, aggregateIndexes, rankIndexes) => {
243
258
  table.rankIndexes.push(index);
244
259
  }
245
260
  };
261
+ const validateExternalSources = (tables) => {
262
+ for (const [name, table] of Object.entries(tables)) {
263
+ const source = table.externalSource;
264
+ if (!source) {
265
+ continue;
266
+ }
267
+ if (table.shardMode.kind === "global") {
268
+ throw new Error(
269
+ `defineSchema: table "${name}" cannot be both .source() and .global() — a sourced table materializes into a shard DO's SQLite, a global table lives in the external tier`
270
+ );
271
+ }
272
+ if (table.shardMode.kind === "shardBy" && !source.tenantBy) {
273
+ throw new Error(
274
+ `defineSchema: sourced + .shardBy() table "${name}" 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.`
275
+ );
276
+ }
277
+ if (source.mode === "incremental") {
278
+ throw new Error(
279
+ `defineSchema: table "${name}" uses \`mode: "incremental"\`, which is not yet implemented — only "full-pull" (the default) is supported. Remove \`mode\` or set it to "full-pull".`
280
+ );
281
+ }
282
+ }
283
+ };
246
284
  const defineSchema = (tables, vectorIndexes = {}, aggregateIndexes = {}, rankIndexes = {}) => {
247
285
  fillIndexTableNames(tables);
248
286
  attachStandaloneIndexes(tables, aggregateIndexes, rankIndexes);
287
+ validateExternalSources(tables);
249
288
  return withExtend({ tables, vectorIndexes });
250
289
  };
251
290
 
@@ -0,0 +1,11 @@
1
+ import { v as validateArgs } from './functions-Di9FUNkf.mjs';
2
+
3
+ const defineMutator = (definition) => {
4
+ const handler = async (context, rawArgs) => {
5
+ const parsed = validateArgs(definition.args ?? {}, rawArgs);
6
+ return definition.server(context, parsed);
7
+ };
8
+ return { __lunoraMutator: true, ...definition, handler, kind: "mutation" };
9
+ };
10
+
11
+ export { defineMutator };
@@ -0,0 +1,17 @@
1
+ import { v as validateArgs } from './functions-Di9FUNkf.mjs';
2
+
3
+ const defineShape = (definition) => {
4
+ if (definition.table.trim() === "") {
5
+ throw new Error("defineShape: `table` must be a non-empty string");
6
+ }
7
+ if (definition.columns?.length === 0) {
8
+ throw new Error("defineShape: `columns` must list at least one column when provided");
9
+ }
10
+ const compileWhere = (context, rawArgs) => {
11
+ const parsed = validateArgs(definition.args ?? {}, rawArgs);
12
+ return definition.where(context, parsed);
13
+ };
14
+ return { __lunoraShape: true, ...definition, compileWhere };
15
+ };
16
+
17
+ export { defineShape };
@@ -0,0 +1,5 @@
1
+ import { parseValidatorMap } from '@lunora/values';
2
+
3
+ const validateArgs = (validators, args) => parseValidatorMap(validators, args, "args");
4
+
5
+ export { validateArgs as v };
@@ -1,8 +1,7 @@
1
- import { parseValidatorMap } from '@lunora/values';
1
+ import { v as validateArgs } from './functions-Di9FUNkf.mjs';
2
+ import { r as readRlsTag } from './policy-tag-DvpVH2tv.mjs';
2
3
  import { r as runMiddlewareChain } from './run-middleware-CYQOuoV6.mjs';
3
4
 
4
- const validateArgs = (validators, args) => parseValidatorMap(validators, args, "args");
5
-
6
5
  const runMiddleware = (middlewares, baseContext) => runMiddlewareChain(middlewares, baseContext, (context) => context);
7
6
  const makeHandler = (args, middlewares, userHandler, output) => async (context, rawArgs) => {
8
7
  const parsed = validateArgs(args, rawArgs);
@@ -35,16 +34,29 @@ const makeStreamHandler = (args, middlewares, userHandler) => (context, rawArgs,
35
34
  }
36
35
  })();
37
36
  };
37
+ const collectRls = (middlewares) => {
38
+ const tags = [];
39
+ for (const middleware of middlewares) {
40
+ const tag = readRlsTag(middleware);
41
+ if (!tag) {
42
+ continue;
43
+ }
44
+ tags.push(tag);
45
+ }
46
+ return tags.length > 0 ? { tags } : void 0;
47
+ };
38
48
  const makeBuilder = (kind, state, visibility) => {
39
49
  return {
40
50
  __lunoraProcedure: kind,
41
51
  ...visibility ? { __lunoraVisibility: visibility } : {},
42
52
  input: (validators) => makeBuilder(kind, { ...state, args: { ...state.args, ...validators } }, visibility),
43
53
  [kind]: (userHandler) => {
54
+ const rls = collectRls(state.middlewares);
44
55
  return {
45
56
  args: state.args,
46
57
  handler: makeHandler(state.args, state.middlewares, userHandler, state.output),
47
58
  kind,
59
+ ...rls ? { rls } : {},
48
60
  ...visibility ? { visibility } : {}
49
61
  };
50
62
  },
@@ -55,10 +67,12 @@ const makeBuilder = (kind, state, visibility) => {
55
67
  // unconditionally keeps the runtime free of per-kind branching.
56
68
  ...kind === "query" ? {
57
69
  stream: (userHandler) => {
70
+ const rls = collectRls(state.middlewares);
58
71
  return {
59
72
  args: state.args,
60
73
  handler: makeStreamHandler(state.args, state.middlewares, userHandler),
61
74
  kind: "stream",
75
+ ...rls ? { rls } : {},
62
76
  ...visibility ? { visibility } : {}
63
77
  };
64
78
  }
@@ -0,0 +1,13 @@
1
+ const RLS_TAG = /* @__PURE__ */ Symbol.for("lunora.rls.middleware-policies");
2
+ const tagRlsMiddleware = (middleware, tag) => {
3
+ Object.defineProperty(middleware, RLS_TAG, { configurable: true, enumerable: false, value: tag });
4
+ return middleware;
5
+ };
6
+ const readRlsTag = (middleware) => {
7
+ if (middleware === null || typeof middleware !== "function" && typeof middleware !== "object") {
8
+ return void 0;
9
+ }
10
+ return middleware[RLS_TAG];
11
+ };
12
+
13
+ export { readRlsTag as r, tagRlsMiddleware as t };
@@ -1,5 +1,6 @@
1
1
  import { LunoraError } from './LunoraError-DhggBJZF.mjs';
2
2
  import { bindTableFacade, bindOrm } from './bindOrm-Ce57S3N9.mjs';
3
+ import { t as tagRlsMiddleware } from './policy-tag-DvpVH2tv.mjs';
3
4
 
4
5
  const DEFAULT_BATCH_LIMIT = 500;
5
6
  const assertBatchLimit = (count, limit, op) => {
@@ -532,7 +533,7 @@ const indexRolePermissions = (roles) => {
532
533
  const rls = (policies, options = {}) => {
533
534
  const perTable = indexByTable(policies);
534
535
  const rolePermissions = indexRolePermissions(options.roles);
535
- return async ({ ctx, next }) => {
536
+ const middleware = async ({ ctx, next }) => {
536
537
  const auth = ctx.auth ?? {};
537
538
  const identity = await auth.getIdentity?.() ?? null;
538
539
  const roles = auth.roles ?? [];
@@ -562,6 +563,7 @@ const rls = (policies, options = {}) => {
562
563
  }
563
564
  return next({ ctx: extension });
564
565
  };
566
+ return tagRlsMiddleware(middleware, { policies, roles: options.roles ?? [] });
565
567
  };
566
568
 
567
569
  export { computeReadBaseWhere, evaluateWrite, indexRolePermissions, matchesWhere, permissionName, rls };
@@ -1,4 +1,4 @@
1
- import { indexRolePermissions, computeReadBaseWhere, matchesWhere, evaluateWrite, permissionName } from '../packem_shared/rls-Bi9HiyDC.mjs';
1
+ import { indexRolePermissions, computeReadBaseWhere, matchesWhere, evaluateWrite, permissionName } from '../packem_shared/rls-ClmjkkxZ.mjs';
2
2
 
3
3
  const expectPolicy = (policies, options = {}) => {
4
4
  const rolePermissions = indexRolePermissions(options.roles);
package/dist/types.d.mts CHANGED
@@ -23,6 +23,51 @@ type ShardMode = {
23
23
  } | {
24
24
  kind: "root";
25
25
  };
26
+ /** Poll cadence for a sourced table — `"manual"` (pull only on an explicit trigger) or a fixed interval. */
27
+ type ExternalSourceRefresh = "manual" | {
28
+ everyMs: number;
29
+ };
30
+ /**
31
+ * Delete-detection mode for external-source ingest. `"full-pull"` (default) reads
32
+ * the whole tenant membership each tick and diffs it, so it observes upstream
33
+ * deletes; `"incremental"` pulls only changed rows (cheap) and is blind to deletes
34
+ * unless paired with a soft-delete column or a `reconcileEveryMs` full-pull sweep.
35
+ */
36
+ type ExternalSourceMode = "full-pull" | "incremental";
37
+ /**
38
+ * Config for `.source(...)` (plan 077): declares a table as **materialized from an
39
+ * external Postgres/MySQL behind Cloudflare Hyperdrive**, not written by user
40
+ * mutations. A system-driven poll loop reads the tenant slice and lands it in the
41
+ * DO's SQLite (via the validated CDC writer), after which `defineShape` carries it
42
+ * to clients unchanged. Orthogonal to `shardMode` — a sourced table almost always
43
+ * also `.shardBy()`s, in which case `tenantBy` is the mandatory tenant-isolation
44
+ * boundary (enforced by the `external_source_unscoped` advisor lint).
45
+ */
46
+ interface ExternalSourceDefinition {
47
+ /** The wrangler Hyperdrive binding name the poll loop reads from. */
48
+ binding: string;
49
+ /** Project the materialized rows to these columns (passed to the membership diff). Omit ⇒ the full mapped document. */
50
+ columns?: ReadonlyArray<string>;
51
+ /** Column whose value becomes the Lunora `_id`. Defaults to `"id"`. */
52
+ idColumn?: string;
53
+ /** Transform an external row into the stored document body. Omit ⇒ every selected column except `idColumn` is copied. */
54
+ map?: (row: Record<string, unknown>) => Record<string, unknown>;
55
+ /** Delete-detection mode. Defaults to `"full-pull"`. */
56
+ mode?: ExternalSourceMode;
57
+ /** The full tenant-membership query, with driver-native placeholders (`$1` / `?`). `tenantBy` binds its params. */
58
+ query: string;
59
+ /** `"incremental"` only: run a full-pull reconcile this often to garbage-collect tombstones the incremental path can't see. */
60
+ reconcileEveryMs?: number;
61
+ /** Poll cadence, or `"manual"`. Omit ⇒ the runtime's size-scaled default. */
62
+ refresh?: ExternalSourceRefresh;
63
+ /**
64
+ * **Mandatory under `.shardBy()`**: map this DO's shard key → the query's bound
65
+ * params, so a tenant DO can only ever pull its own rows. An unscoped sourced +
66
+ * sharded table replicates the whole multitenant table into every shard — the
67
+ * `external_source_unscoped` advisor lint fails the build when this is absent.
68
+ */
69
+ tenantBy?: (shardKey: string) => ReadonlyArray<unknown>;
70
+ }
26
71
  interface IndexDefinition {
27
72
  fields: ReadonlyArray<string>;
28
73
  name: string;
@@ -138,6 +183,14 @@ interface TableDefinition<Shape extends Record<string, Validator> = Record<strin
138
183
  * without scanning the underlying table.
139
184
  */
140
185
  aggregateIndexes: ReadonlyArray<AggregateIndexDefinition>;
186
+ /**
187
+ * Set by `.source(...)` (named `externalSource`, not `source`, so the data
188
+ * field doesn't collide with the fluent `.source()` builder method — same
189
+ * convention as `shardBy()`/`shardMode`). When present, the table is
190
+ * materialized from an external Hyperdrive-backed database by a system poll
191
+ * loop rather than user mutations. Implies `isExternallyManaged`.
192
+ */
193
+ externalSource?: ExternalSourceDefinition;
141
194
  indexes: ReadonlyArray<IndexDefinition>;
142
195
  /**
143
196
  * `true` when `.externallyManaged()` was called — the table's rows are
@@ -1101,4 +1154,4 @@ interface ActionCtx {
1101
1154
  */
1102
1155
  type AnyApi = Record<string, Record<string, RegisteredFunction<ArgsValidator, unknown, FunctionKind>>>;
1103
1156
  declare const anyApi: AnyApi;
1104
- export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type DatabaseReader, type DatabaseWriter, type DurableObjectJurisdiction, type FunctionKind, type FunctionVisibility, type GlobalBackend, type IndexDefinition, type IndexRangeBuilder, type InferArgs, type LifecycleEvent, type LifecycleEventKind, type LunoraLogger, type MutationCtx, type OnDeleteAction, type PaginationOptions, type PaginationResult, type QueryCtx, type RankIndexDefinition, type RankSortKey, type ReadOnlyStorage, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationDefinition, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type Schema, type SearchFilterBuilder, type SearchIndexDefinition, type Secrets, type SecretsStoreSecretLike, type ShardMode, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableDefinition, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerBuilder, type TriggerCtx, type TriggerDatabase, type TriggerDefinition, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type VectorEmbedder, type VectorIndexDefinition, type VectorMatch, type VectorMatches, type VectorMetric, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi };
1157
+ export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type DatabaseReader, type DatabaseWriter, type DurableObjectJurisdiction, type ExternalSourceDefinition, type ExternalSourceMode, type ExternalSourceRefresh, type FunctionKind, type FunctionVisibility, type GlobalBackend, type IndexDefinition, type IndexRangeBuilder, type InferArgs, type LifecycleEvent, type LifecycleEventKind, type LunoraLogger, type MutationCtx, type OnDeleteAction, type PaginationOptions, type PaginationResult, type QueryCtx, type RankIndexDefinition, type RankSortKey, type ReadOnlyStorage, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationDefinition, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type Schema, type SearchFilterBuilder, type SearchIndexDefinition, type Secrets, type SecretsStoreSecretLike, type ShardMode, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableDefinition, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerBuilder, type TriggerCtx, type TriggerDatabase, type TriggerDefinition, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type VectorEmbedder, type VectorIndexDefinition, type VectorMatch, type VectorMatches, type VectorMetric, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi };
package/dist/types.d.ts CHANGED
@@ -23,6 +23,51 @@ type ShardMode = {
23
23
  } | {
24
24
  kind: "root";
25
25
  };
26
+ /** Poll cadence for a sourced table — `"manual"` (pull only on an explicit trigger) or a fixed interval. */
27
+ type ExternalSourceRefresh = "manual" | {
28
+ everyMs: number;
29
+ };
30
+ /**
31
+ * Delete-detection mode for external-source ingest. `"full-pull"` (default) reads
32
+ * the whole tenant membership each tick and diffs it, so it observes upstream
33
+ * deletes; `"incremental"` pulls only changed rows (cheap) and is blind to deletes
34
+ * unless paired with a soft-delete column or a `reconcileEveryMs` full-pull sweep.
35
+ */
36
+ type ExternalSourceMode = "full-pull" | "incremental";
37
+ /**
38
+ * Config for `.source(...)` (plan 077): declares a table as **materialized from an
39
+ * external Postgres/MySQL behind Cloudflare Hyperdrive**, not written by user
40
+ * mutations. A system-driven poll loop reads the tenant slice and lands it in the
41
+ * DO's SQLite (via the validated CDC writer), after which `defineShape` carries it
42
+ * to clients unchanged. Orthogonal to `shardMode` — a sourced table almost always
43
+ * also `.shardBy()`s, in which case `tenantBy` is the mandatory tenant-isolation
44
+ * boundary (enforced by the `external_source_unscoped` advisor lint).
45
+ */
46
+ interface ExternalSourceDefinition {
47
+ /** The wrangler Hyperdrive binding name the poll loop reads from. */
48
+ binding: string;
49
+ /** Project the materialized rows to these columns (passed to the membership diff). Omit ⇒ the full mapped document. */
50
+ columns?: ReadonlyArray<string>;
51
+ /** Column whose value becomes the Lunora `_id`. Defaults to `"id"`. */
52
+ idColumn?: string;
53
+ /** Transform an external row into the stored document body. Omit ⇒ every selected column except `idColumn` is copied. */
54
+ map?: (row: Record<string, unknown>) => Record<string, unknown>;
55
+ /** Delete-detection mode. Defaults to `"full-pull"`. */
56
+ mode?: ExternalSourceMode;
57
+ /** The full tenant-membership query, with driver-native placeholders (`$1` / `?`). `tenantBy` binds its params. */
58
+ query: string;
59
+ /** `"incremental"` only: run a full-pull reconcile this often to garbage-collect tombstones the incremental path can't see. */
60
+ reconcileEveryMs?: number;
61
+ /** Poll cadence, or `"manual"`. Omit ⇒ the runtime's size-scaled default. */
62
+ refresh?: ExternalSourceRefresh;
63
+ /**
64
+ * **Mandatory under `.shardBy()`**: map this DO's shard key → the query's bound
65
+ * params, so a tenant DO can only ever pull its own rows. An unscoped sourced +
66
+ * sharded table replicates the whole multitenant table into every shard — the
67
+ * `external_source_unscoped` advisor lint fails the build when this is absent.
68
+ */
69
+ tenantBy?: (shardKey: string) => ReadonlyArray<unknown>;
70
+ }
26
71
  interface IndexDefinition {
27
72
  fields: ReadonlyArray<string>;
28
73
  name: string;
@@ -138,6 +183,14 @@ interface TableDefinition<Shape extends Record<string, Validator> = Record<strin
138
183
  * without scanning the underlying table.
139
184
  */
140
185
  aggregateIndexes: ReadonlyArray<AggregateIndexDefinition>;
186
+ /**
187
+ * Set by `.source(...)` (named `externalSource`, not `source`, so the data
188
+ * field doesn't collide with the fluent `.source()` builder method — same
189
+ * convention as `shardBy()`/`shardMode`). When present, the table is
190
+ * materialized from an external Hyperdrive-backed database by a system poll
191
+ * loop rather than user mutations. Implies `isExternallyManaged`.
192
+ */
193
+ externalSource?: ExternalSourceDefinition;
141
194
  indexes: ReadonlyArray<IndexDefinition>;
142
195
  /**
143
196
  * `true` when `.externallyManaged()` was called — the table's rows are
@@ -1101,4 +1154,4 @@ interface ActionCtx {
1101
1154
  */
1102
1155
  type AnyApi = Record<string, Record<string, RegisteredFunction<ArgsValidator, unknown, FunctionKind>>>;
1103
1156
  declare const anyApi: AnyApi;
1104
- export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type DatabaseReader, type DatabaseWriter, type DurableObjectJurisdiction, type FunctionKind, type FunctionVisibility, type GlobalBackend, type IndexDefinition, type IndexRangeBuilder, type InferArgs, type LifecycleEvent, type LifecycleEventKind, type LunoraLogger, type MutationCtx, type OnDeleteAction, type PaginationOptions, type PaginationResult, type QueryCtx, type RankIndexDefinition, type RankSortKey, type ReadOnlyStorage, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationDefinition, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type Schema, type SearchFilterBuilder, type SearchIndexDefinition, type Secrets, type SecretsStoreSecretLike, type ShardMode, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableDefinition, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerBuilder, type TriggerCtx, type TriggerDatabase, type TriggerDefinition, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type VectorEmbedder, type VectorIndexDefinition, type VectorMatch, type VectorMatches, type VectorMetric, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi };
1157
+ export { type ActionCtx, type AggregateIndexDefinition, type AggregateOp, type AnyApi, type ArgsValidator, type AuthState, type DatabaseReader, type DatabaseWriter, type DurableObjectJurisdiction, type ExternalSourceDefinition, type ExternalSourceMode, type ExternalSourceRefresh, type FunctionKind, type FunctionVisibility, type GlobalBackend, type IndexDefinition, type IndexRangeBuilder, type InferArgs, type LifecycleEvent, type LifecycleEventKind, type LunoraLogger, type MutationCtx, type OnDeleteAction, type PaginationOptions, type PaginationResult, type QueryCtx, type RankIndexDefinition, type RankSortKey, type ReadOnlyStorage, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMutation, type RegisteredQuery, type RegisteredStream, type RelationDefinition, type ScheduledFunctionDoc, type ScheduledJob, type Scheduler, type Schema, type SearchFilterBuilder, type SearchIndexDefinition, type Secrets, type SecretsStoreSecretLike, type ShardMode, type Storage, type StorageMetadata, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemTableName, type TableDefinition, type TableReader, type TableVectorIndex, type TriggerAggregateOptions, type TriggerBuilder, type TriggerCtx, type TriggerDatabase, type TriggerDefinition, type TriggerDeleteEvent, type TriggerEvent, type TriggerGroupByEntry, type TriggerGroupByOptions, type TriggerHandler, type TriggerInsertEvent, type TriggerOp, type TriggerQueryArgs, type TriggerQueryPage, type TriggerRankOptions, type TriggerRankPageOptions, type TriggerRankResult, type TriggerRow, type TriggerTiming, type TriggerUpdateEvent, type VectorEmbedder, type VectorIndexDefinition, type VectorMatch, type VectorMatches, type VectorMetric, type VectorQueryInput, type VectorRecord, type VectorSearch, type VectorSearchReader, type VectorUpsertInput, type WorkflowCreateOptions, type WorkflowHandle, type WorkflowInstance, type WorkflowInstanceStatus, type WorkflowStatusResult, type Workflows, anyApi };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/server",
3
- "version": "1.0.0-alpha.6",
3
+ "version": "1.0.0-alpha.8",
4
4
  "description": "Server primitives for Lunora: defineSchema, defineTable, query, mutation, and action",
5
5
  "keywords": [
6
6
  "backend",