@lunora/server 1.0.0-alpha.5 → 1.0.0-alpha.7

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,4 +1,4 @@
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
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";
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";
@@ -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.
@@ -1735,6 +1784,104 @@ interface RlsContextIn {
1735
1784
  }
1736
1785
  declare const rls: <Context extends RlsContextIn = RlsContextIn>(policies: ReadonlyArray<Policy<Context>>, options?: RlsOptions) => Middleware<Context, Context>;
1737
1786
  /**
1787
+ * One `rls()` tag's read policies for a table, paired with the role→permission
1788
+ * grants of that SAME middleware. Keeping the role map per-group is what lets a
1789
+ * policy's `auth.can(...)` resolve against its own middleware's roles — never a
1790
+ * permission registered on a different `rls()` step.
1791
+ */
1792
+ interface ScopedReadPolicies {
1793
+ readonly policies: ReadonlyArray<Policy>;
1794
+ readonly rolePermissions: ReadonlyMap<string, ReadonlySet<string>>;
1795
+ }
1796
+ /** Table-indexed read-policy groups, each scoped to the roles of the rls() middleware that declared it. */
1797
+ interface RlsReadRegistry {
1798
+ readonly byTable: ReadonlyMap<string, ReadonlyArray<ScopedReadPolicies>>;
1799
+ }
1800
+ /** The trusted, server-resolved facts a shape's RLS evaluation runs under. */
1801
+ interface ShapeReadWhereRequest {
1802
+ /** The shape ctx (the procedure context a policy `when` reads as `ctx`). */
1803
+ readonly ctx: unknown;
1804
+ /** Resolved identity claims (the socket's verified identity), or `null` when anonymous. */
1805
+ readonly identity: Record<string, unknown> | null;
1806
+ /** `true` when the schema is `.rls("required")` — gates the fail-closed branch. */
1807
+ readonly rlsRequired: boolean;
1808
+ /** Role labels the request carries (drives `auth.can(...)`). */
1809
+ readonly roles: ReadonlyArray<string>;
1810
+ /** The shape's own predicate (`where(ctx, args)`). */
1811
+ readonly shapeWhere: WhereInput;
1812
+ /** Logical table the shape replicates. */
1813
+ readonly table: string;
1814
+ /** `true` when the table is `.public()` (exempt from `.rls("required")` denial). */
1815
+ readonly tablePublic: boolean;
1816
+ /** Verified user id, or `null` when anonymous. */
1817
+ readonly userId: null | string;
1818
+ }
1819
+ /**
1820
+ * Build the read-policy registry from the registered functions (pass
1821
+ * `Object.values(LUNORA_FUNCTIONS)`). Only `on: "read"` policies are collected,
1822
+ * grouped per `rls()` middleware so each group keeps its own role→permission map
1823
+ * (a `(table, when)` pair is de-duplicated within a tag). A tag reused across
1824
+ * several procedures (a shared `const guard = rls(...)`) is folded once. This
1825
+ * mirrors the request-time `rls()` path exactly: a policy's `auth.can(...)`
1826
+ * resolves against the roles of the middleware that declared it, never a union.
1827
+ */
1828
+ declare const buildRlsReadRegistry: (functions: Iterable<unknown>) => RlsReadRegistry;
1829
+ /**
1830
+ * Compute the effective `where` a shape replicates: the table's RLS read
1831
+ * base-where AND the shape's own predicate. Returns the shape predicate
1832
+ * unchanged for a table with no read policy (a `.public()` or non-RLS table),
1833
+ * and the FALSE sentinel (replicate nothing) when a `.rls("required")` schema
1834
+ * exposes a protected, policy-less table.
1835
+ */
1836
+ declare const composeShapeReadWhere: (registry: RlsReadRegistry, request: ShapeReadWhereRequest) => WhereInput;
1837
+ /**
1838
+ * A shape declaration. `where` receives the trusted procedure context and the
1839
+ * validated client args and returns the same {@link WhereInput} shape the RLS
1840
+ * DSL uses, so the DO can AND-merge it with the table's read base-where via the
1841
+ * existing where-compiler (zero second predicate implementation).
1842
+ */
1843
+ interface ShapeDefinition<Args extends ValidatorMap = ValidatorMap, Context = QueryCtx> {
1844
+ /**
1845
+ * Validator for the client-supplied shape parameters. Validated on the DO
1846
+ * before `where` runs, so a malformed `args` envelope is rejected at the
1847
+ * subscription boundary rather than silently widening the partition. Omit
1848
+ * for a parameterless shape.
1849
+ */
1850
+ readonly args?: Args;
1851
+ /**
1852
+ * Project the replicated rows to these columns (the system columns `_id` and
1853
+ * `_creationTime` are always included). Omit to replicate every column. An
1854
+ * empty array is rejected — it would replicate no data, which is never the
1855
+ * intent.
1856
+ */
1857
+ readonly columns?: ReadonlyArray<string>;
1858
+ /** Logical table this shape replicates a partition of. */
1859
+ readonly table: string;
1860
+ /**
1861
+ * Predicate selecting the rows this shape replicates. AND-composed with the
1862
+ * table's RLS read base-where on the DO. Runs server-side with a trusted
1863
+ * `ctx` (identity/auth the client can't forge) and the validated client
1864
+ * `args`; returns a {@link WhereInput} using the same operator set as the
1865
+ * SQL compiler (`eq`/`in`/`lt`/… + `AND`/`OR`/`NOT`).
1866
+ */
1867
+ readonly where: (context: Context, args: InferValidatorMap<Args>) => WhereInput;
1868
+ }
1869
+ /** A {@link ShapeDefinition} plus the codegen discovery marker and a dispatch-shaped `compileWhere`. */
1870
+ interface RegisteredShape<Args extends ValidatorMap = ValidatorMap, Context = QueryCtx> extends ShapeDefinition<Args, Context> {
1871
+ readonly __lunoraShape: true;
1872
+ /**
1873
+ * Validate `rawArgs`, then evaluate `where` under the trusted `ctx` and
1874
+ * return its {@link WhereInput}. Used by the generated DO's `resolveShape`
1875
+ * override: `ctx` is erased to `unknown` at this dispatch boundary (the DO
1876
+ * builds it from the socket's verified identity and hands it back as the
1877
+ * concrete {@link QueryContext} the predicate expects), exactly like
1878
+ * `RegisteredLunoraFunction.handler` erases its context.
1879
+ */
1880
+ readonly compileWhere: (context: unknown, rawArgs: Record<string, unknown>) => WhereInput;
1881
+ }
1882
+ /** Declare a replication shape. See the module docs for runtime semantics. */
1883
+ declare const defineShape: <Args extends ValidatorMap = ValidatorMap, Context = QueryCtx>(definition: ShapeDefinition<Args, Context>) => RegisteredShape<Args, Context>;
1884
+ /**
1738
1885
  * Operations a storage rule can gate. `read` covers `download` / `getMetadata`
1739
1886
  * / `getSignedUrl` / `getUrl`; `write` covers `store` / `generateUploadUrl`;
1740
1887
  * `delete` is `delete`; `list` is a prefix listing (governed via the file
@@ -1817,4 +1964,4 @@ interface StorageContextIn {
1817
1964
  }
1818
1965
  declare const storageRules: <Context extends StorageContextIn = StorageContextIn>(rules: ReadonlyArray<StorageRule<Context>>, options?: StorageRulesOptions) => Middleware<Context, Context>;
1819
1966
  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 };
1967
+ 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,4 +1,4 @@
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
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";
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";
@@ -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.
@@ -1735,6 +1784,104 @@ interface RlsContextIn {
1735
1784
  }
1736
1785
  declare const rls: <Context extends RlsContextIn = RlsContextIn>(policies: ReadonlyArray<Policy<Context>>, options?: RlsOptions) => Middleware<Context, Context>;
1737
1786
  /**
1787
+ * One `rls()` tag's read policies for a table, paired with the role→permission
1788
+ * grants of that SAME middleware. Keeping the role map per-group is what lets a
1789
+ * policy's `auth.can(...)` resolve against its own middleware's roles — never a
1790
+ * permission registered on a different `rls()` step.
1791
+ */
1792
+ interface ScopedReadPolicies {
1793
+ readonly policies: ReadonlyArray<Policy>;
1794
+ readonly rolePermissions: ReadonlyMap<string, ReadonlySet<string>>;
1795
+ }
1796
+ /** Table-indexed read-policy groups, each scoped to the roles of the rls() middleware that declared it. */
1797
+ interface RlsReadRegistry {
1798
+ readonly byTable: ReadonlyMap<string, ReadonlyArray<ScopedReadPolicies>>;
1799
+ }
1800
+ /** The trusted, server-resolved facts a shape's RLS evaluation runs under. */
1801
+ interface ShapeReadWhereRequest {
1802
+ /** The shape ctx (the procedure context a policy `when` reads as `ctx`). */
1803
+ readonly ctx: unknown;
1804
+ /** Resolved identity claims (the socket's verified identity), or `null` when anonymous. */
1805
+ readonly identity: Record<string, unknown> | null;
1806
+ /** `true` when the schema is `.rls("required")` — gates the fail-closed branch. */
1807
+ readonly rlsRequired: boolean;
1808
+ /** Role labels the request carries (drives `auth.can(...)`). */
1809
+ readonly roles: ReadonlyArray<string>;
1810
+ /** The shape's own predicate (`where(ctx, args)`). */
1811
+ readonly shapeWhere: WhereInput;
1812
+ /** Logical table the shape replicates. */
1813
+ readonly table: string;
1814
+ /** `true` when the table is `.public()` (exempt from `.rls("required")` denial). */
1815
+ readonly tablePublic: boolean;
1816
+ /** Verified user id, or `null` when anonymous. */
1817
+ readonly userId: null | string;
1818
+ }
1819
+ /**
1820
+ * Build the read-policy registry from the registered functions (pass
1821
+ * `Object.values(LUNORA_FUNCTIONS)`). Only `on: "read"` policies are collected,
1822
+ * grouped per `rls()` middleware so each group keeps its own role→permission map
1823
+ * (a `(table, when)` pair is de-duplicated within a tag). A tag reused across
1824
+ * several procedures (a shared `const guard = rls(...)`) is folded once. This
1825
+ * mirrors the request-time `rls()` path exactly: a policy's `auth.can(...)`
1826
+ * resolves against the roles of the middleware that declared it, never a union.
1827
+ */
1828
+ declare const buildRlsReadRegistry: (functions: Iterable<unknown>) => RlsReadRegistry;
1829
+ /**
1830
+ * Compute the effective `where` a shape replicates: the table's RLS read
1831
+ * base-where AND the shape's own predicate. Returns the shape predicate
1832
+ * unchanged for a table with no read policy (a `.public()` or non-RLS table),
1833
+ * and the FALSE sentinel (replicate nothing) when a `.rls("required")` schema
1834
+ * exposes a protected, policy-less table.
1835
+ */
1836
+ declare const composeShapeReadWhere: (registry: RlsReadRegistry, request: ShapeReadWhereRequest) => WhereInput;
1837
+ /**
1838
+ * A shape declaration. `where` receives the trusted procedure context and the
1839
+ * validated client args and returns the same {@link WhereInput} shape the RLS
1840
+ * DSL uses, so the DO can AND-merge it with the table's read base-where via the
1841
+ * existing where-compiler (zero second predicate implementation).
1842
+ */
1843
+ interface ShapeDefinition<Args extends ValidatorMap = ValidatorMap, Context = QueryCtx> {
1844
+ /**
1845
+ * Validator for the client-supplied shape parameters. Validated on the DO
1846
+ * before `where` runs, so a malformed `args` envelope is rejected at the
1847
+ * subscription boundary rather than silently widening the partition. Omit
1848
+ * for a parameterless shape.
1849
+ */
1850
+ readonly args?: Args;
1851
+ /**
1852
+ * Project the replicated rows to these columns (the system columns `_id` and
1853
+ * `_creationTime` are always included). Omit to replicate every column. An
1854
+ * empty array is rejected — it would replicate no data, which is never the
1855
+ * intent.
1856
+ */
1857
+ readonly columns?: ReadonlyArray<string>;
1858
+ /** Logical table this shape replicates a partition of. */
1859
+ readonly table: string;
1860
+ /**
1861
+ * Predicate selecting the rows this shape replicates. AND-composed with the
1862
+ * table's RLS read base-where on the DO. Runs server-side with a trusted
1863
+ * `ctx` (identity/auth the client can't forge) and the validated client
1864
+ * `args`; returns a {@link WhereInput} using the same operator set as the
1865
+ * SQL compiler (`eq`/`in`/`lt`/… + `AND`/`OR`/`NOT`).
1866
+ */
1867
+ readonly where: (context: Context, args: InferValidatorMap<Args>) => WhereInput;
1868
+ }
1869
+ /** A {@link ShapeDefinition} plus the codegen discovery marker and a dispatch-shaped `compileWhere`. */
1870
+ interface RegisteredShape<Args extends ValidatorMap = ValidatorMap, Context = QueryCtx> extends ShapeDefinition<Args, Context> {
1871
+ readonly __lunoraShape: true;
1872
+ /**
1873
+ * Validate `rawArgs`, then evaluate `where` under the trusted `ctx` and
1874
+ * return its {@link WhereInput}. Used by the generated DO's `resolveShape`
1875
+ * override: `ctx` is erased to `unknown` at this dispatch boundary (the DO
1876
+ * builds it from the socket's verified identity and hands it back as the
1877
+ * concrete {@link QueryContext} the predicate expects), exactly like
1878
+ * `RegisteredLunoraFunction.handler` erases its context.
1879
+ */
1880
+ readonly compileWhere: (context: unknown, rawArgs: Record<string, unknown>) => WhereInput;
1881
+ }
1882
+ /** Declare a replication shape. See the module docs for runtime semantics. */
1883
+ declare const defineShape: <Args extends ValidatorMap = ValidatorMap, Context = QueryCtx>(definition: ShapeDefinition<Args, Context>) => RegisteredShape<Args, Context>;
1884
+ /**
1738
1885
  * Operations a storage rule can gate. `read` covers `download` / `getMetadata`
1739
1886
  * / `getSignedUrl` / `getUrl`; `write` covers `store` / `generateUploadUrl`;
1740
1887
  * `delete` is `delete`; `list` is a prefix listing (governed via the file
@@ -1817,4 +1964,4 @@ interface StorageContextIn {
1817
1964
  }
1818
1965
  declare const storageRules: <Context extends StorageContextIn = StorageContextIn>(rules: ReadonlyArray<StorageRule<Context>>, options?: StorageRulesOptions) => Middleware<Context, Context>;
1819
1966
  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 };
1967
+ 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-DH9zLyXa.mjs';
12
13
  export { protectPublic } from './packem_shared/protectPublic-BjFkQ_Or.mjs';
13
14
  export { defineAggregateIndex, defineRankIndex, defineSchema, defineTable, defineVectorIndex } from './packem_shared/defineAggregateIndex-C2gT1GzM.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,5 +1,5 @@
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';
@@ -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 };
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/server",
3
- "version": "1.0.0-alpha.5",
3
+ "version": "1.0.0-alpha.7",
4
4
  "description": "Server primitives for Lunora: defineSchema, defineTable, query, mutation, and action",
5
5
  "keywords": [
6
6
  "backend",
@@ -65,7 +65,7 @@
65
65
  "@lunora/scheduler": "1.0.0-alpha.3",
66
66
  "@lunora/values": "1.0.0-alpha.3",
67
67
  "drizzle-orm": "^0.45.2",
68
- "hono": "^4.12.26"
68
+ "hono": "^4.12.27"
69
69
  },
70
70
  "engines": {
71
71
  "node": "^22.15.0 || >=24.11.0"