@lunora/server 1.0.0-alpha.56 → 1.0.0-alpha.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data-model.d.mts +21 -0
- package/dist/data-model.d.ts +21 -0
- package/dist/index.d.mts +885 -752
- package/dist/index.d.ts +885 -752
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/PRESENCE_DEFAULT_TTL_MS-DO2-TdzC.mjs +1 -0
- package/dist/packem_shared/buildMaskRegistry-BCIzKxdK.mjs +1 -0
- package/dist/packem_shared/{buildRlsReadRegistry-WdiqSj87.mjs → buildRlsReadRegistry-CA-aUi7I.mjs} +1 -1
- package/dist/packem_shared/composePluginMiddleware-B67TQ409.mjs +1 -0
- package/dist/packem_shared/defineAggregateIndex-DMhN3nby.mjs +1 -0
- package/dist/packem_shared/initLunora-DXCOVntr.mjs +1 -0
- package/dist/packem_shared/mask-DF7mtxQe.mjs +1 -0
- package/dist/packem_shared/optional-writer-override-CXXO30sZ.mjs +1 -0
- package/dist/packem_shared/plugin-B5pDeN17.mjs +1 -0
- package/dist/packem_shared/rls-tRDkICWZ.mjs +1 -0
- package/dist/rls/testing.mjs +1 -1
- package/package.json +4 -4
- package/dist/packem_shared/PRESENCE_DEFAULT_TTL_MS-C9RX3Cl6.mjs +0 -1
- package/dist/packem_shared/composePluginMiddleware-COr09CXA.mjs +0 -1
- package/dist/packem_shared/defineAggregateIndex-_gWNmkQZ.mjs +0 -1
- package/dist/packem_shared/initLunora-QNqOuG0A.mjs +0 -1
- package/dist/packem_shared/mask-C6Bi78qj.mjs +0 -1
- package/dist/packem_shared/rls-_iVsPvhX.mjs +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -980,860 +980,986 @@ declare const clampLimit: (limit: number | undefined, fallback: number, maxLimit
|
|
|
980
980
|
*/
|
|
981
981
|
declare const defineListArgs: <TDocument>() => <F extends ListFilterShape<TDocument>, O extends keyof TDocument & string>(config: DefineListArgsConfig<F, O>) => ListArgsSpec<TDocument, F, O>;
|
|
982
982
|
/**
|
|
983
|
-
*
|
|
984
|
-
* `
|
|
985
|
-
*
|
|
986
|
-
*
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
*
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
983
|
+
* Structural mirrors of `@lunora/shard-engine`'s rank-page-row shapes
|
|
984
|
+
* (`RankPageRowKey` / `RankPageRow` / `ShardRankPageResult`) — the return type
|
|
985
|
+
* of the writer's `rankPageRows` seam, the cross-shard companion to
|
|
986
|
+
* `rankPage`.
|
|
987
|
+
*
|
|
988
|
+
* Shared by `../rls/middleware` and `../mask/middleware`: both wrap
|
|
989
|
+
* `rankPageRows` structurally (no `@lunora/shard-engine` import, mirroring how
|
|
990
|
+
* every other method on their `DatabaseWriterLike`/`MaskDatabase` projections
|
|
991
|
+
* is hand-mirrored rather than imported) and both need the exact same result
|
|
992
|
+
* shape to type their overrides. A single copy here means the two wrappers
|
|
993
|
+
* can't drift out of lockstep with each other — see AGENTS.md's platform
|
|
994
|
+
* parity note on `ShardSqlExec` and the canonical binding `*Like` projections
|
|
995
|
+
* shipping wrong for exactly this reason (two hand-maintained mirrors of one
|
|
996
|
+
* upstream type).
|
|
997
|
+
*/
|
|
998
|
+
/** Structural mirror of `@lunora/shard-engine`'s `RankPageRowKey`. */
|
|
999
|
+
interface RankPageRowKeyLike {
|
|
1000
|
+
partitionKey: string;
|
|
1001
|
+
rowId: string;
|
|
1002
|
+
sortValues: ReadonlyArray<unknown>;
|
|
1003
|
+
}
|
|
1004
|
+
/** Structural mirror of `@lunora/shard-engine`'s `RankPageRow`. */
|
|
1005
|
+
interface RankPageRowLike {
|
|
1006
|
+
doc: Record<string, unknown>;
|
|
1007
|
+
key: RankPageRowKeyLike;
|
|
1008
|
+
}
|
|
1009
|
+
/** Structural mirror of `@lunora/shard-engine`'s `ShardRankPageResult` — the `rankPageRows` return shape. */
|
|
1010
|
+
interface ShardRankPageResultLike {
|
|
1011
|
+
directions: ReadonlyArray<"asc" | "desc">;
|
|
1012
|
+
hasMore: boolean;
|
|
1013
|
+
rows: ReadonlyArray<RankPageRowLike>;
|
|
1004
1014
|
}
|
|
1005
1015
|
/**
|
|
1006
|
-
*
|
|
1007
|
-
* {@link
|
|
1008
|
-
*
|
|
1009
|
-
* or format-preserving tokens. A function that **throws** fails closed — the
|
|
1010
|
-
* cell is redacted to `null`, never leaked raw.
|
|
1016
|
+
* The prefixed tables a single plugin `P` contributes, or an empty map when it
|
|
1017
|
+
* ships no schema extension. Mirrors {@link PrefixedTables} at the plugin level
|
|
1018
|
+
* so {@link InstalledTables} can fold a tuple of plugins.
|
|
1011
1019
|
*/
|
|
1012
|
-
type
|
|
1020
|
+
type ExtensionTablesOf<P> = P extends {
|
|
1021
|
+
readonly extension: SchemaExtension<infer X> & {
|
|
1022
|
+
readonly key: infer K;
|
|
1023
|
+
};
|
|
1024
|
+
} ? K extends string ? PrefixedTables<X, K> : Record<never, never> : Record<never, never>;
|
|
1013
1025
|
/**
|
|
1014
|
-
*
|
|
1015
|
-
*
|
|
1016
|
-
*
|
|
1017
|
-
* the right choice for any value that must actually be kept secret.
|
|
1018
|
-
* - `"hash"` — replace with a stable token (unsalted 32-bit FNV-1a hex) so the
|
|
1019
|
-
* same input always yields the same token (joinable/groupable client-side).
|
|
1020
|
-
* **This is NOT a confidentiality control.** It is a non-cryptographic,
|
|
1021
|
-
* unsalted, deterministic, narrow (~2^32) digest: low-entropy values (emails,
|
|
1022
|
-
* phone numbers, SSNs) are brute-force-recoverable by the very caller you are
|
|
1023
|
-
* masking from, and identical values always produce identical tokens across
|
|
1024
|
-
* rows/columns/tenants (enabling correlation). Use `"hash"` ONLY when you want a
|
|
1025
|
-
* stable pseudonym for grouping/joining and leaking the value is acceptable —
|
|
1026
|
-
* never to hide sensitive PII. For PII that must stay hidden, use `"redact"`.
|
|
1027
|
-
* - a {@link MaskFn} — author-defined transform (partial mask, role-aware reveal).
|
|
1026
|
+
* Fold a tuple of plugins onto a base table map `T`, accumulating each plugin's
|
|
1027
|
+
* auto-prefixed extension tables left-to-right — the type-level mirror of
|
|
1028
|
+
* {@link installPlugins} applying `mergeSchemaExtension` for each plugin in turn.
|
|
1028
1029
|
*/
|
|
1029
|
-
type
|
|
1030
|
-
/** Per-column strategy map for one table: `{ email: "redact", phone: maskMiddle }`. */
|
|
1031
|
-
type MaskColumns<Context = unknown> = Record<string, MaskStrategy<Context>>;
|
|
1030
|
+
type InstalledTables<T extends Record<string, TableDefinition>, Plugins extends ReadonlyArray<unknown>> = Plugins extends readonly [infer Head, ...infer Rest] ? InstalledTables<ExtensionTablesOf<Head> & T, Rest> : T;
|
|
1032
1031
|
/**
|
|
1033
|
-
*
|
|
1034
|
-
*
|
|
1035
|
-
*
|
|
1036
|
-
*
|
|
1032
|
+
* Union every plugin's `ContextOut` in a tuple — the type-level mirror of the
|
|
1033
|
+
* `ctx.api.<key>` additions {@link composePluginMiddleware} accumulates as each
|
|
1034
|
+
* plugin middleware runs. Independent of the incoming context, which the builder
|
|
1035
|
+
* infers at the `.use(...)` site.
|
|
1037
1036
|
*/
|
|
1038
|
-
type
|
|
1037
|
+
type ComposedOut<Plugins extends ReadonlyArray<unknown>> = Plugins extends readonly [infer Head, ...infer Rest] ? ComposedOut<Rest> & (Head extends Plugin<any, any, infer Out> ? Out : unknown) : unknown;
|
|
1039
1038
|
/**
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1043
|
-
*
|
|
1044
|
-
* not listed grants no permissions (fails closed for unknown roles).
|
|
1045
|
-
* - `bypass` is a procedure-wide escape hatch: when it returns `true` the whole
|
|
1046
|
-
* mask is skipped (the caller sees raw values). Use it for a privileged
|
|
1047
|
-
* viewer — `bypass: ({ auth }) => auth.can("pii:view")`. Prefer this over
|
|
1048
|
-
* branching every column when an entire class of caller should see clear data.
|
|
1039
|
+
* Schema fragment a plugin contributes. Same shape as the `tables` map
|
|
1040
|
+
* passed to `defineSchema`. Optional `vectorIndexes` mirror the top-level
|
|
1041
|
+
* `defineSchema` argument so a plugin can ship vector decls alongside its
|
|
1042
|
+
* tables.
|
|
1049
1043
|
*/
|
|
1050
|
-
interface
|
|
1051
|
-
|
|
1052
|
-
readonly
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1044
|
+
interface SchemaExtension<T extends Record<string, TableDefinition> = Record<string, TableDefinition>> {
|
|
1045
|
+
/** Stable key identifying the plugin that owns this extension. */
|
|
1046
|
+
readonly key: string;
|
|
1047
|
+
/**
|
|
1048
|
+
* Extension tables, keyed by **bare** name (e.g. `buckets`). At merge time
|
|
1049
|
+
* each is auto-prefixed with `key` (`ratelimit_buckets`) so it can't
|
|
1050
|
+
* collide with an app table; do **not** namespace manually.
|
|
1051
|
+
*/
|
|
1052
|
+
readonly tables: T;
|
|
1053
|
+
/**
|
|
1054
|
+
* Optional standalone vector indexes the plugin ships, keyed by index
|
|
1055
|
+
* name. Merged into the host schema's `vectorIndexes`; a key collision
|
|
1056
|
+
* with the base schema is a hard error (same policy as tables).
|
|
1057
|
+
*/
|
|
1058
|
+
readonly vectorIndexes?: Record<string, VectorIndexDefinition>;
|
|
1058
1059
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1060
|
+
/**
|
|
1061
|
+
* Build a {@link SchemaExtension}. The `key` is a runtime tag (used for
|
|
1062
|
+
* error messages on collision) and a type-level brand.
|
|
1063
|
+
*/
|
|
1064
|
+
declare const defineSchemaExtension: <T extends Record<string, TableDefinition>>(key: string, options: {
|
|
1065
|
+
tables: T;
|
|
1066
|
+
vectorIndexes?: Record<string, VectorIndexDefinition>;
|
|
1067
|
+
}) => SchemaExtension<T>;
|
|
1068
|
+
/**
|
|
1069
|
+
* A plugin packages an optional schema extension and optional middleware.
|
|
1070
|
+
* Both are independently usable: an app can install only the schema (e.g.
|
|
1071
|
+
* for plugins that ship background workers but no per-request behavior)
|
|
1072
|
+
* or only the middleware (plugins that augment ctx without persistent
|
|
1073
|
+
* state).
|
|
1074
|
+
*/
|
|
1075
|
+
interface Plugin<TExtension extends Record<string, TableDefinition> = Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn> {
|
|
1076
|
+
/**
|
|
1077
|
+
* Optional schema extension. Apps install via
|
|
1078
|
+
* `defineSchema(...).extend(plugin.extension)`.
|
|
1079
|
+
*/
|
|
1080
|
+
readonly extension?: SchemaExtension<TExtension>;
|
|
1081
|
+
/** Stable key identifying the plugin. Matches `extension.key` when set. */
|
|
1082
|
+
readonly key: string;
|
|
1083
|
+
/**
|
|
1084
|
+
* Optional middleware. Users attach with `c.query.use(plugin.middleware)`.
|
|
1085
|
+
* The middleware can extend `ctx`; convention is to attach helpers under
|
|
1086
|
+
* `ctx.api.<key>`, e.g.
|
|
1087
|
+
*
|
|
1088
|
+
* ```ts
|
|
1089
|
+
* middleware: ({ ctx, next }) =>
|
|
1090
|
+
* next({ ctx: { api: { ...ctx.api, ratelimit: api } } })
|
|
1091
|
+
* ```
|
|
1092
|
+
*/
|
|
1093
|
+
readonly middleware?: Middleware<TContextIn, TContextOut>;
|
|
1066
1094
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1095
|
+
/** Options to {@link definePlugin}. */
|
|
1096
|
+
interface DefinePluginOptions<TExtension extends Record<string, TableDefinition>, TContextIn, TContextOut> {
|
|
1097
|
+
extension?: SchemaExtension<TExtension>;
|
|
1098
|
+
middleware?: Middleware<TContextIn, TContextOut>;
|
|
1071
1099
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1100
|
+
/**
|
|
1101
|
+
* Call signatures for {@link definePlugin}. When `extension` is supplied the
|
|
1102
|
+
* returned plugin's `extension` is typed as PRESENT (not `?`), so the
|
|
1103
|
+
* canonical install pattern `defineSchema(...).extend(plugin.extension)`
|
|
1104
|
+
* typechecks without a non-null assertion — the shape every scaffold template
|
|
1105
|
+
* ships. The bare-options signature keeps `extension` optional for plugins
|
|
1106
|
+
* that carry only middleware.
|
|
1107
|
+
*/
|
|
1108
|
+
interface DefinePluginFunction {
|
|
1109
|
+
<TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut> & {
|
|
1110
|
+
extension: SchemaExtension<TExtension>;
|
|
1111
|
+
}): Plugin<TExtension, TContextIn, TContextOut> & {
|
|
1112
|
+
readonly extension: SchemaExtension<TExtension>;
|
|
1076
1113
|
};
|
|
1077
|
-
|
|
1078
|
-
where?: unknown;
|
|
1114
|
+
<TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut>): Plugin<TExtension, TContextIn, TContextOut>;
|
|
1079
1115
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1116
|
+
/**
|
|
1117
|
+
* Package a schema extension + middleware as a reusable plugin. Either
|
|
1118
|
+
* field is optional — `definePlugin("foo", {})` is valid but degenerate.
|
|
1119
|
+
*/
|
|
1120
|
+
declare const definePlugin: DefinePluginFunction;
|
|
1121
|
+
/**
|
|
1122
|
+
* Bundle of registered functions a {@link Component} ships. Keys are the
|
|
1123
|
+
* function's local name (e.g. `check`, `reset`); the registered function
|
|
1124
|
+
* value carries its own kind / args / handler.
|
|
1125
|
+
*
|
|
1126
|
+
* Users re-export from their own lunora module so codegen picks them up:
|
|
1127
|
+
*
|
|
1128
|
+
* ```ts
|
|
1129
|
+
* // lunora/ratelimit.ts
|
|
1130
|
+
* import { ratelimit } from "@vendor/ratelimit-component";
|
|
1131
|
+
* export const { check, reset } = ratelimit.functions;
|
|
1132
|
+
* // Emits as `ratelimit:check` / `ratelimit:reset` in the generated `api`.
|
|
1133
|
+
* ```
|
|
1134
|
+
*
|
|
1135
|
+
* Codegen follows the re-export back to the bundled `query/mutation/action`
|
|
1136
|
+
* call (property access or destructuring both work), so the functions land in
|
|
1137
|
+
* the generated `api` under the re-exporting file's namespace.
|
|
1138
|
+
*/
|
|
1139
|
+
type ComponentFunctions = Readonly<Record<string, RegisteredFunction<any, any, FunctionKind>>>;
|
|
1140
|
+
/**
|
|
1141
|
+
* Component = {@link Plugin} with a bundle of registered functions. The
|
|
1142
|
+
* extension + middleware + functions are independent: a component can ship
|
|
1143
|
+
* functions without a schema (e.g. a stateless utility), or a schema
|
|
1144
|
+
* without functions (e.g. shared table definitions), and any combination.
|
|
1145
|
+
*/
|
|
1146
|
+
interface Component<TExtension extends Record<string, TableDefinition> = Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn, F extends ComponentFunctions = ComponentFunctions> extends Plugin<TExtension, TContextIn, TContextOut> {
|
|
1147
|
+
readonly functions: F;
|
|
1148
|
+
}
|
|
1149
|
+
interface DefineComponentOptions<TExtension extends Record<string, TableDefinition>, TContextIn, TContextOut, F extends ComponentFunctions> extends DefinePluginOptions<TExtension, TContextIn, TContextOut> {
|
|
1150
|
+
/** Registered functions the component ships. Keys are the function's local name. */
|
|
1151
|
+
functions?: F;
|
|
1094
1152
|
}
|
|
1095
1153
|
/**
|
|
1096
|
-
*
|
|
1097
|
-
*
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1154
|
+
* Convenience wrapper around {@link definePlugin} that also bundles a set
|
|
1155
|
+
* of registered functions. The resulting `component.functions` object is a
|
|
1156
|
+
* record of `name → registered query/mutation/action`; consumers
|
|
1157
|
+
* re-export entries so codegen discovers them as user functions:
|
|
1158
|
+
*
|
|
1159
|
+
* ```ts
|
|
1160
|
+
* export const ratelimit = defineComponent("ratelimit", {
|
|
1161
|
+
* // Bare `buckets` merges in as `ratelimit_buckets`.
|
|
1162
|
+
* extension: defineSchemaExtension("ratelimit", { tables: { buckets } }),
|
|
1163
|
+
* middleware: ({ ctx, next }) => next({ ctx: { ...ctx, ratelimit: api(ctx) } }),
|
|
1164
|
+
* functions: {
|
|
1165
|
+
* check: query.input({ key: v.string() }).query(async ({ ctx, args }) => ...),
|
|
1166
|
+
* reset: mutation.input({ key: v.string() }).mutation(async ({ ctx, args }) => ...),
|
|
1167
|
+
* },
|
|
1168
|
+
* });
|
|
1169
|
+
* ```
|
|
1170
|
+
*
|
|
1171
|
+
* Re-exporting an entry (by property access or destructuring) is enough for
|
|
1172
|
+
* codegen to discover it in the host app's namespace — the discovery resolver
|
|
1173
|
+
* chases the re-export back to the bundled registration call.
|
|
1100
1174
|
*/
|
|
1101
|
-
|
|
1102
|
-
aggregate: (tableName: string, options: AggregateArgs$1) => Promise<null | number>;
|
|
1103
|
-
count: (tableName: string, whereOrArgs?: unknown) => Promise<number>;
|
|
1104
|
-
delete: (id: string, expectedTable?: string) => Promise<void>;
|
|
1105
|
-
deleteMany: (ids: ReadonlyArray<string>, options?: {
|
|
1106
|
-
limit?: number;
|
|
1107
|
-
}) => Promise<{
|
|
1108
|
-
deleted: number;
|
|
1109
|
-
}>;
|
|
1110
|
-
deleteWhere?: (tableName: string, where: Record<string, unknown>, options?: {
|
|
1111
|
-
limit?: number;
|
|
1112
|
-
}) => Promise<{
|
|
1113
|
-
deleted: number;
|
|
1114
|
-
}>;
|
|
1115
|
-
findFirst: (tableName: string, args?: QueryArgs$1) => Promise<Record<string, unknown> | null>;
|
|
1116
|
-
findFirstOrThrow: (tableName: string, args?: QueryArgs$1) => Promise<Record<string, unknown>>;
|
|
1117
|
-
findMany: (tableName: string, args?: QueryArgs$1) => Promise<QueryPage$1>;
|
|
1118
|
-
get: (id: string, expectedTable?: string) => Promise<Record<string, unknown> | null>;
|
|
1119
|
-
groupBy: (tableName: string, options: GroupByArgs$1) => Promise<ReadonlyArray<{
|
|
1120
|
-
key: Record<string, unknown>;
|
|
1121
|
-
value: null | number;
|
|
1122
|
-
}>>;
|
|
1123
|
-
insert: (tableName: string, document: Record<string, unknown>) => Promise<string>;
|
|
1124
|
-
insertMany: (tableName: string, documents: ReadonlyArray<Record<string, unknown>>, options?: {
|
|
1125
|
-
limit?: number;
|
|
1126
|
-
skipDuplicates?: boolean;
|
|
1127
|
-
}) => Promise<(string | null)[]>;
|
|
1128
|
-
lookupById?: (id: string, expectedTable?: string) => Promise<null | {
|
|
1129
|
-
row: Record<string, unknown>;
|
|
1130
|
-
tableName: string;
|
|
1131
|
-
}>;
|
|
1132
|
-
patch: (id: string, patch: Record<string, unknown>, expectedTable?: string) => Promise<void>;
|
|
1133
|
-
patchMany: (patches: ReadonlyArray<{
|
|
1134
|
-
id: string;
|
|
1135
|
-
patch: Record<string, unknown>;
|
|
1136
|
-
}>, options?: {
|
|
1137
|
-
limit?: number;
|
|
1138
|
-
}) => Promise<{
|
|
1139
|
-
patched: number;
|
|
1140
|
-
}>;
|
|
1141
|
-
patchWhere?: (tableName: string, args: {
|
|
1142
|
-
patch: Record<string, unknown>;
|
|
1143
|
-
where: Record<string, unknown>;
|
|
1144
|
-
}, options?: {
|
|
1145
|
-
limit?: number;
|
|
1146
|
-
}) => Promise<{
|
|
1147
|
-
patched: number;
|
|
1148
|
-
}>;
|
|
1149
|
-
query: (tableName: string) => TableReaderLike$1;
|
|
1150
|
-
rank: (tableName: string, indexName: string, options: unknown) => Promise<null | {
|
|
1151
|
-
position: number;
|
|
1152
|
-
total: number;
|
|
1153
|
-
}>;
|
|
1154
|
-
rankBefore?: (tableName: string, indexName: string, options: unknown) => Promise<{
|
|
1155
|
-
before: number;
|
|
1156
|
-
total: number;
|
|
1157
|
-
}>;
|
|
1158
|
-
rankPage: (tableName: string, indexName: string, options?: unknown) => Promise<QueryPage$1>;
|
|
1159
|
-
replace: (id: string, document: Record<string, unknown>, expectedTable?: string) => Promise<void>;
|
|
1160
|
-
}
|
|
1161
|
-
/** Roles list source on the context. Tolerant of older auth states (mirrors RLS's `AuthLike`). */
|
|
1162
|
-
type AuthLike$1 = {
|
|
1163
|
-
getIdentity?: () => Promise<Record<string, unknown> | null>;
|
|
1164
|
-
roles?: ReadonlyArray<string>;
|
|
1165
|
-
userId?: null | string;
|
|
1166
|
-
};
|
|
1167
|
-
interface MaskContextIn {
|
|
1168
|
-
auth?: AuthLike$1;
|
|
1169
|
-
db: MaskDatabase;
|
|
1170
|
-
}
|
|
1175
|
+
declare const defineComponent: <TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn, F extends ComponentFunctions = ComponentFunctions>(key: string, options: DefineComponentOptions<TExtension, TContextIn, TContextOut, F>) => Component<TExtension, TContextIn, TContextOut, F>;
|
|
1171
1176
|
/**
|
|
1172
|
-
*
|
|
1173
|
-
*
|
|
1174
|
-
* the `
|
|
1175
|
-
*
|
|
1176
|
-
* IMPORTANT: a mask is in scope only for procedures whose builder chain
|
|
1177
|
-
* includes this middleware — opt-in, never global (the same invariant as RLS).
|
|
1177
|
+
* Map every key `K` of an extension's table map `X` to its auto-prefixed name
|
|
1178
|
+
* `${Key}_${K}`. Mirrors the runtime prefixing in {@link mergeSchemaExtension}
|
|
1179
|
+
* so the typed `.extend(...)` chain reflects the real merged table names.
|
|
1178
1180
|
*/
|
|
1179
|
-
|
|
1180
|
-
/** A document handed to a migration transform: the stored row including `_id`/`_creationTime`. */
|
|
1181
|
-
type MigrationDocument = Record<string, unknown>;
|
|
1181
|
+
type PrefixedTables<X extends Record<string, TableDefinition>, Key extends string> = { [K in keyof X as K extends string ? `${Key}_${K}` : K]: X[K]; };
|
|
1182
1182
|
/**
|
|
1183
|
-
*
|
|
1183
|
+
* Merge a {@link SchemaExtension} into an existing schema. Returns a new
|
|
1184
|
+
* schema object — never mutates the input.
|
|
1184
1185
|
*
|
|
1185
|
-
*
|
|
1186
|
-
*
|
|
1187
|
-
*
|
|
1186
|
+
* Extension tables are auto-namespaced: each bare table name is prefixed with
|
|
1187
|
+
* the extension `key` (`buckets` → `ratelimit_buckets`), Convex-Components
|
|
1188
|
+
* style, and every intra-extension reference (relation targets, aggregate /
|
|
1189
|
+
* rank index `on`, standalone vector index `table`) is rewritten to match.
|
|
1190
|
+
* References to base/app tables are left untouched.
|
|
1191
|
+
*
|
|
1192
|
+
* Because each extension lives in its own `key` namespace, app↔component
|
|
1193
|
+
* collisions are impossible. The only remaining hard error is two extensions
|
|
1194
|
+
* sharing the same `key` and producing the same prefixed table (or vector
|
|
1195
|
+
* index) name — silent shadow would let one plugin hijack another's data.
|
|
1196
|
+
*
|
|
1197
|
+
* Re-runs {@link validateIndexFields} against the merged table set before
|
|
1198
|
+
* returning: `defineSchema` only validates the tables it was called with, so
|
|
1199
|
+
* without this an extension-contributed index with a typo'd/out-of-shape
|
|
1200
|
+
* field (or a duplicate name within one kind) would never be checked at all.
|
|
1201
|
+
* Re-validating the whole merged set (base + prefixed extension tables) is
|
|
1202
|
+
* cheap and idempotent for the base tables, which already passed this same
|
|
1203
|
+
* check when the base schema was built. Both callers of this function —
|
|
1204
|
+
* `withExtend.extend()` (`./schema`) and `installPlugins` (below) — get the
|
|
1205
|
+
* re-validation for free from this single call site (plan 258 §4/§9 Q3).
|
|
1188
1206
|
*/
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
findMany: (table: string, args?: Record<string, unknown>) => Promise<{
|
|
1193
|
-
isDone: boolean;
|
|
1194
|
-
page: MigrationDocument[];
|
|
1195
|
-
}>;
|
|
1196
|
-
get: (id: string, expectedTable?: string) => Promise<MigrationDocument | null>;
|
|
1197
|
-
}
|
|
1198
|
-
/** The context handed to a transform alongside the row. */
|
|
1199
|
-
interface MigrationCtx {
|
|
1200
|
-
db: MigrationReader;
|
|
1201
|
-
}
|
|
1207
|
+
declare const mergeSchemaExtension: <T extends Record<string, TableDefinition>, X extends Record<string, TableDefinition>, Key extends string = string>(base: Schema<T>, extension: SchemaExtension<X> & {
|
|
1208
|
+
readonly key: Key;
|
|
1209
|
+
}) => Schema<PrefixedTables<X, Key> & T>;
|
|
1202
1210
|
/**
|
|
1203
|
-
*
|
|
1204
|
-
*
|
|
1205
|
-
*
|
|
1206
|
-
*
|
|
1211
|
+
* Install several plugins' schema extensions in one call — the one-shot
|
|
1212
|
+
* counterpart to chaining `defineSchema(...).extend(a).extend(b)`. Plugins
|
|
1213
|
+
* without an `extension` (middleware-only) are skipped; tables from those that
|
|
1214
|
+
* do are auto-prefixed and reference-rewritten exactly as
|
|
1215
|
+
* {@link mergeSchemaExtension} does for a single `.extend(...)`.
|
|
1207
1216
|
*
|
|
1208
|
-
*
|
|
1209
|
-
*
|
|
1210
|
-
*
|
|
1211
|
-
*
|
|
1212
|
-
* down onto its children.
|
|
1217
|
+
* ```ts
|
|
1218
|
+
* const schema = installPlugins(defineSchema({ todos }), [ratelimit, audit]);
|
|
1219
|
+
* // → todos + ratelimit_* + audit_*
|
|
1220
|
+
* ```
|
|
1213
1221
|
*
|
|
1214
|
-
*
|
|
1222
|
+
* Pair it with {@link composePluginMiddleware} to attach every plugin's
|
|
1223
|
+
* middleware in a single `.use(...)`, so installing N plugins is two calls
|
|
1224
|
+
* rather than N `.extend(...)` + N `.use(...)`.
|
|
1225
|
+
*/
|
|
1226
|
+
declare const installPlugins: <T extends Record<string, TableDefinition>, const Plugins extends ReadonlyArray<Plugin<any, any, any>>>(base: Schema<T>, plugins: Plugins) => Schema<InstalledTables<T, Plugins>>;
|
|
1227
|
+
/**
|
|
1228
|
+
* Compose every plugin's middleware into a single middleware you attach with one
|
|
1229
|
+
* `.use(...)`. Plugins without middleware (schema-only) are skipped; the rest run
|
|
1230
|
+
* in array order, each seeing the context the previous one widened, so the final
|
|
1231
|
+
* `next({ ctx })` the builder receives carries every plugin's `ctx.api.<key>`
|
|
1232
|
+
* additions. Equivalent to `.use(a.middleware).use(b.middleware)…` but as one
|
|
1233
|
+
* value, the middleware sibling of {@link installPlugins}.
|
|
1215
1234
|
*
|
|
1216
|
-
*
|
|
1217
|
-
*
|
|
1218
|
-
*
|
|
1219
|
-
* different Durable Object, which a per-shard runner cannot do. Re-keying is an
|
|
1220
|
-
* export → transform → import, not a migration.
|
|
1235
|
+
* `ContextIn` is left free so the builder infers it from the context at the
|
|
1236
|
+
* `.use(...)` site; the result type widens it by the union of the plugins'
|
|
1237
|
+
* outputs.
|
|
1221
1238
|
*/
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
/** Forward transform, applied to every row by `migrate up`. */
|
|
1233
|
-
readonly up: MigrationTransform;
|
|
1239
|
+
declare const composePluginMiddleware: <ContextIn = unknown, const Plugins extends ReadonlyArray<Plugin<any, any, any>> = ReadonlyArray<Plugin<any, any, any>>>(plugins: Plugins) => Middleware<ContextIn, ComposedOut<Plugins> & ContextIn>;
|
|
1240
|
+
/** Options for `.vectorize(field, opts)` (DSL Shape A). */
|
|
1241
|
+
interface VectorizeOptions<Shape extends Record<string, Validator> = Record<string, Validator>> {
|
|
1242
|
+
dimensions: number;
|
|
1243
|
+
embed: VectorEmbedder;
|
|
1244
|
+
/** Logical index name; must match a `[[vectorize]]` binding in wrangler. */
|
|
1245
|
+
index: string;
|
|
1246
|
+
/** Fields mirrored into Vectorize metadata for filtering. */
|
|
1247
|
+
metadata?: ReadonlyArray<keyof Shape & string>;
|
|
1248
|
+
metric: VectorMetric;
|
|
1234
1249
|
}
|
|
1235
|
-
/** A
|
|
1236
|
-
interface
|
|
1237
|
-
readonly
|
|
1250
|
+
/** A `one` (many-to-one) relation descriptor; phantom `Target` carries the target table name. */
|
|
1251
|
+
interface OneRelation<Target extends string = string> extends RelationDefinition {
|
|
1252
|
+
readonly __target?: Target;
|
|
1253
|
+
readonly kind: "one";
|
|
1254
|
+
}
|
|
1255
|
+
/** A `many` (one-to-many) relation descriptor; phantom `Target` carries the target table name. */
|
|
1256
|
+
interface ManyRelation<Target extends string = string> extends RelationDefinition {
|
|
1257
|
+
readonly __target?: Target;
|
|
1258
|
+
readonly kind: "many";
|
|
1259
|
+
}
|
|
1260
|
+
/** The `r` argument passed to `.relations((r) => …)`. */
|
|
1261
|
+
interface RelationBuilder {
|
|
1262
|
+
/** One-to-many: the FK `field` lives on the target table, matching this table's `references` (default `_id`). */
|
|
1263
|
+
many: <Target extends string>(table: Target, options: {
|
|
1264
|
+
field: string;
|
|
1265
|
+
references?: string;
|
|
1266
|
+
}) => ManyRelation<Target>;
|
|
1267
|
+
/** Many-to-one: the FK `field` lives on this table, pointing at `table`.`references` (default `_id`). */
|
|
1268
|
+
one: <Target extends string>(table: Target, options: {
|
|
1269
|
+
field: string;
|
|
1270
|
+
onDelete?: OnDeleteAction;
|
|
1271
|
+
references?: string;
|
|
1272
|
+
}) => OneRelation<Target>;
|
|
1238
1273
|
}
|
|
1239
|
-
/** Declare an online data migration. See the module docs for runtime semantics. */
|
|
1240
|
-
declare const defineMigration: (definition: MigrationDefinition) => RegisteredMigration;
|
|
1241
1274
|
/**
|
|
1242
|
-
*
|
|
1243
|
-
*
|
|
1244
|
-
*
|
|
1245
|
-
* `args`.
|
|
1275
|
+
* Options for the inline `.aggregateIndex(name, opts)` builder. `op` defaults to
|
|
1276
|
+
* `count` so `aggregateIndex("byUser", { by: ["userId"] })` is a single-line
|
|
1277
|
+
* `COUNT(*) GROUP BY userId` accelerator.
|
|
1246
1278
|
*/
|
|
1247
|
-
interface
|
|
1279
|
+
interface InlineAggregateIndexOptions<Shape extends Record<string, Validator> = Record<string, Validator>> {
|
|
1280
|
+
/** Group keys; counter rows are one per distinct tuple. Omitted = single-row aggregate over the whole table. */
|
|
1281
|
+
by?: ReadonlyArray<keyof Shape & string>;
|
|
1282
|
+
/** The column the reducer applies to. Required for `sum`/`min`/`max`/`avg`; ignored for `count`. */
|
|
1283
|
+
field?: keyof Shape & string;
|
|
1284
|
+
/** Reducer (default `count`). */
|
|
1285
|
+
op?: AggregateOp;
|
|
1286
|
+
/** Static predicate baked into the counter — only matching rows are aggregated. */
|
|
1287
|
+
where?: Record<string, unknown>;
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Options for the inline `.rankIndex(name, opts)` builder. `sortBy` is required;
|
|
1291
|
+
* accepts either an array of `{ field, direction }` keys, or the shorthand
|
|
1292
|
+
* `["field"]` (asc) / `{ field: "desc" }` map entries. `partitionBy` scopes the
|
|
1293
|
+
* rank — omitted ⇒ one global rank over the whole table.
|
|
1294
|
+
*/
|
|
1295
|
+
interface InlineRankIndexOptions<Shape extends Record<string, Validator> = Record<string, Validator>> {
|
|
1296
|
+
/** Columns that scope each ranking; omitted ⇒ one global rank. */
|
|
1297
|
+
partitionBy?: ReadonlyArray<keyof Shape & string>;
|
|
1298
|
+
/** Ordered sort keys driving the rank. Required. */
|
|
1299
|
+
sortBy: ReadonlyArray<{
|
|
1300
|
+
direction?: "asc" | "desc";
|
|
1301
|
+
field: keyof Shape & string;
|
|
1302
|
+
}>;
|
|
1303
|
+
/** Static predicate baked into the index; only matching rows enter. */
|
|
1304
|
+
where?: Record<string, unknown>;
|
|
1305
|
+
}
|
|
1306
|
+
interface TableBuilder<Shape extends Record<string, Validator> = Record<string, Validator>> extends TableDefinition<Shape> {
|
|
1307
|
+
/** Declare an aggregate (counter/sum/…) maintained by triggers for O(1) reads. */
|
|
1308
|
+
aggregateIndex: (name: string, options?: InlineAggregateIndexOptions<Shape>) => TableBuilder<Shape>;
|
|
1248
1309
|
/**
|
|
1249
|
-
*
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
1310
|
+
* Mark this table as written outside Lunora's discoverable insert path —
|
|
1311
|
+
* by an adapter, a migration, or framework middleware (e.g. `@lunora/auth`'s
|
|
1312
|
+
* better-auth tables, `@lunora/ratelimit`'s store). Advisor insert-path lints
|
|
1313
|
+
* (`table_without_insert`) then skip it instead of flagging the absent
|
|
1314
|
+
* `ctx.db.insert(...)`.
|
|
1252
1315
|
*/
|
|
1253
|
-
|
|
1316
|
+
externallyManaged: () => TableBuilder<Shape>;
|
|
1254
1317
|
/**
|
|
1255
|
-
*
|
|
1256
|
-
*
|
|
1257
|
-
*
|
|
1258
|
-
*
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1318
|
+
* Declare a geospatial index over a `v.geoPoint()` column. The runtime keeps
|
|
1319
|
+
* a geohash companion so `withGeoIndex(name, q => q.near(point, radius))` and
|
|
1320
|
+
* `.within(bbox)` resolve as a geohash-prefix range scan + Haversine
|
|
1321
|
+
* refine/sort. `options.precision` tunes the geohash length (default 9).
|
|
1322
|
+
*/
|
|
1323
|
+
geoIndex: (name: string, options: {
|
|
1324
|
+
field: keyof Shape & string;
|
|
1325
|
+
precision?: number;
|
|
1326
|
+
}) => TableBuilder<Shape>;
|
|
1262
1327
|
/**
|
|
1263
|
-
*
|
|
1264
|
-
* `
|
|
1265
|
-
*
|
|
1266
|
-
*
|
|
1267
|
-
|
|
1328
|
+
* Mark this table as global (cross-shard). Backed by **D1** by default;
|
|
1329
|
+
* pass `{ backend: "hyperdrive" }` to store it in a Postgres/MySQL database
|
|
1330
|
+
* via Cloudflare Hyperdrive (PlanetScale, Neon, …) instead. Either way the
|
|
1331
|
+
* table stays reactive — live queries re-run on write.
|
|
1332
|
+
*/
|
|
1333
|
+
global: (options?: {
|
|
1334
|
+
backend?: GlobalBackend;
|
|
1335
|
+
}) => TableBuilder<Shape>;
|
|
1336
|
+
/** Add a secondary index. */
|
|
1337
|
+
index: (name: string, fields: ReadonlyArray<(keyof Shape & string) | (typeof SYSTEM_INDEX_FIELDS)[number]>, options?: {
|
|
1338
|
+
unique?: boolean;
|
|
1339
|
+
}) => TableBuilder<Shape>;
|
|
1340
|
+
/**
|
|
1341
|
+
* Name the column holding the owning user's id, so "only the owner sees these
|
|
1342
|
+
* rows" is declared once here rather than restated in every shape.
|
|
1268
1343
|
*
|
|
1269
|
-
*
|
|
1270
|
-
*
|
|
1271
|
-
*
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
1274
|
-
* several tables and has no single owning table to read it from.
|
|
1344
|
+
* A `defineShape({ table, owner: true })` over this table derives its predicate
|
|
1345
|
+
* from the field: the subscriber's verified `ctx.auth.userId` must match, and an
|
|
1346
|
+
* anonymous subscriber is denied. Pairs naturally with `.shardBy(field)` on the
|
|
1347
|
+
* same column — the shard key routes the storage, `ownedBy` states who the rows
|
|
1348
|
+
* belong to — but the two are independent and either can be used alone.
|
|
1275
1349
|
*
|
|
1276
|
-
*
|
|
1277
|
-
*
|
|
1350
|
+
* This is a *shape* declaration, not an RLS policy: it narrows what a shape
|
|
1351
|
+
* replicates. Guarding procedure reads/writes is still `rls(...)`'s job.
|
|
1278
1352
|
*/
|
|
1279
|
-
|
|
1353
|
+
ownedBy: (field: keyof Shape & string) => TableBuilder<Shape>;
|
|
1280
1354
|
/**
|
|
1281
|
-
*
|
|
1282
|
-
*
|
|
1283
|
-
*
|
|
1284
|
-
*
|
|
1355
|
+
* Opt this table OUT of secure-by-default RLS. Under a schema marked
|
|
1356
|
+
* `.rls("required")`, every table is protected (the write path denies raw,
|
|
1357
|
+
* non-RLS `ctx.db` access); calling `.public()` exempts this one table so a
|
|
1358
|
+
* plain `query`/`mutation` may read/write it without an RLS policy. No effect
|
|
1359
|
+
* when the schema does not require RLS.
|
|
1285
1360
|
*/
|
|
1286
|
-
|
|
1287
|
-
}
|
|
1288
|
-
/**
|
|
1289
|
-
* A {@link MutatorDefinition} plus the codegen discovery marker and a
|
|
1290
|
-
* dispatch-shaped `handler` (validates `args`, then runs `server`) so the DO
|
|
1291
|
-
* invokes a mutator exactly like a registered procedure.
|
|
1292
|
-
*/
|
|
1293
|
-
interface RegisteredMutator<Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown> extends MutatorDefinition<Args, ServerContext, ClientTx, R> {
|
|
1294
|
-
readonly __lunoraMutator: true;
|
|
1295
|
-
/** Validate `rawArgs`, then run the authoritative `server` impl. Used by the DO push path. */
|
|
1296
|
-
readonly handler: (context: ServerContext, rawArgs: Record<string, unknown>) => Promise<R>;
|
|
1361
|
+
public: () => TableBuilder<Shape>;
|
|
1297
1362
|
/**
|
|
1298
|
-
*
|
|
1299
|
-
* `
|
|
1300
|
-
* reads `kind === "mutation"` to wrap the authoritative `server` impl in the
|
|
1301
|
-
* shard's BEGIN/COMMIT span (all-or-nothing writes), exactly like an
|
|
1302
|
-
* ordinary `mutation`.
|
|
1363
|
+
* Declare a rank index (sorted companion table, btree-backed) for
|
|
1364
|
+
* `rank(row)` / `rankPage()` reads in O(log n). See {@link RankIndexDefinition}.
|
|
1303
1365
|
*/
|
|
1304
|
-
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
declare const defineMutator: <Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown>(definition: MutatorDefinition<Args, ServerContext, ClientTx, R>) => RegisteredMutator<Args, ServerContext, ClientTx, R>;
|
|
1308
|
-
/**
|
|
1309
|
-
* The prefixed tables a single plugin `P` contributes, or an empty map when it
|
|
1310
|
-
* ships no schema extension. Mirrors {@link PrefixedTables} at the plugin level
|
|
1311
|
-
* so {@link InstalledTables} can fold a tuple of plugins.
|
|
1312
|
-
*/
|
|
1313
|
-
type ExtensionTablesOf<P> = P extends {
|
|
1314
|
-
readonly extension: SchemaExtension<infer X> & {
|
|
1315
|
-
readonly key: infer K;
|
|
1316
|
-
};
|
|
1317
|
-
} ? K extends string ? PrefixedTables<X, K> : Record<never, never> : Record<never, never>;
|
|
1318
|
-
/**
|
|
1319
|
-
* Fold a tuple of plugins onto a base table map `T`, accumulating each plugin's
|
|
1320
|
-
* auto-prefixed extension tables left-to-right — the type-level mirror of
|
|
1321
|
-
* {@link installPlugins} applying `mergeSchemaExtension` for each plugin in turn.
|
|
1322
|
-
*/
|
|
1323
|
-
type InstalledTables<T extends Record<string, TableDefinition>, Plugins extends ReadonlyArray<unknown>> = Plugins extends readonly [infer Head, ...infer Rest] ? InstalledTables<ExtensionTablesOf<Head> & T, Rest> : T;
|
|
1324
|
-
/**
|
|
1325
|
-
* Union every plugin's `ContextOut` in a tuple — the type-level mirror of the
|
|
1326
|
-
* `ctx.api.<key>` additions {@link composePluginMiddleware} accumulates as each
|
|
1327
|
-
* plugin middleware runs. Independent of the incoming context, which the builder
|
|
1328
|
-
* infers at the `.use(...)` site.
|
|
1329
|
-
*/
|
|
1330
|
-
type ComposedOut<Plugins extends ReadonlyArray<unknown>> = Plugins extends readonly [infer Head, ...infer Rest] ? ComposedOut<Rest> & (Head extends Plugin<any, any, infer Out> ? Out : unknown) : unknown;
|
|
1331
|
-
/**
|
|
1332
|
-
* Schema fragment a plugin contributes. Same shape as the `tables` map
|
|
1333
|
-
* passed to `defineSchema`. Optional `vectorIndexes` mirror the top-level
|
|
1334
|
-
* `defineSchema` argument so a plugin can ship vector decls alongside its
|
|
1335
|
-
* tables.
|
|
1336
|
-
*/
|
|
1337
|
-
interface SchemaExtension<T extends Record<string, TableDefinition> = Record<string, TableDefinition>> {
|
|
1338
|
-
/** Stable key identifying the plugin that owns this extension. */
|
|
1339
|
-
readonly key: string;
|
|
1366
|
+
rankIndex: (name: string, options: InlineRankIndexOptions<Shape>) => TableBuilder<Shape>;
|
|
1367
|
+
/** Declare relations to other tables, loaded via `findMany({ with })`. */
|
|
1368
|
+
relations: (build: (r: RelationBuilder) => Record<string, RelationDefinition>) => TableBuilder<Shape>;
|
|
1340
1369
|
/**
|
|
1341
|
-
*
|
|
1342
|
-
*
|
|
1343
|
-
*
|
|
1370
|
+
* Add a full-text search index over `field`, queried with
|
|
1371
|
+
* `.withSearchIndex(name, q => q.search(field, term))`. `field` may be a
|
|
1372
|
+
* dot-separated path into a nested object (`"properties.name"`).
|
|
1373
|
+
* `filterFields` (at most 16) lists the columns `.eq()` may narrow by inside
|
|
1374
|
+
* the search. `language` selects the text analysis (accent folding always,
|
|
1375
|
+
* plus that language's stopwords). `staged: true` skips the migration-time
|
|
1376
|
+
* backfill on a large existing table. `strategy: "native"` uses the engine's
|
|
1377
|
+
* own full-text index where it has one (Postgres) — faster on large corpora,
|
|
1378
|
+
* at the cost of the engine ranking rather than the shared scorer.
|
|
1344
1379
|
*/
|
|
1345
|
-
|
|
1380
|
+
searchIndex: (name: string, options: {
|
|
1381
|
+
field: string;
|
|
1382
|
+
filterFields?: ReadonlyArray<string>;
|
|
1383
|
+
language?: SearchLanguage;
|
|
1384
|
+
staged?: boolean;
|
|
1385
|
+
strategy?: SearchStrategy;
|
|
1386
|
+
}) => TableBuilder<Shape>;
|
|
1387
|
+
/** Route storage by the named field — one DO per distinct value. */
|
|
1388
|
+
shardBy: (field: keyof Shape & string) => TableBuilder<Shape>;
|
|
1346
1389
|
/**
|
|
1347
|
-
*
|
|
1348
|
-
*
|
|
1349
|
-
*
|
|
1390
|
+
* Turn on soft delete. Adds a nullable timestamp column (`options.field`,
|
|
1391
|
+
* default `deletedAt`) and changes `ctx.db.<table>.delete()` to **set** it
|
|
1392
|
+
* instead of removing the row; `onDelete: "cascade"` children are recursively
|
|
1393
|
+
* soft-deleted too. **List reads** (`findMany`/`findFirst`/`query()`/`count`/
|
|
1394
|
+
* `aggregate`/relation loads) then hide soft-deleted rows unless they pass
|
|
1395
|
+
* `includeDeleted: true`; by-id `get`/`patch`/`replace` and the new
|
|
1396
|
+
* `restore()` still address the row directly. `hardDelete()` physically
|
|
1397
|
+
* removes it (cascading as a real delete). Note: `includeDeleted` is a read
|
|
1398
|
+
* scope, not access control — anyone who can run the read can set it; a unique
|
|
1399
|
+
* index still rejects a new row that collides with a soft-deleted one (the row
|
|
1400
|
+
* physically persists).
|
|
1350
1401
|
*/
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
* Build a {@link SchemaExtension}. The `key` is a runtime tag (used for
|
|
1355
|
-
* error messages on collision) and a type-level brand.
|
|
1356
|
-
*/
|
|
1357
|
-
declare const defineSchemaExtension: <T extends Record<string, TableDefinition>>(key: string, options: {
|
|
1358
|
-
tables: T;
|
|
1359
|
-
vectorIndexes?: Record<string, VectorIndexDefinition>;
|
|
1360
|
-
}) => SchemaExtension<T>;
|
|
1361
|
-
/**
|
|
1362
|
-
* A plugin packages an optional schema extension and optional middleware.
|
|
1363
|
-
* Both are independently usable: an app can install only the schema (e.g.
|
|
1364
|
-
* for plugins that ship background workers but no per-request behavior)
|
|
1365
|
-
* or only the middleware (plugins that augment ctx without persistent
|
|
1366
|
-
* state).
|
|
1367
|
-
*/
|
|
1368
|
-
interface Plugin<TExtension extends Record<string, TableDefinition> = Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn> {
|
|
1402
|
+
softDelete: (options?: {
|
|
1403
|
+
field?: string;
|
|
1404
|
+
}) => TableBuilder<Shape>;
|
|
1369
1405
|
/**
|
|
1370
|
-
*
|
|
1371
|
-
*
|
|
1406
|
+
* Materialize this table from an external Postgres/MySQL behind Cloudflare
|
|
1407
|
+
* Hyperdrive (plan 077). A system-driven poll loop reads the tenant slice
|
|
1408
|
+
* (`query`, with params bound from `tenantBy`) and lands it in the DO's SQLite,
|
|
1409
|
+
* after which `defineShape` carries it to clients unchanged. Implies
|
|
1410
|
+
* `.externallyManaged()` (rows come from the ingest loop, not user mutations).
|
|
1411
|
+
*
|
|
1412
|
+
* Orthogonal to `.shardBy()` — combine them for per-tenant DOs. **Under
|
|
1413
|
+
* `.shardBy()` `tenantBy` is mandatory** (the tenant-isolation boundary); the
|
|
1414
|
+
* `external_source_unscoped` advisor lint fails the build when it is absent, and
|
|
1415
|
+
* `external_source_on_global` rejects combining `.source()` with `.global()`.
|
|
1372
1416
|
*/
|
|
1373
|
-
|
|
1374
|
-
/**
|
|
1375
|
-
|
|
1417
|
+
source: (definition: ExternalSourceDefinition) => TableBuilder<Shape>;
|
|
1418
|
+
/** Declare named lifecycle triggers fired inline within the write path. */
|
|
1419
|
+
triggers: (build: (t: TriggerBuilder<Shape>) => Record<string, TriggerDefinition>) => TableBuilder<Shape>;
|
|
1376
1420
|
/**
|
|
1377
|
-
*
|
|
1378
|
-
*
|
|
1379
|
-
* `
|
|
1380
|
-
*
|
|
1381
|
-
*
|
|
1382
|
-
*
|
|
1383
|
-
* next({ ctx: { api: { ...ctx.api, ratelimit: api } } })
|
|
1384
|
-
* ```
|
|
1421
|
+
* Declare a table-level TTL: a DO alarm-driven sweep auto-deletes rows whose
|
|
1422
|
+
* expiry has passed (or soft-deletes them when the table also
|
|
1423
|
+
* `.softDelete()`s). `field` is an epoch-millisecond column; without
|
|
1424
|
+
* `options.after` its value is the absolute expiry instant, with `after` the
|
|
1425
|
+
* row expires `after` ms past `field` (`field + after`). Coarse, cheap,
|
|
1426
|
+
* table-level — for per-row schedules use `@lunora/scheduler`.
|
|
1385
1427
|
*/
|
|
1386
|
-
|
|
1428
|
+
ttl: (field: keyof Shape & string, options?: {
|
|
1429
|
+
after?: number;
|
|
1430
|
+
}) => TableBuilder<Shape>;
|
|
1431
|
+
/** Declare a vector index over a single text field on this table. */
|
|
1432
|
+
vectorize: (field: keyof Shape & string, options: VectorizeOptions<Shape>) => TableBuilder<Shape>;
|
|
1387
1433
|
}
|
|
1388
|
-
/** Options
|
|
1389
|
-
interface
|
|
1390
|
-
|
|
1391
|
-
|
|
1434
|
+
/** Options for `defineVectorIndex(...)` (DSL Shape B). */
|
|
1435
|
+
interface VectorIndexOptions {
|
|
1436
|
+
dimensions: number;
|
|
1437
|
+
embed: VectorEmbedder;
|
|
1438
|
+
/** Optional projection of the source row into Vectorize metadata. */
|
|
1439
|
+
metadata?: (row: Record<string, unknown>) => Record<string, unknown>;
|
|
1440
|
+
metric: VectorMetric;
|
|
1441
|
+
/** The vector source: which table, and how to derive the embedded text. */
|
|
1442
|
+
source: {
|
|
1443
|
+
select: (row: Record<string, unknown>) => string;
|
|
1444
|
+
table: string;
|
|
1445
|
+
};
|
|
1392
1446
|
}
|
|
1393
1447
|
/**
|
|
1394
|
-
*
|
|
1395
|
-
*
|
|
1396
|
-
* canonical install pattern `defineSchema(...).extend(plugin.extension)`
|
|
1397
|
-
* typechecks without a non-null assertion — the shape every scaffold template
|
|
1398
|
-
* ships. The bare-options signature keeps `extension` optional for plugins
|
|
1399
|
-
* that carry only middleware.
|
|
1448
|
+
* Build a table definition. Returned object is both the table definition (for
|
|
1449
|
+
* `defineSchema`) and a fluent builder for indexes + sharding metadata.
|
|
1400
1450
|
*/
|
|
1401
|
-
|
|
1402
|
-
<TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut> & {
|
|
1403
|
-
extension: SchemaExtension<TExtension>;
|
|
1404
|
-
}): Plugin<TExtension, TContextIn, TContextOut> & {
|
|
1405
|
-
readonly extension: SchemaExtension<TExtension>;
|
|
1406
|
-
};
|
|
1407
|
-
<TExtension extends Record<string, TableDefinition>, TContextIn = unknown, TContextOut = TContextIn>(key: string, options: DefinePluginOptions<TExtension, TContextIn, TContextOut>): Plugin<TExtension, TContextIn, TContextOut>;
|
|
1408
|
-
}
|
|
1451
|
+
declare const defineTable: <Shape extends Record<string, Validator>>(inputShape: Shape) => TableBuilder<Shape>;
|
|
1409
1452
|
/**
|
|
1410
|
-
*
|
|
1411
|
-
*
|
|
1453
|
+
* Declare a standalone vector index (DSL Shape B). Pass the returned value in
|
|
1454
|
+
* the `vectorIndexes` map of {@link defineSchema} when the source is derived
|
|
1455
|
+
* from multiple fields or a computation rather than a single column.
|
|
1412
1456
|
*/
|
|
1413
|
-
declare const
|
|
1457
|
+
declare const defineVectorIndex: (options: VectorIndexOptions) => VectorIndexDefinition;
|
|
1414
1458
|
/**
|
|
1415
|
-
*
|
|
1416
|
-
*
|
|
1417
|
-
*
|
|
1418
|
-
*
|
|
1419
|
-
* Users re-export from their own lunora module so codegen picks them up:
|
|
1420
|
-
*
|
|
1421
|
-
* ```ts
|
|
1422
|
-
* // lunora/ratelimit.ts
|
|
1423
|
-
* import { ratelimit } from "@vendor/ratelimit-component";
|
|
1424
|
-
* export const { check, reset } = ratelimit.functions;
|
|
1425
|
-
* // Emits as `ratelimit:check` / `ratelimit:reset` in the generated `api`.
|
|
1426
|
-
* ```
|
|
1427
|
-
*
|
|
1428
|
-
* Codegen follows the re-export back to the bundled `query/mutation/action`
|
|
1429
|
-
* call (property access or destructuring both work), so the functions land in
|
|
1430
|
-
* the generated `api` under the re-exporting file's namespace.
|
|
1459
|
+
* Options for the standalone `defineAggregateIndex(name, opts)` helper (DSL
|
|
1460
|
+
* Shape B). Unlike the inline `.aggregateIndex(...)` builder, this form takes
|
|
1461
|
+
* the owning table explicitly via `on` — handy when a single counter wants to
|
|
1462
|
+
* live next to the schema map rather than inside a table chain.
|
|
1431
1463
|
*/
|
|
1432
|
-
|
|
1464
|
+
interface AggregateIndexOptions {
|
|
1465
|
+
by?: ReadonlyArray<string>;
|
|
1466
|
+
field?: string;
|
|
1467
|
+
on: string;
|
|
1468
|
+
op?: AggregateOp;
|
|
1469
|
+
where?: Record<string, unknown>;
|
|
1470
|
+
}
|
|
1433
1471
|
/**
|
|
1434
|
-
*
|
|
1435
|
-
*
|
|
1436
|
-
*
|
|
1437
|
-
*
|
|
1472
|
+
* Declare a standalone aggregate index. Pass the returned value to
|
|
1473
|
+
* `defineSchema(tables, vectorIndexes, aggregateIndexes)` keyed by index name —
|
|
1474
|
+
* the schema attaches it to `tables[on].aggregateIndexes` so runtime consumers
|
|
1475
|
+
* (DO + D1) read every index uniformly off the table definition.
|
|
1438
1476
|
*/
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1477
|
+
declare const defineAggregateIndex: (name: string, options: AggregateIndexOptions) => AggregateIndexDefinition;
|
|
1478
|
+
/**
|
|
1479
|
+
* Options for the standalone `defineRankIndex(name, opts)` helper (DSL Shape B).
|
|
1480
|
+
* Mirrors the inline `.rankIndex(...)` builder but takes the owning table via
|
|
1481
|
+
* `table` so it can sit next to the schema map.
|
|
1482
|
+
*/
|
|
1483
|
+
interface RankIndexOptions {
|
|
1484
|
+
partitionBy?: ReadonlyArray<string>;
|
|
1485
|
+
sortBy: ReadonlyArray<{
|
|
1486
|
+
direction?: "asc" | "desc";
|
|
1487
|
+
field: string;
|
|
1488
|
+
}>;
|
|
1489
|
+
table: string;
|
|
1490
|
+
where?: Record<string, unknown>;
|
|
1445
1491
|
}
|
|
1446
1492
|
/**
|
|
1447
|
-
*
|
|
1448
|
-
*
|
|
1449
|
-
*
|
|
1450
|
-
* re-export entries so codegen discovers them as user functions:
|
|
1451
|
-
*
|
|
1452
|
-
* ```ts
|
|
1453
|
-
* export const ratelimit = defineComponent("ratelimit", {
|
|
1454
|
-
* // Bare `buckets` merges in as `ratelimit_buckets`.
|
|
1455
|
-
* extension: defineSchemaExtension("ratelimit", { tables: { buckets } }),
|
|
1456
|
-
* middleware: ({ ctx, next }) => next({ ctx: { ...ctx, ratelimit: api(ctx) } }),
|
|
1457
|
-
* functions: {
|
|
1458
|
-
* check: query.input({ key: v.string() }).query(async ({ ctx, args }) => ...),
|
|
1459
|
-
* reset: mutation.input({ key: v.string() }).mutation(async ({ ctx, args }) => ...),
|
|
1460
|
-
* },
|
|
1461
|
-
* });
|
|
1462
|
-
* ```
|
|
1463
|
-
*
|
|
1464
|
-
* Re-exporting an entry (by property access or destructuring) is enough for
|
|
1465
|
-
* codegen to discover it in the host app's namespace — the discovery resolver
|
|
1466
|
-
* chases the re-export back to the bundled registration call.
|
|
1493
|
+
* Declare a standalone rank index. Pass the returned value to
|
|
1494
|
+
* `defineSchema(tables, vectorIndexes, aggregateIndexes, rankIndexes)` keyed
|
|
1495
|
+
* by index name — the schema attaches it to `tables[on].rankIndexes`.
|
|
1467
1496
|
*/
|
|
1468
|
-
declare const
|
|
1497
|
+
declare const defineRankIndex: (name: string, options: RankIndexOptions) => RankIndexDefinition;
|
|
1469
1498
|
/**
|
|
1470
|
-
*
|
|
1471
|
-
*
|
|
1472
|
-
*
|
|
1499
|
+
* Build the application schema. The first argument is the table map; the
|
|
1500
|
+
* optional second argument registers standalone `defineVectorIndex(...)`
|
|
1501
|
+
* declarations (DSL Shape B) keyed by index name. The optional third argument
|
|
1502
|
+
* registers standalone `defineAggregateIndex(...)` declarations (DSL Shape B);
|
|
1503
|
+
* the optional fourth argument registers standalone `defineRankIndex(...)`
|
|
1504
|
+
* declarations. Both are folded into the matching `tables[on].*Indexes` array
|
|
1505
|
+
* so runtime backends read every index uniformly off the table definition.
|
|
1473
1506
|
*/
|
|
1474
|
-
type PrefixedTables<X extends Record<string, TableDefinition>, Key extends string> = { [K in keyof X as K extends string ? `${Key}_${K}` : K]: X[K]; };
|
|
1475
1507
|
/**
|
|
1476
|
-
*
|
|
1477
|
-
*
|
|
1478
|
-
*
|
|
1479
|
-
* Extension tables are auto-namespaced: each bare table name is prefixed with
|
|
1480
|
-
* the extension `key` (`buckets` → `ratelimit_buckets`), Convex-Components
|
|
1481
|
-
* style, and every intra-extension reference (relation targets, aggregate /
|
|
1482
|
-
* rank index `on`, standalone vector index `table`) is rewritten to match.
|
|
1483
|
-
* References to base/app tables are left untouched.
|
|
1508
|
+
* Schema with an in-place `.extend(plugin.extension)` method. Used so apps
|
|
1509
|
+
* can compose plugin schemas: `defineSchema({...}).extend(authPlugin.extension)`.
|
|
1484
1510
|
*
|
|
1485
|
-
*
|
|
1486
|
-
*
|
|
1487
|
-
*
|
|
1488
|
-
*
|
|
1511
|
+
* `extend` is non-mutating — returns a fresh `ExtendableSchema` containing
|
|
1512
|
+
* the merged tables. Extension tables are auto-namespaced by the extension
|
|
1513
|
+
* `key` (`buckets` → `ratelimit_buckets`), so the merged type carries the
|
|
1514
|
+
* prefixed names via {@link PrefixedTables}. Chains:
|
|
1515
|
+
* `defineSchema(...).extend(a).extend(b)` is the typed equivalent of merging
|
|
1516
|
+
* `a`'s prefixed tables then `b`'s.
|
|
1489
1517
|
*/
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1518
|
+
type ExtendableSchema<T extends Record<string, TableDefinition>> = {
|
|
1519
|
+
extend: <X extends Record<string, TableDefinition>, Key extends string>(extension: SchemaExtension<X> & {
|
|
1520
|
+
readonly key: Key;
|
|
1521
|
+
}) => ExtendableSchema<PrefixedTables<X, Key> & T>;
|
|
1522
|
+
/**
|
|
1523
|
+
* Pin every Durable Object the app reaches — shards, fan-out, subscriptions,
|
|
1524
|
+
* the scheduler, and `ctx.containers` — to a Cloudflare data-residency
|
|
1525
|
+
* jurisdiction (`"eu"`, `"us"`, `"fedramp"`). Codegen reads this off the
|
|
1526
|
+
* schema and emits it into the generated worker's `createWorker({ jurisdiction })`
|
|
1527
|
+
* (and `ctx.scheduler` / `ctx.containers`). Non-mutating: returns a fresh
|
|
1528
|
+
* `ExtendableSchema`, so it composes with `.rls(...)` / `.extend(...)` in any order.
|
|
1529
|
+
*
|
|
1530
|
+
* ⚠️ **Set this once, before your first deploy — changing or removing it
|
|
1531
|
+
* strands data.** A Durable Object name maps to a *different* ID in each
|
|
1532
|
+
* jurisdiction, so toggling this on an existing app makes every shard, scheduler
|
|
1533
|
+
* job, and session DO resolve to a NEW, empty DO; the previous data stays in the
|
|
1534
|
+
* old jurisdiction's DOs and is no longer reachable. There is no in-place
|
|
1535
|
+
* migration — you would have to export from the old jurisdiction and import
|
|
1536
|
+
* into the new one.
|
|
1537
|
+
*
|
|
1538
|
+
* Note: this pins **DO-backed** state only. D1-backed state — `.global()`
|
|
1539
|
+
* tables and `@lunora/auth` sessions alike — is governed by D1's own location
|
|
1540
|
+
* settings, not this option.
|
|
1541
|
+
* @see https://developers.cloudflare.com/durable-objects/reference/data-location/
|
|
1542
|
+
*/
|
|
1543
|
+
jurisdiction: (jurisdiction: DurableObjectJurisdiction) => ExtendableSchema<T>;
|
|
1544
|
+
/**
|
|
1545
|
+
* Turn on secure-by-default RLS for the whole schema. Every table is then
|
|
1546
|
+
* protected — the DO/D1 write path denies raw, non-RLS `ctx.db` access, so a
|
|
1547
|
+
* procedure that forgets `.use(rls(...))` fails closed. Opt a table out with
|
|
1548
|
+
* `.public()`. Non-mutating: returns a fresh `ExtendableSchema` carrying the
|
|
1549
|
+
* mode, so `.rls("required")` composes with `.extend(...)` either order.
|
|
1550
|
+
*/
|
|
1551
|
+
rls: (mode: "required") => ExtendableSchema<T>;
|
|
1552
|
+
} & Schema<T>;
|
|
1493
1553
|
/**
|
|
1494
|
-
*
|
|
1495
|
-
*
|
|
1496
|
-
*
|
|
1497
|
-
*
|
|
1498
|
-
*
|
|
1554
|
+
* Columns every row carries implicitly (never part of a table's declared
|
|
1555
|
+
* `shape`), so `.index()` may legitimately name them. The single source for
|
|
1556
|
+
* both the compile-time allow-list (`TableBuilder["index"]`'s `fields` type,
|
|
1557
|
+
* via `(typeof SYSTEM_INDEX_FIELDS)[number]`) and the runtime cross-check
|
|
1558
|
+
* below (via `SYSTEM_INDEX_FIELDS_SET`) — declared once so the two can't
|
|
1559
|
+
* drift apart.
|
|
1560
|
+
*/
|
|
1561
|
+
declare const SYSTEM_INDEX_FIELDS: readonly ["_creationTime", "_id"];
|
|
1562
|
+
/**
|
|
1563
|
+
* Per-table, per-KIND index→declared-fields map: for each table, each index
|
|
1564
|
+
* KIND (`index` | `rank` | `geo`) that has at least one declared index maps
|
|
1565
|
+
* to a name→fields record for that kind only. Distilled by
|
|
1566
|
+
* {@link indexFieldsFromSchema}; this is the shape `mask()`'s
|
|
1567
|
+
* `MaskOptions.indexFields` expects (see `./mask/types`), so a table not
|
|
1568
|
+
* present here (no declared indexes of any kind) is simply absent from the
|
|
1569
|
+
* map rather than mapped to `{}`, and a kind with no declared indexes on a
|
|
1570
|
+
* table that HAS other kinds is simply absent from that table's entry.
|
|
1499
1571
|
*
|
|
1500
|
-
*
|
|
1501
|
-
*
|
|
1502
|
-
*
|
|
1503
|
-
*
|
|
1572
|
+
* Kept per kind (rather than one flat name→fields record) because the engine
|
|
1573
|
+
* resolves `withIndex`/`withGeoIndex`/rank reads in THREE separate
|
|
1574
|
+
* namespaces (`tableDefinition.indexes` / `.geoIndexes` / `.rankIndexes` —
|
|
1575
|
+
* see `@lunora/shard-engine`'s `ctx-db.ts`), so the same name can legally and
|
|
1576
|
+
* unambiguously denote a different index per kind. A flat map would let one
|
|
1577
|
+
* kind's fields silently shadow another's for a colliding name, producing a
|
|
1578
|
+
* wrong-namespace answer from the mask guard (checking the wrong index's
|
|
1579
|
+
* fields) instead of the documented fail-open (missing lookup) — see plan 258.
|
|
1580
|
+
*/
|
|
1581
|
+
type IndexFieldsByTable = Readonly<Record<string, {
|
|
1582
|
+
readonly geo?: Readonly<Record<string, ReadonlyArray<string>>>;
|
|
1583
|
+
readonly index?: Readonly<Record<string, ReadonlyArray<string>>>;
|
|
1584
|
+
readonly rank?: Readonly<Record<string, ReadonlyArray<string>>>;
|
|
1585
|
+
}>>;
|
|
1586
|
+
declare const indexFieldsFromSchema: (schema: Schema) => IndexFieldsByTable;
|
|
1587
|
+
declare const defineSchema: <T extends Record<string, TableDefinition>>(tables: T, vectorIndexes?: Record<string, VectorIndexDefinition>, aggregateIndexes?: Record<string, AggregateIndexDefinition>, rankIndexes?: Record<string, RankIndexDefinition>) => ExtendableSchema<T>;
|
|
1588
|
+
/**
|
|
1589
|
+
* Context handed to a {@link MaskFn} (and to {@link MaskOptions.bypass}). The
|
|
1590
|
+
* `auth` shape mirrors RLS's `PolicyContext.auth` one-for-one — same identity
|
|
1591
|
+
* resolver, same `can(...)` permission check — so an author can branch a mask
|
|
1592
|
+
* on the caller's role/permission. `row` is the full pre-mask row the column
|
|
1593
|
+
* belongs to; `column` is the column currently being masked. Both are absent
|
|
1594
|
+
* when the context is used for the procedure-wide `bypass` check (no specific
|
|
1595
|
+
* cell is in play yet).
|
|
1596
|
+
*/
|
|
1597
|
+
interface MaskContext<Context = unknown> {
|
|
1598
|
+
readonly auth: {
|
|
1599
|
+
/** `true` when any of the request's `roles` grants `permission` (see {@link MaskOptions.roles}). Fails closed for unregistered roles. */
|
|
1600
|
+
readonly can: (permission: Permission | string) => boolean;
|
|
1601
|
+
readonly identity?: Record<string, unknown> | null;
|
|
1602
|
+
readonly roles: ReadonlyArray<string>;
|
|
1603
|
+
readonly userId: null | string;
|
|
1604
|
+
};
|
|
1605
|
+
/** The column currently being masked. Present only inside a per-cell {@link MaskFn}. */
|
|
1606
|
+
readonly column?: string;
|
|
1607
|
+
readonly ctx: Context;
|
|
1608
|
+
/** The full pre-mask row the masked cell belongs to. Present only inside a per-cell {@link MaskFn}. */
|
|
1609
|
+
readonly row?: Record<string, unknown>;
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* A custom masking function. Receives the raw cell value and the
|
|
1613
|
+
* {@link MaskContext}, returns the value to surface. Use it for partial masks
|
|
1614
|
+
* (`maskMiddle(phone)`), role-aware reveals (`ctx.auth.can(...) ? value : null`),
|
|
1615
|
+
* or format-preserving tokens. A function that **throws** fails closed — the
|
|
1616
|
+
* cell is redacted to `null`, never leaked raw.
|
|
1617
|
+
*/
|
|
1618
|
+
type MaskFn<Context = unknown> = (value: unknown, context: MaskContext<Context>) => unknown;
|
|
1619
|
+
/**
|
|
1620
|
+
* How a column is masked:
|
|
1504
1621
|
*
|
|
1505
|
-
*
|
|
1506
|
-
*
|
|
1507
|
-
*
|
|
1622
|
+
* - `"redact"` — drop the value to `null`. The simplest, safest strategy, and
|
|
1623
|
+
* the right choice for any value that must actually be kept secret.
|
|
1624
|
+
* - `"hash"` — replace with a stable token (unsalted 32-bit FNV-1a hex) so the
|
|
1625
|
+
* same input always yields the same token (joinable/groupable client-side).
|
|
1626
|
+
* **This is NOT a confidentiality control.** It is a non-cryptographic,
|
|
1627
|
+
* unsalted, deterministic, narrow (~2^32) digest: low-entropy values (emails,
|
|
1628
|
+
* phone numbers, SSNs) are brute-force-recoverable by the very caller you are
|
|
1629
|
+
* masking from, and identical values always produce identical tokens across
|
|
1630
|
+
* rows/columns/tenants (enabling correlation). Use `"hash"` ONLY when you want a
|
|
1631
|
+
* stable pseudonym for grouping/joining and leaking the value is acceptable —
|
|
1632
|
+
* never to hide sensitive PII. For PII that must stay hidden, use `"redact"`.
|
|
1633
|
+
* - a {@link MaskFn} — author-defined transform (partial mask, role-aware reveal).
|
|
1508
1634
|
*/
|
|
1509
|
-
|
|
1635
|
+
type MaskStrategy<Context = unknown> = "hash" | "redact" | MaskFn<Context>;
|
|
1636
|
+
/** Per-column strategy map for one table: `{ email: "redact", phone: maskMiddle }`. */
|
|
1637
|
+
type MaskColumns<Context = unknown> = Record<string, MaskStrategy<Context>>;
|
|
1510
1638
|
/**
|
|
1511
|
-
*
|
|
1512
|
-
*
|
|
1513
|
-
*
|
|
1514
|
-
*
|
|
1515
|
-
|
|
1516
|
-
|
|
1639
|
+
* The mask declaration passed to `mask(...)`: a table → column → strategy map.
|
|
1640
|
+
* Deliberately a plain object literal so the codegen feeder can statically read
|
|
1641
|
+
* which columns a procedure masks (powering the `mask_uncovered_pii_column`
|
|
1642
|
+
* advisor lint), exactly as the RLS feeder reads policy tables.
|
|
1643
|
+
*/
|
|
1644
|
+
type MaskPolicies<Context = unknown> = Record<string, MaskColumns<Context>>;
|
|
1645
|
+
/**
|
|
1646
|
+
* Options for `mask(policies, options)`.
|
|
1517
1647
|
*
|
|
1518
|
-
* `
|
|
1519
|
-
*
|
|
1520
|
-
*
|
|
1648
|
+
* - `roles` registers the role→permission grants that back `ctx.auth.can(...)`
|
|
1649
|
+
* inside a {@link MaskFn} — identical to `rls(policies, { roles })`. A role
|
|
1650
|
+
* not listed grants no permissions (fails closed for unknown roles).
|
|
1651
|
+
* - `bypass` is a procedure-wide escape hatch: when it returns `true` the whole
|
|
1652
|
+
* mask is skipped (the caller sees raw values). Use it for a privileged
|
|
1653
|
+
* viewer — `bypass: ({ auth }) => auth.can("pii:view")`. Prefer this over
|
|
1654
|
+
* branching every column when an entire class of caller should see clear data.
|
|
1655
|
+
* - `indexFields` closes the bare-index-scan / rank / geo position oracle (see
|
|
1656
|
+
* the `mask/middleware` module docblock's "Residual read-position oracles" section).
|
|
1521
1657
|
*/
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1658
|
+
interface MaskOptions<Context = unknown> {
|
|
1659
|
+
readonly bypass?: (context: MaskContext<Context>) => boolean;
|
|
1660
|
+
/**
|
|
1661
|
+
* Per-table, per-KIND index→declared-fields map (regular index `fields`
|
|
1662
|
+
* under `index`; rank index `sortBy` ∪ `partitionBy` under `rank`; geo
|
|
1663
|
+
* index `field` under `geo`). Supplied to close the bare-index-scan /
|
|
1664
|
+
* rank / geo position oracle: a `withIndex(name)` with no range callback,
|
|
1665
|
+
* a `rank`/`rankPage`/`rankBefore` read, or a `withGeoIndex` read, over an
|
|
1666
|
+
* index whose DECLARED fields (for that read's own kind) intersect a
|
|
1667
|
+
* masked column, is rejected. Kept per kind — rather than one flat
|
|
1668
|
+
* name→fields map — because the engine resolves `withIndex` /
|
|
1669
|
+
* `withGeoIndex` / rank reads in three separate namespaces, so the same
|
|
1670
|
+
* index name can legally denote a different index per kind; a flat map
|
|
1671
|
+
* would let one kind's fields shadow another's for a colliding name.
|
|
1672
|
+
* OPTIONAL and additive — omit it and behaviour is unchanged (the oracle
|
|
1673
|
+
* stays open, exactly as before this option existed). Build it with
|
|
1674
|
+
* `indexFieldsFromSchema` (exported from `@lunora/server`):
|
|
1675
|
+
* `mask(policies, { indexFields: indexFieldsFromSchema(schema) })`.
|
|
1676
|
+
*/
|
|
1677
|
+
readonly indexFields?: IndexFieldsByTable;
|
|
1678
|
+
readonly roles?: ReadonlyArray<Role>;
|
|
1532
1679
|
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1680
|
+
interface QueryPage$1 {
|
|
1681
|
+
continueCursor: null | string;
|
|
1682
|
+
isDone: boolean;
|
|
1683
|
+
page: Record<string, unknown>[];
|
|
1537
1684
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1685
|
+
interface QueryArgs$1 {
|
|
1686
|
+
baseWhere?: unknown;
|
|
1687
|
+
cursor?: null | string;
|
|
1688
|
+
limit?: number;
|
|
1689
|
+
orderBy?: ReadonlyArray<Record<string, unknown>>;
|
|
1690
|
+
where?: unknown;
|
|
1691
|
+
with?: Record<string, unknown>;
|
|
1542
1692
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
field: string;
|
|
1548
|
-
references?: string;
|
|
1549
|
-
}) => ManyRelation<Target>;
|
|
1550
|
-
/** Many-to-one: the FK `field` lives on this table, pointing at `table`.`references` (default `_id`). */
|
|
1551
|
-
one: <Target extends string>(table: Target, options: {
|
|
1552
|
-
field: string;
|
|
1553
|
-
onDelete?: OnDeleteAction;
|
|
1554
|
-
references?: string;
|
|
1555
|
-
}) => OneRelation<Target>;
|
|
1693
|
+
interface AggregateArgs$1 {
|
|
1694
|
+
field?: string;
|
|
1695
|
+
op: string;
|
|
1696
|
+
where?: unknown;
|
|
1556
1697
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1698
|
+
interface GroupByArgs$1 {
|
|
1699
|
+
agg?: {
|
|
1700
|
+
field?: string;
|
|
1701
|
+
op: string;
|
|
1702
|
+
};
|
|
1703
|
+
by: ReadonlyArray<string>;
|
|
1704
|
+
where?: unknown;
|
|
1705
|
+
}
|
|
1706
|
+
/** One row of a `.collectWithScores()` result — mirrors `@lunora/shard-engine`'s `ScoredDocument`. */
|
|
1707
|
+
type ScoredDocument = GeoScoredDocument | SearchScoredDocument;
|
|
1708
|
+
/** A `.withGeoIndex()` row — mirrors `@lunora/shard-engine`'s `GeoScoredDocument`. */
|
|
1709
|
+
interface GeoScoredDocument {
|
|
1710
|
+
distanceMeters: null | number;
|
|
1711
|
+
document: Record<string, unknown>;
|
|
1712
|
+
score?: never;
|
|
1713
|
+
}
|
|
1714
|
+
/** A `.withSearchIndex()` row — mirrors `@lunora/shard-engine`'s `SearchScoredDocument`. */
|
|
1715
|
+
interface SearchScoredDocument {
|
|
1716
|
+
distanceMeters?: never;
|
|
1717
|
+
document: Record<string, unknown>;
|
|
1718
|
+
score: number;
|
|
1719
|
+
}
|
|
1720
|
+
interface TableReaderLike$1 {
|
|
1721
|
+
[Symbol.asyncIterator]: () => AsyncIterator<Record<string, unknown>>;
|
|
1722
|
+
collect: () => Promise<Record<string, unknown>[]>;
|
|
1723
|
+
collectWithScores: () => Promise<ScoredDocument[]>;
|
|
1724
|
+
filter: (predicate: (document: Record<string, unknown>) => boolean) => TableReaderLike$1;
|
|
1725
|
+
first: () => Promise<Record<string, unknown> | null>;
|
|
1726
|
+
order: (direction: "asc" | "desc") => TableReaderLike$1;
|
|
1727
|
+
paginate: (options: {
|
|
1728
|
+
cursor?: null | string;
|
|
1729
|
+
numItems: number;
|
|
1730
|
+
}) => Promise<QueryPage$1>;
|
|
1731
|
+
take: (limit: number) => Promise<Record<string, unknown>[]>;
|
|
1732
|
+
unique: () => Promise<Record<string, unknown> | null>;
|
|
1733
|
+
withGeoIndex: (indexName: string, build: (q: unknown) => unknown) => TableReaderLike$1;
|
|
1734
|
+
withIndex: (indexName: string, range?: (q: unknown) => unknown) => TableReaderLike$1;
|
|
1735
|
+
withSearchIndex: (indexName: string, search: (q: unknown) => unknown) => TableReaderLike$1;
|
|
1571
1736
|
}
|
|
1572
1737
|
/**
|
|
1573
|
-
*
|
|
1574
|
-
*
|
|
1575
|
-
* `
|
|
1576
|
-
*
|
|
1738
|
+
* Structural projection of the runtime ORM writer — the same subset
|
|
1739
|
+
* `../rls/middleware` mirrors, so the wrapper is interchangeable between
|
|
1740
|
+
* `@lunora/do`'s and `@lunora/d1`'s `DatabaseWriterLike` without an
|
|
1741
|
+
* inter-package dependency. `rankBefore` is optional (the D1 twin omits it).
|
|
1577
1742
|
*/
|
|
1578
|
-
interface
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1743
|
+
interface MaskDatabase {
|
|
1744
|
+
aggregate: (tableName: string, options: AggregateArgs$1) => Promise<null | number>;
|
|
1745
|
+
count: (tableName: string, whereOrArgs?: unknown) => Promise<number>;
|
|
1746
|
+
delete: (id: string, expectedTable?: string) => Promise<void>;
|
|
1747
|
+
deleteMany: (ids: ReadonlyArray<string>, options?: {
|
|
1748
|
+
limit?: number;
|
|
1749
|
+
}) => Promise<{
|
|
1750
|
+
deleted: number;
|
|
1585
1751
|
}>;
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
}) =>
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
* Opt this table OUT of secure-by-default RLS. Under a schema marked
|
|
1639
|
-
* `.rls("required")`, every table is protected (the write path denies raw,
|
|
1640
|
-
* non-RLS `ctx.db` access); calling `.public()` exempts this one table so a
|
|
1641
|
-
* plain `query`/`mutation` may read/write it without an RLS policy. No effect
|
|
1642
|
-
* when the schema does not require RLS.
|
|
1643
|
-
*/
|
|
1644
|
-
public: () => TableBuilder<Shape>;
|
|
1645
|
-
/**
|
|
1646
|
-
* Declare a rank index (sorted companion table, btree-backed) for
|
|
1647
|
-
* `rank(row)` / `rankPage()` reads in O(log n). See {@link RankIndexDefinition}.
|
|
1648
|
-
*/
|
|
1649
|
-
rankIndex: (name: string, options: InlineRankIndexOptions<Shape>) => TableBuilder<Shape>;
|
|
1650
|
-
/** Declare relations to other tables, loaded via `findMany({ with })`. */
|
|
1651
|
-
relations: (build: (r: RelationBuilder) => Record<string, RelationDefinition>) => TableBuilder<Shape>;
|
|
1652
|
-
/**
|
|
1653
|
-
* Add a full-text search index over `field`, queried with
|
|
1654
|
-
* `.withSearchIndex(name, q => q.search(field, term))`. `field` may be a
|
|
1655
|
-
* dot-separated path into a nested object (`"properties.name"`).
|
|
1656
|
-
* `filterFields` (at most 16) lists the columns `.eq()` may narrow by inside
|
|
1657
|
-
* the search. `language` selects the text analysis (accent folding always,
|
|
1658
|
-
* plus that language's stopwords). `staged: true` skips the migration-time
|
|
1659
|
-
* backfill on a large existing table. `strategy: "native"` uses the engine's
|
|
1660
|
-
* own full-text index where it has one (Postgres) — faster on large corpora,
|
|
1661
|
-
* at the cost of the engine ranking rather than the shared scorer.
|
|
1662
|
-
*/
|
|
1663
|
-
searchIndex: (name: string, options: {
|
|
1664
|
-
field: string;
|
|
1665
|
-
filterFields?: ReadonlyArray<string>;
|
|
1666
|
-
language?: SearchLanguage;
|
|
1667
|
-
staged?: boolean;
|
|
1668
|
-
strategy?: SearchStrategy;
|
|
1669
|
-
}) => TableBuilder<Shape>;
|
|
1670
|
-
/** Route storage by the named field — one DO per distinct value. */
|
|
1671
|
-
shardBy: (field: keyof Shape & string) => TableBuilder<Shape>;
|
|
1672
|
-
/**
|
|
1673
|
-
* Turn on soft delete. Adds a nullable timestamp column (`options.field`,
|
|
1674
|
-
* default `deletedAt`) and changes `ctx.db.<table>.delete()` to **set** it
|
|
1675
|
-
* instead of removing the row; `onDelete: "cascade"` children are recursively
|
|
1676
|
-
* soft-deleted too. **List reads** (`findMany`/`findFirst`/`query()`/`count`/
|
|
1677
|
-
* `aggregate`/relation loads) then hide soft-deleted rows unless they pass
|
|
1678
|
-
* `includeDeleted: true`; by-id `get`/`patch`/`replace` and the new
|
|
1679
|
-
* `restore()` still address the row directly. `hardDelete()` physically
|
|
1680
|
-
* removes it (cascading as a real delete). Note: `includeDeleted` is a read
|
|
1681
|
-
* scope, not access control — anyone who can run the read can set it; a unique
|
|
1682
|
-
* index still rejects a new row that collides with a soft-deleted one (the row
|
|
1683
|
-
* physically persists).
|
|
1684
|
-
*/
|
|
1685
|
-
softDelete: (options?: {
|
|
1686
|
-
field?: string;
|
|
1687
|
-
}) => TableBuilder<Shape>;
|
|
1688
|
-
/**
|
|
1689
|
-
* Materialize this table from an external Postgres/MySQL behind Cloudflare
|
|
1690
|
-
* Hyperdrive (plan 077). A system-driven poll loop reads the tenant slice
|
|
1691
|
-
* (`query`, with params bound from `tenantBy`) and lands it in the DO's SQLite,
|
|
1692
|
-
* after which `defineShape` carries it to clients unchanged. Implies
|
|
1693
|
-
* `.externallyManaged()` (rows come from the ingest loop, not user mutations).
|
|
1694
|
-
*
|
|
1695
|
-
* Orthogonal to `.shardBy()` — combine them for per-tenant DOs. **Under
|
|
1696
|
-
* `.shardBy()` `tenantBy` is mandatory** (the tenant-isolation boundary); the
|
|
1697
|
-
* `external_source_unscoped` advisor lint fails the build when it is absent, and
|
|
1698
|
-
* `external_source_on_global` rejects combining `.source()` with `.global()`.
|
|
1699
|
-
*/
|
|
1700
|
-
source: (definition: ExternalSourceDefinition) => TableBuilder<Shape>;
|
|
1701
|
-
/** Declare named lifecycle triggers fired inline within the write path. */
|
|
1702
|
-
triggers: (build: (t: TriggerBuilder<Shape>) => Record<string, TriggerDefinition>) => TableBuilder<Shape>;
|
|
1703
|
-
/**
|
|
1704
|
-
* Declare a table-level TTL: a DO alarm-driven sweep auto-deletes rows whose
|
|
1705
|
-
* expiry has passed (or soft-deletes them when the table also
|
|
1706
|
-
* `.softDelete()`s). `field` is an epoch-millisecond column; without
|
|
1707
|
-
* `options.after` its value is the absolute expiry instant, with `after` the
|
|
1708
|
-
* row expires `after` ms past `field` (`field + after`). Coarse, cheap,
|
|
1709
|
-
* table-level — for per-row schedules use `@lunora/scheduler`.
|
|
1710
|
-
*/
|
|
1711
|
-
ttl: (field: keyof Shape & string, options?: {
|
|
1712
|
-
after?: number;
|
|
1713
|
-
}) => TableBuilder<Shape>;
|
|
1714
|
-
/** Declare a vector index over a single text field on this table. */
|
|
1715
|
-
vectorize: (field: keyof Shape & string, options: VectorizeOptions<Shape>) => TableBuilder<Shape>;
|
|
1752
|
+
deleteWhere?: (tableName: string, where: Record<string, unknown>, options?: {
|
|
1753
|
+
limit?: number;
|
|
1754
|
+
}) => Promise<{
|
|
1755
|
+
deleted: number;
|
|
1756
|
+
}>;
|
|
1757
|
+
findFirst: (tableName: string, args?: QueryArgs$1) => Promise<Record<string, unknown> | null>;
|
|
1758
|
+
findFirstOrThrow: (tableName: string, args?: QueryArgs$1) => Promise<Record<string, unknown>>;
|
|
1759
|
+
findMany: (tableName: string, args?: QueryArgs$1) => Promise<QueryPage$1>;
|
|
1760
|
+
get: (id: string, expectedTable?: string) => Promise<Record<string, unknown> | null>;
|
|
1761
|
+
groupBy: (tableName: string, options: GroupByArgs$1) => Promise<ReadonlyArray<{
|
|
1762
|
+
key: Record<string, unknown>;
|
|
1763
|
+
value: null | number;
|
|
1764
|
+
}>>;
|
|
1765
|
+
insert: (tableName: string, document: Record<string, unknown>) => Promise<string>;
|
|
1766
|
+
insertMany: (tableName: string, documents: ReadonlyArray<Record<string, unknown>>, options?: {
|
|
1767
|
+
limit?: number;
|
|
1768
|
+
skipDuplicates?: boolean;
|
|
1769
|
+
}) => Promise<(string | null)[]>;
|
|
1770
|
+
lookupById?: (id: string, expectedTable?: string) => Promise<null | {
|
|
1771
|
+
row: Record<string, unknown>;
|
|
1772
|
+
tableName: string;
|
|
1773
|
+
}>;
|
|
1774
|
+
patch: (id: string, patch: Record<string, unknown>, expectedTable?: string) => Promise<void>;
|
|
1775
|
+
patchMany: (patches: ReadonlyArray<{
|
|
1776
|
+
id: string;
|
|
1777
|
+
patch: Record<string, unknown>;
|
|
1778
|
+
}>, options?: {
|
|
1779
|
+
limit?: number;
|
|
1780
|
+
}) => Promise<{
|
|
1781
|
+
patched: number;
|
|
1782
|
+
}>;
|
|
1783
|
+
patchWhere?: (tableName: string, args: {
|
|
1784
|
+
patch: Record<string, unknown>;
|
|
1785
|
+
where: Record<string, unknown>;
|
|
1786
|
+
}, options?: {
|
|
1787
|
+
limit?: number;
|
|
1788
|
+
}) => Promise<{
|
|
1789
|
+
patched: number;
|
|
1790
|
+
}>;
|
|
1791
|
+
query: (tableName: string) => TableReaderLike$1;
|
|
1792
|
+
rank: (tableName: string, indexName: string, options: unknown) => Promise<null | {
|
|
1793
|
+
position: number;
|
|
1794
|
+
total: number;
|
|
1795
|
+
}>;
|
|
1796
|
+
rankBefore?: (tableName: string, indexName: string, options: unknown) => Promise<{
|
|
1797
|
+
before: number;
|
|
1798
|
+
total: number;
|
|
1799
|
+
}>;
|
|
1800
|
+
rankPage: (tableName: string, indexName: string, options?: unknown) => Promise<QueryPage$1>;
|
|
1801
|
+
/** Cross-shard companion to `rankPage`, gated the same way `rankPage` is masked below. */
|
|
1802
|
+
rankPageRows?: (tableName: string, indexName: string, options?: unknown) => Promise<ShardRankPageResultLike>;
|
|
1803
|
+
replace: (id: string, document: Record<string, unknown>, expectedTable?: string) => Promise<void>;
|
|
1716
1804
|
}
|
|
1717
|
-
/**
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
select: (row: Record<string, unknown>) => string;
|
|
1727
|
-
table: string;
|
|
1728
|
-
};
|
|
1805
|
+
/** Roles list source on the context. Tolerant of older auth states (mirrors RLS's `AuthLike`). */
|
|
1806
|
+
type AuthLike$1 = {
|
|
1807
|
+
getIdentity?: () => Promise<Record<string, unknown> | null>;
|
|
1808
|
+
roles?: ReadonlyArray<string>;
|
|
1809
|
+
userId?: null | string;
|
|
1810
|
+
};
|
|
1811
|
+
interface MaskContextIn {
|
|
1812
|
+
auth?: AuthLike$1;
|
|
1813
|
+
db: MaskDatabase;
|
|
1729
1814
|
}
|
|
1730
1815
|
/**
|
|
1731
|
-
*
|
|
1732
|
-
*
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
*
|
|
1737
|
-
* the `vectorIndexes` map of {@link defineSchema} when the source is derived
|
|
1738
|
-
* from multiple fields or a computation rather than a single column.
|
|
1739
|
-
*/
|
|
1740
|
-
declare const defineVectorIndex: (options: VectorIndexOptions) => VectorIndexDefinition;
|
|
1741
|
-
/**
|
|
1742
|
-
* Options for the standalone `defineAggregateIndex(name, opts)` helper (DSL
|
|
1743
|
-
* Shape B). Unlike the inline `.aggregateIndex(...)` builder, this form takes
|
|
1744
|
-
* the owning table explicitly via `on` — handy when a single counter wants to
|
|
1745
|
-
* live next to the schema map rather than inside a table chain.
|
|
1816
|
+
* Procedure-builder middleware. Apply per-request via `.use(mask(policies))`.
|
|
1817
|
+
* Closes over the policy map at builder-construction time; resolves identity +
|
|
1818
|
+
* the `bypass` decision per call against the live ctx.
|
|
1819
|
+
*
|
|
1820
|
+
* IMPORTANT: a mask is in scope only for procedures whose builder chain
|
|
1821
|
+
* includes this middleware — opt-in, never global (the same invariant as RLS).
|
|
1746
1822
|
*/
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
on: string;
|
|
1751
|
-
op?: AggregateOp;
|
|
1752
|
-
where?: Record<string, unknown>;
|
|
1753
|
-
}
|
|
1823
|
+
declare const mask: <Context extends MaskContextIn = MaskContextIn>(policies: MaskPolicies<Context>, options?: MaskOptions<Context>) => Middleware<Context, Context>;
|
|
1824
|
+
/** Project-wide masked-column registry: every table any registered function masks, unioned. */
|
|
1825
|
+
type MaskRegistry = ReadonlyMap<string, ReadonlySet<string>>;
|
|
1754
1826
|
/**
|
|
1755
|
-
*
|
|
1756
|
-
* `
|
|
1757
|
-
*
|
|
1758
|
-
*
|
|
1827
|
+
* Build the project-wide masked-column registry from the registered functions
|
|
1828
|
+
* (pass `Object.values(LUNORA_FUNCTIONS)`) — the mask-column twin of
|
|
1829
|
+
* `buildRlsReadRegistry`. Unions every function's `.use(mask(...))` columns
|
|
1830
|
+
* per table: a column masked by ANY registered function counts as masked in
|
|
1831
|
+
* the registry — there is no "which procedure would this shape have gone
|
|
1832
|
+
* through" question to narrow by, so the union is the only safe answer.
|
|
1759
1833
|
*/
|
|
1760
|
-
declare const
|
|
1834
|
+
declare const buildMaskRegistry: (functions: Iterable<unknown>) => MaskRegistry;
|
|
1835
|
+
/** A document handed to a migration transform: the stored row including `_id`/`_creationTime`. */
|
|
1836
|
+
type MigrationDocument = Record<string, unknown>;
|
|
1761
1837
|
/**
|
|
1762
|
-
*
|
|
1763
|
-
*
|
|
1764
|
-
*
|
|
1838
|
+
* The read surface a transform reaches through its `ctx`.
|
|
1839
|
+
*
|
|
1840
|
+
* Read-only by design: the runner accounts for exactly one rewrite per row read,
|
|
1841
|
+
* and a transform writing directly would make that count describe something
|
|
1842
|
+
* other than what happened. Scoped to the shard the runner is walking.
|
|
1765
1843
|
*/
|
|
1766
|
-
interface
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1844
|
+
interface MigrationReader {
|
|
1845
|
+
count: (table: string, where?: Record<string, unknown>) => Promise<number>;
|
|
1846
|
+
findFirst: (table: string, args?: Record<string, unknown>) => Promise<MigrationDocument | null>;
|
|
1847
|
+
findMany: (table: string, args?: Record<string, unknown>) => Promise<{
|
|
1848
|
+
isDone: boolean;
|
|
1849
|
+
page: MigrationDocument[];
|
|
1771
1850
|
}>;
|
|
1772
|
-
|
|
1773
|
-
|
|
1851
|
+
get: (id: string, expectedTable?: string) => Promise<MigrationDocument | null>;
|
|
1852
|
+
}
|
|
1853
|
+
/** The context handed to a transform alongside the row. */
|
|
1854
|
+
interface MigrationCtx {
|
|
1855
|
+
db: MigrationReader;
|
|
1774
1856
|
}
|
|
1775
1857
|
/**
|
|
1776
|
-
*
|
|
1777
|
-
* `
|
|
1778
|
-
*
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
*
|
|
1783
|
-
*
|
|
1784
|
-
*
|
|
1785
|
-
*
|
|
1786
|
-
*
|
|
1787
|
-
*
|
|
1788
|
-
*
|
|
1858
|
+
* Transform applied to one document. Return a new document to rewrite the row,
|
|
1859
|
+
* or `undefined` to leave it untouched (skipped, not counted as changed). The
|
|
1860
|
+
* runner always preserves the original `_id` and `_creationTime`, so the
|
|
1861
|
+
* returned document neither needs to nor should change row identity.
|
|
1862
|
+
*
|
|
1863
|
+
* The second parameter carries a shard-scoped reader. Without it a transform
|
|
1864
|
+
* could only rewrite the row it was handed — enough for a backfill whose new
|
|
1865
|
+
* value is a pure function of the old row (`displayName = name ?? "Anonymous"`),
|
|
1866
|
+
* but not for the shape people actually write: read the parent, copy a field
|
|
1867
|
+
* down onto its children.
|
|
1868
|
+
*
|
|
1869
|
+
* May return a promise, since a cross-table read is asynchronous.
|
|
1870
|
+
*
|
|
1871
|
+
* **A shard key cannot be backfilled this way, even with a reader.** A row whose
|
|
1872
|
+
* shard-key field is unset does not belong to any shard, so a shard-scoped query
|
|
1873
|
+
* will not enumerate it; and writing the key would have to MOVE the row to a
|
|
1874
|
+
* different Durable Object, which a per-shard runner cannot do. Re-keying is an
|
|
1875
|
+
* export → transform → import, not a migration.
|
|
1789
1876
|
*/
|
|
1877
|
+
type MigrationTransform = (document: MigrationDocument, ctx: MigrationCtx) => MigrationDocument | Promise<MigrationDocument | undefined | void> | undefined | void;
|
|
1878
|
+
interface MigrationDefinition {
|
|
1879
|
+
/** Rows fetched and rewritten per batch. Defaults to the runner's batch size when omitted. */
|
|
1880
|
+
readonly batchSize?: number;
|
|
1881
|
+
/** Optional reverse transform, applied by `migrate down`. */
|
|
1882
|
+
readonly down?: MigrationTransform;
|
|
1883
|
+
/** Stable, unique identifier — the key per-shard run-state is tracked under. */
|
|
1884
|
+
readonly id: string;
|
|
1885
|
+
/** Table whose documents this migration iterates. */
|
|
1886
|
+
readonly table: string;
|
|
1887
|
+
/** Forward transform, applied to every row by `migrate up`. */
|
|
1888
|
+
readonly up: MigrationTransform;
|
|
1889
|
+
}
|
|
1890
|
+
/** A {@link MigrationDefinition} plus the codegen discovery marker. */
|
|
1891
|
+
interface RegisteredMigration extends MigrationDefinition {
|
|
1892
|
+
readonly __lunoraMigration: true;
|
|
1893
|
+
}
|
|
1894
|
+
/** Declare an online data migration. See the module docs for runtime semantics. */
|
|
1895
|
+
declare const defineMigration: (definition: MigrationDefinition) => RegisteredMigration;
|
|
1790
1896
|
/**
|
|
1791
|
-
*
|
|
1792
|
-
*
|
|
1793
|
-
*
|
|
1794
|
-
* `
|
|
1795
|
-
* the merged tables. Extension tables are auto-namespaced by the extension
|
|
1796
|
-
* `key` (`buckets` → `ratelimit_buckets`), so the merged type carries the
|
|
1797
|
-
* prefixed names via {@link PrefixedTables}. Chains:
|
|
1798
|
-
* `defineSchema(...).extend(a).extend(b)` is the typed equivalent of merging
|
|
1799
|
-
* `a`'s prefixed tables then `b`'s.
|
|
1897
|
+
* A mutator declaration. `server` is authoritative; `client` is the optimistic
|
|
1898
|
+
* twin (optional — omit it to let the optimistic write fall through to the
|
|
1899
|
+
* server round-trip with no local preview). Both receive the same validated
|
|
1900
|
+
* `args`.
|
|
1800
1901
|
*/
|
|
1801
|
-
|
|
1802
|
-
extend: <X extends Record<string, TableDefinition>, Key extends string>(extension: SchemaExtension<X> & {
|
|
1803
|
-
readonly key: Key;
|
|
1804
|
-
}) => ExtendableSchema<PrefixedTables<X, Key> & T>;
|
|
1902
|
+
interface MutatorDefinition<Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown> {
|
|
1805
1903
|
/**
|
|
1806
|
-
*
|
|
1807
|
-
* the
|
|
1808
|
-
*
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1904
|
+
* Validator for the mutator's arguments. Validated on the DO before `server`
|
|
1905
|
+
* runs and (when present) on the client before `client` runs, so both impls
|
|
1906
|
+
* see the same parsed shape. Omit for a parameterless mutator.
|
|
1907
|
+
*/
|
|
1908
|
+
readonly args?: Args;
|
|
1909
|
+
/**
|
|
1910
|
+
* Optimistic client implementation. Runs in a TanStack DB transaction
|
|
1911
|
+
* against the local collections; its writes are applied immediately and
|
|
1912
|
+
* automatically rolled back / rebased as the authoritative result syncs
|
|
1913
|
+
* back. Pure and side-effect-free beyond the local store. Omit to skip the
|
|
1914
|
+
* local preview.
|
|
1915
|
+
*/
|
|
1916
|
+
readonly client?: (tx: ClientTx, args: InferValidatorMap<Args>) => Promise<void> | void;
|
|
1917
|
+
/**
|
|
1918
|
+
* Owner-scope the write: names the column carrying the row owner (e.g.
|
|
1919
|
+
* `owner: "userId"`). Before `server` runs, the mutator requires a verified
|
|
1920
|
+
* identity, rejects a client-supplied owner that disagrees with it, and sets
|
|
1921
|
+
* the column to the verified value — so the impl reads `args[owner]` without
|
|
1922
|
+
* trusting the client and never repeats the check by hand.
|
|
1812
1923
|
*
|
|
1813
|
-
*
|
|
1814
|
-
*
|
|
1815
|
-
*
|
|
1816
|
-
*
|
|
1817
|
-
*
|
|
1818
|
-
*
|
|
1819
|
-
* into the new one.
|
|
1924
|
+
* This replaces the "every mutator opens with `assertOwner(ctx, args.userId)`"
|
|
1925
|
+
* pattern, and is the write-side counterpart to an `owner`-scoped
|
|
1926
|
+
* {@link import("./shapes").ShapeDefinition}. Unlike a shape it takes the column
|
|
1927
|
+
* NAME rather than `true`: a shape is bound to one `table`, so the table's
|
|
1928
|
+
* `.ownedBy(field)` resolves unambiguously, whereas one mutator may write
|
|
1929
|
+
* several tables and has no single owning table to read it from.
|
|
1820
1930
|
*
|
|
1821
|
-
*
|
|
1822
|
-
*
|
|
1823
|
-
* settings, not this option.
|
|
1824
|
-
* @see https://developers.cloudflare.com/durable-objects/reference/data-location/
|
|
1931
|
+
* Declare the column `v.optional(...)` to leave it off the wire entirely; it is
|
|
1932
|
+
* injected either way.
|
|
1825
1933
|
*/
|
|
1826
|
-
|
|
1934
|
+
readonly owner?: string;
|
|
1827
1935
|
/**
|
|
1828
|
-
*
|
|
1829
|
-
*
|
|
1830
|
-
*
|
|
1831
|
-
*
|
|
1832
|
-
* mode, so `.rls("required")` composes with `.extend(...)` either order.
|
|
1936
|
+
* Authoritative server implementation. Runs inside the shard DO with a full
|
|
1937
|
+
* {@link MutationContext} (`ctx.db` writer); its writes append to `__cdc_log`
|
|
1938
|
+
* and poke back to subscribers. This is the source of truth — the client
|
|
1939
|
+
* impl is only a prediction of it.
|
|
1833
1940
|
*/
|
|
1834
|
-
|
|
1835
|
-
}
|
|
1836
|
-
|
|
1941
|
+
readonly server: (context: ServerContext, args: InferValidatorMap<Args>) => Promise<R> | R;
|
|
1942
|
+
}
|
|
1943
|
+
/**
|
|
1944
|
+
* A {@link MutatorDefinition} plus the codegen discovery marker and a
|
|
1945
|
+
* dispatch-shaped `handler` (validates `args`, then runs `server`) so the DO
|
|
1946
|
+
* invokes a mutator exactly like a registered procedure.
|
|
1947
|
+
*/
|
|
1948
|
+
interface RegisteredMutator<Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown> extends MutatorDefinition<Args, ServerContext, ClientTx, R> {
|
|
1949
|
+
readonly __lunoraMutator: true;
|
|
1950
|
+
/** Validate `rawArgs`, then run the authoritative `server` impl. Used by the DO push path. */
|
|
1951
|
+
readonly handler: (context: ServerContext, rawArgs: Record<string, unknown>) => Promise<R>;
|
|
1952
|
+
/**
|
|
1953
|
+
* Marks the dispatch kind so codegen can register the mutator in the same
|
|
1954
|
+
* `LUNORA_FUNCTIONS` table queries/mutations use — the DO's `handleRpc`
|
|
1955
|
+
* reads `kind === "mutation"` to wrap the authoritative `server` impl in the
|
|
1956
|
+
* shard's BEGIN/COMMIT span (all-or-nothing writes), exactly like an
|
|
1957
|
+
* ordinary `mutation`.
|
|
1958
|
+
*/
|
|
1959
|
+
readonly kind: "mutation";
|
|
1960
|
+
}
|
|
1961
|
+
/** Declare a custom mutator. See the module docs for runtime semantics. */
|
|
1962
|
+
declare const defineMutator: <Args extends ValidatorMap = ValidatorMap, ServerContext = MutationCtx, ClientTx = unknown, R = unknown>(definition: MutatorDefinition<Args, ServerContext, ClientTx, R>) => RegisteredMutator<Args, ServerContext, ClientTx, R>;
|
|
1837
1963
|
/** Default time-to-live for a presence row: a heartbeat keeps a member "present" for this long. */
|
|
1838
1964
|
declare const DEFAULT_TTL_MS = 3e4;
|
|
1839
1965
|
declare const PRESENCE_BARE_TABLE = "present";
|
|
@@ -2221,6 +2347,13 @@ interface DatabaseWriterLike {
|
|
|
2221
2347
|
* Required for the same reason as `aggregate`.
|
|
2222
2348
|
*/
|
|
2223
2349
|
rankPage: (tableName: string, indexName: string, options?: RankPageArgs) => Promise<QueryPage>;
|
|
2350
|
+
/**
|
|
2351
|
+
* Cross-shard companion to `rankPage`: same ranked slice, but each row
|
|
2352
|
+
* keeps its rank-key tuple for the query coordinator's k-way merge. Same
|
|
2353
|
+
* count-of-partition RLS hazard as `rankPage` — failed closed under a read
|
|
2354
|
+
* policy for the identical reason (see `rankPage` above).
|
|
2355
|
+
*/
|
|
2356
|
+
rankPageRows?: (tableName: string, indexName: string, options?: RankPageArgs) => Promise<ShardRankPageResultLike>;
|
|
2224
2357
|
replace: (id: string, document: Record<string, unknown>, expectedTable?: string) => Promise<void>;
|
|
2225
2358
|
restore?: (id: string, expectedTable?: string) => Promise<void>;
|
|
2226
2359
|
/**
|
|
@@ -2502,4 +2635,4 @@ interface StorageContextIn {
|
|
|
2502
2635
|
}
|
|
2503
2636
|
declare const storageRules: <Context extends StorageContextIn = StorageContextIn>(rules: ReadonlyArray<StorageRule<Context>>, options?: StorageRulesOptions) => Middleware<Context, Context>;
|
|
2504
2637
|
declare const VERSION = "0.0.0";
|
|
2505
|
-
export { type ActionBuilder, type ActionCtx, type AggregateIndexDefinition, type AggregateIndexOptions, type AggregateOp, type ArgsValidator, type Component, type ComponentFunctions, type CreateOptions, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, type DataModelInit, type DefineComponentOptions, type DefineIdentityOptions, type DefineListArgsConfig, type DefinePluginOptions, type DefinePolicyInput, type DefinePresenceOptions, type DefineStorageRuleInput, type DurableObjectJurisdiction, type EmptyArgs, type EnvAccessor, type EnvKeyFailure, type EnvShape, type ExposeConfig, type ExtendableSchema, type FacadeEntry, type FacadeWriterLike, type FunctionKind, type HttpActionCtx, type HttpActionHandler, type HttpMethod, type HttpRoute, type HttpRouteBuilder, type HttpRouteFactory, type HttpRouteHandlerOptions, type HttpStreamHandlerOptions, type IdentityContract, type IdentityRejectMode, type IdentityValidation, type InferArgs, type InferEnv, type InferIdentity, type InlineAggregateIndexOptions, type InlineRankIndexOptions, type InternalActionBuilder, type InternalMutationBuilder, type InternalQueryBuilder, type LifecycleEvent, type LifecycleHandler, type ListArgsSpec, type ListArgsValidators, type ListArgsValue, type ListFilterOperators, type ListOrderByEntry, type ListWhere, type LunoraBuilders, LunoraEnvError, LunoraError, type LunoraHttpApp, type LunoraHttpEnv, type LunoraRouteHandler, type ManyRelation, type MaskColumns, type MaskContext, type MaskFn, type MaskOptions, type MaskPolicies, type MaskStrategy, type Middleware, type MiddlewareNext, type MigrationCtx, type MigrationDefinition, type MigrationDocument, type MigrationReader, type MigrationTransform, type MutationBuilder, type MutationCtx, type MutatorDefinition, type OnDeleteAction, type OneRelation, type OrmLike, DEFAULT_TTL_MS as PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, type Permission, type Plugin, type Policy, type PrefixedTables, type PresenceComponent, type PresenceFunctions, type PresenceMember, type ProtectPublicOptions, type QueryBuilder, type QueryCtx, type RankIndexDefinition, type RankIndexOptions, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMigration, type RegisteredMutation, type RegisteredMutator, type RegisteredQuery, type RegisteredShape, type RegisteredStream, type RelationBuilder, type RelationDefinition, type RlsOptions, type RlsReadRegistry, type Role, type Schema, type SchemaExtension, type ShapeDefinition, type ShapeReadWhereRequest, type StorageOperation, type StorageRule, type StorageRuleContext, type StorageRuleDecision, type StorageRulesOptions, type TableBuilder, type TableDefinition, type TerminalKind, type TriggerBuilder, type TriggerDefinition, type TypedDefinePolicyInput, VERSION, type VectorEmbedder, type VectorIndexDefinition, type VectorIndexOptions, type VectorMetric, type VectorizeOptions, type WhereInput, allowAll, asBucketStorage, bindOrm, bindTableFacade, buildRlsReadRegistry, clampLimit, composePluginMiddleware, composeShapeReadWhere, createPolicyDsl, createSecrets, defineAggregateIndex, defineComponent, defineEnv, defineIdentity, defineListArgs, defineMigration, defineMutator, definePermission, definePlugin, definePolicies, definePolicy, definePresence, defineRankIndex, defineRole, defineSchema, defineSchemaExtension, defineShape, defineStorageRule, defineStorageRules, defineTable, defineVectorIndex, deny, httpAction, httpRoute, httpRouter, initLunora, installPlugins, isDeny, isSafeHeaderValue, mask, mergeSchemaExtension, onConnect, onDisconnect, presenceExtension, protectPublic, redactSecrets, rls, serveStorageObject, storageRules, toWhereInput };
|
|
2638
|
+
export { type ActionBuilder, type ActionCtx, type AggregateIndexDefinition, type AggregateIndexOptions, type AggregateOp, type ArgsValidator, type Component, type ComponentFunctions, type CreateOptions, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, type DataModelInit, type DefineComponentOptions, type DefineIdentityOptions, type DefineListArgsConfig, type DefinePluginOptions, type DefinePolicyInput, type DefinePresenceOptions, type DefineStorageRuleInput, type DurableObjectJurisdiction, type EmptyArgs, type EnvAccessor, type EnvKeyFailure, type EnvShape, type ExposeConfig, type ExtendableSchema, type FacadeEntry, type FacadeWriterLike, type FunctionKind, type HttpActionCtx, type HttpActionHandler, type HttpMethod, type HttpRoute, type HttpRouteBuilder, type HttpRouteFactory, type HttpRouteHandlerOptions, type HttpStreamHandlerOptions, type IdentityContract, type IdentityRejectMode, type IdentityValidation, type IndexFieldsByTable, type InferArgs, type InferEnv, type InferIdentity, type InlineAggregateIndexOptions, type InlineRankIndexOptions, type InternalActionBuilder, type InternalMutationBuilder, type InternalQueryBuilder, type LifecycleEvent, type LifecycleHandler, type ListArgsSpec, type ListArgsValidators, type ListArgsValue, type ListFilterOperators, type ListOrderByEntry, type ListWhere, type LunoraBuilders, LunoraEnvError, LunoraError, type LunoraHttpApp, type LunoraHttpEnv, type LunoraRouteHandler, type ManyRelation, type MaskColumns, type MaskContext, type MaskFn, type MaskOptions, type MaskPolicies, type MaskRegistry, type MaskStrategy, type Middleware, type MiddlewareNext, type MigrationCtx, type MigrationDefinition, type MigrationDocument, type MigrationReader, type MigrationTransform, type MutationBuilder, type MutationCtx, type MutatorDefinition, type OnDeleteAction, type OneRelation, type OrmLike, DEFAULT_TTL_MS as PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, type Permission, type Plugin, type Policy, type PrefixedTables, type PresenceComponent, type PresenceFunctions, type PresenceMember, type ProtectPublicOptions, type QueryBuilder, type QueryCtx, type RankIndexDefinition, type RankIndexOptions, type RegisteredAction, type RegisteredFunction, type RegisteredLifecycleHook, type RegisteredMigration, type RegisteredMutation, type RegisteredMutator, type RegisteredQuery, type RegisteredShape, type RegisteredStream, type RelationBuilder, type RelationDefinition, type RlsOptions, type RlsReadRegistry, type Role, type Schema, type SchemaExtension, type ShapeDefinition, type ShapeReadWhereRequest, type StorageOperation, type StorageRule, type StorageRuleContext, type StorageRuleDecision, type StorageRulesOptions, type TableBuilder, type TableDefinition, type TerminalKind, type TriggerBuilder, type TriggerDefinition, type TypedDefinePolicyInput, VERSION, type VectorEmbedder, type VectorIndexDefinition, type VectorIndexOptions, type VectorMetric, type VectorizeOptions, type WhereInput, allowAll, asBucketStorage, bindOrm, bindTableFacade, buildMaskRegistry, buildRlsReadRegistry, clampLimit, composePluginMiddleware, composeShapeReadWhere, createPolicyDsl, createSecrets, defineAggregateIndex, defineComponent, defineEnv, defineIdentity, defineListArgs, defineMigration, defineMutator, definePermission, definePlugin, definePolicies, definePolicy, definePresence, defineRankIndex, defineRole, defineSchema, defineSchemaExtension, defineShape, defineStorageRule, defineStorageRules, defineTable, defineVectorIndex, deny, httpAction, httpRoute, httpRouter, indexFieldsFromSchema, initLunora, installPlugins, isDeny, isSafeHeaderValue, mask, mergeSchemaExtension, onConnect, onDisconnect, presenceExtension, protectPublic, redactSecrets, rls, serveStorageObject, storageRules, toWhereInput };
|