@lunora/server 1.0.0-alpha.21 → 1.0.0-alpha.211
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/LICENSE.md +213 -0
- package/README.md +18 -12
- package/dist/data-model.d.mts +392 -170
- package/dist/data-model.d.ts +392 -170
- package/dist/data-model.mjs +0 -1
- package/dist/drizzle.mjs +1 -1
- package/dist/in-process.d.mts +101 -0
- package/dist/in-process.d.ts +101 -0
- package/dist/in-process.mjs +1 -0
- package/dist/index.d.mts +3618 -1548
- package/dist/index.d.ts +3618 -1548
- package/dist/index.mjs +1 -29
- package/dist/otel.d.mts +554 -0
- package/dist/otel.d.ts +554 -0
- package/dist/otel.mjs +1 -0
- package/dist/packem_shared/ACTION_CACHE_DEFAULT_TTL_MS-mmP-6BcT.mjs +1 -0
- package/dist/packem_shared/DEFAULT_LIMIT-pT_tV8R0.mjs +1 -0
- package/dist/packem_shared/DOCUMENT_HISTORY_REDACTED_FIELDS-B5Zp-7SI.mjs +1 -0
- package/dist/packem_shared/LunoraEnvError-bkdMu_-N.mjs +4 -0
- package/dist/packem_shared/PRESENCE_DEFAULT_TTL_MS-DFpy5J8G.mjs +1 -0
- package/dist/packem_shared/allowAll-DkRAgItV.mjs +1 -0
- package/dist/packem_shared/apply-output-C5wZ5EAL.mjs +1 -0
- package/dist/packem_shared/asBucketStorage-DoGYOjq3.mjs +1 -0
- package/dist/packem_shared/assertShapesDeclareReadPolicies-Bg4Ter7Q.mjs +1 -0
- package/dist/packem_shared/beginDeferredDeletes-DhwFBbvs.mjs +1 -0
- package/dist/packem_shared/beginDeferredSchedules-C7QLYr6C.mjs +1 -0
- package/dist/packem_shared/bindOrm-9fbLRbOb.mjs +1 -0
- package/dist/packem_shared/buildMaskRegistry-DpWMtalG.mjs +1 -0
- package/dist/packem_shared/compose-middleware-BK9FlblN.mjs +1 -0
- package/dist/packem_shared/composePluginMiddleware-ZtDIkbvJ.mjs +1 -0
- package/dist/packem_shared/context-identity-B2XYGA0B.mjs +1 -0
- package/dist/packem_shared/createPolicyDsl-BZa6SqLJ.mjs +1 -0
- package/dist/packem_shared/createSecrets-D6rLB42U.mjs +1 -0
- package/dist/packem_shared/createServices-IJvoqi0e.mjs +1 -0
- package/dist/packem_shared/defineAggregateIndex-DF8NiKHr.mjs +1 -0
- package/dist/packem_shared/defineIdentity-DwkNKwYa.mjs +1 -0
- package/dist/packem_shared/defineIdentityGuard-Bt8N65Zs.mjs +1 -0
- package/dist/packem_shared/defineMigration-CXOS0Bvq.mjs +1 -0
- package/dist/packem_shared/defineModule-OecXWM3U.mjs +1 -0
- package/dist/packem_shared/defineMutator-B4ipDtDR.mjs +1 -0
- package/dist/packem_shared/defineRoom-CoGi-xwm.mjs +1 -0
- package/dist/packem_shared/defineShape-CwKm5KLp.mjs +1 -0
- package/dist/packem_shared/defineStorageRule-Dv4nJE0H.mjs +1 -0
- package/dist/packem_shared/fnv1a-D3ueNTWB.mjs +1 -0
- package/dist/packem_shared/functions-B2-H4PQT.mjs +1 -0
- package/dist/packem_shared/httpAction-D2S7bQzl.mjs +6 -0
- package/dist/packem_shared/initLunora-B-7Xs3Qq.mjs +1 -0
- package/dist/packem_shared/isPerDispatchMiddleware-DDQPatSm.mjs +1 -0
- package/dist/packem_shared/mask-D-jRCAhy.mjs +1 -0
- package/dist/packem_shared/middleware-3ySZVrpt.mjs +1 -0
- package/dist/packem_shared/onConnect-BLRoOpv2.mjs +1 -0
- package/dist/packem_shared/onQueryChange-iuLzmyPs.mjs +1 -0
- package/dist/packem_shared/onWhisper-CfDbFLQh.mjs +1 -0
- package/dist/packem_shared/platformAdmin-kczXQme8.mjs +1 -0
- package/dist/packem_shared/plugin-Bm9wIpiI.mjs +1 -0
- package/dist/packem_shared/policy-tag-V0lqFzgS.mjs +1 -0
- package/dist/packem_shared/protectPublic-Bi5_95aP.mjs +1 -0
- package/dist/packem_shared/rls-Dwj6TlY_.mjs +1 -0
- package/dist/packem_shared/run-middleware-BefYq7Xd.mjs +1 -0
- package/dist/packem_shared/serveStorageObject-aqBFrLyo.mjs +1 -0
- package/dist/packem_shared/storageRules-CBLioxF7.mjs +1 -0
- package/dist/packem_shared/types.d-B7FLdYUD.d.mts +148 -0
- package/dist/packem_shared/types.d-BCtjbIzl.d.ts +148 -0
- package/dist/rls/testing.d.mts +42 -34
- package/dist/rls/testing.d.ts +42 -34
- package/dist/rls/testing.mjs +1 -49
- package/dist/types.d.mts +2234 -623
- package/dist/types.d.ts +2234 -623
- package/dist/types.mjs +0 -31
- package/package.json +24 -6
- package/dist/packem_shared/LunoraEnvError-BGmd1Qs0.mjs +0 -187
- package/dist/packem_shared/LunoraError-WbxmrpxR.mjs +0 -9
- package/dist/packem_shared/PRESENCE_DEFAULT_TTL_MS-BtPCiNLW.mjs +0 -114
- package/dist/packem_shared/asBucketStorage-Cnxd9y2q.mjs +0 -11
- package/dist/packem_shared/bindOrm-Bfgl7f-Q.mjs +0 -146
- package/dist/packem_shared/buildRlsReadRegistry-D54vUQe4.mjs +0 -107
- package/dist/packem_shared/composePluginMiddleware-CcTj1_v2.mjs +0 -103
- package/dist/packem_shared/createPolicyDsl-By3QB4he.mjs +0 -32
- package/dist/packem_shared/createSecrets-DwaR2rNG.mjs +0 -58
- package/dist/packem_shared/defineAggregateIndex-znq4ygKQ.mjs +0 -295
- package/dist/packem_shared/defineIdentity-DiX4zM9x.mjs +0 -35
- package/dist/packem_shared/defineMigration-Hx01yIht.mjs +0 -10
- package/dist/packem_shared/defineMutator-EIXAWhs9.mjs +0 -11
- package/dist/packem_shared/defineShape-C5scNOrf.mjs +0 -18
- package/dist/packem_shared/defineStorageRule-B5nL4Z1P.mjs +0 -23
- package/dist/packem_shared/functions-Di9FUNkf.mjs +0 -5
- package/dist/packem_shared/httpAction-DCXoYPIk.mjs +0 -355
- package/dist/packem_shared/initLunora-sQUqaejx.mjs +0 -100
- package/dist/packem_shared/mask-BT1YgRbF.mjs +0 -291
- package/dist/packem_shared/onConnect-CIPXKPyw.mjs +0 -13
- package/dist/packem_shared/policy-tag-DvpVH2tv.mjs +0 -13
- package/dist/packem_shared/protectPublic-BlcGpiRc.mjs +0 -15
- package/dist/packem_shared/rls-B_ZWgslr.mjs +0 -595
- package/dist/packem_shared/run-middleware-I6EiQfxL.mjs +0 -20
- package/dist/packem_shared/storageRules-6QxzDOcx.mjs +0 -88
- package/dist/packem_shared/types.d-BB3pjV0m.d.mts +0 -141
- package/dist/packem_shared/types.d-Cxl6ndhm.d.ts +0 -141
package/dist/data-model.d.ts
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Schema-independent type machinery for the generated data model.
|
|
3
|
-
*
|
|
4
|
-
* `@lunora/codegen` emits `lunora/_generated/dataModel.ts` with the
|
|
5
|
-
* schema-specific pieces (the per-table `Doc_*` / `Insert_*` interfaces, the
|
|
6
|
-
* `DataModel` / `Relations` / index-name maps) and then binds the generics
|
|
7
|
-
* below to them. Everything here is identical for every project, so it lives
|
|
8
|
-
* in the shipped package rather than in generated output — evolving the query
|
|
9
|
-
* DSL or the table-facade API no longer regenerates a single line of a user's
|
|
10
|
-
* `_generated` directory.
|
|
11
|
-
*
|
|
12
|
-
* The generics are parameterized over the generated maps:
|
|
13
|
-
* - `DM` — `DataModel`: table name → document type
|
|
14
|
-
* - `IM` — `InsertModel`: table name → insert shape
|
|
15
|
-
* - `REL` — `Relations`: table name → relation-descriptor map
|
|
16
|
-
* - `RANK` — `RankIndexNamesByTable`: table name → declared rank-index names
|
|
17
|
-
* - `SEARCH` — `SearchIndexNamesByTable`: table name → declared search-index names
|
|
18
|
-
*
|
|
19
|
-
* Relation descriptors are matched structurally (`{ __relationKind; __target }`)
|
|
20
|
-
* so this module needs no reference to the project-local `OneRelation` /
|
|
21
|
-
* `ManyRelation` aliases the codegen still emits.
|
|
22
|
-
*/
|
|
2
|
+
* Schema-independent type machinery for the generated data model.
|
|
3
|
+
*
|
|
4
|
+
* `@lunora/codegen` emits `lunora/_generated/dataModel.ts` with the
|
|
5
|
+
* schema-specific pieces (the per-table `Doc_*` / `Insert_*` interfaces, the
|
|
6
|
+
* `DataModel` / `Relations` / index-name maps) and then binds the generics
|
|
7
|
+
* below to them. Everything here is identical for every project, so it lives
|
|
8
|
+
* in the shipped package rather than in generated output — evolving the query
|
|
9
|
+
* DSL or the table-facade API no longer regenerates a single line of a user's
|
|
10
|
+
* `_generated` directory.
|
|
11
|
+
*
|
|
12
|
+
* The generics are parameterized over the generated maps:
|
|
13
|
+
* - `DM` — `DataModel`: table name → document type
|
|
14
|
+
* - `IM` — `InsertModel`: table name → insert shape
|
|
15
|
+
* - `REL` — `Relations`: table name → relation-descriptor map
|
|
16
|
+
* - `RANK` — `RankIndexNamesByTable`: table name → declared rank-index names
|
|
17
|
+
* - `SEARCH` — `SearchIndexNamesByTable`: table name → declared search-index names
|
|
18
|
+
*
|
|
19
|
+
* Relation descriptors are matched structurally (`{ __relationKind; __target }`)
|
|
20
|
+
* so this module needs no reference to the project-local `OneRelation` /
|
|
21
|
+
* `ManyRelation` aliases the codegen still emits.
|
|
22
|
+
*/
|
|
23
23
|
/** A branded id for table `TName`. Structurally a `string` at runtime. */
|
|
24
24
|
type Id<TName extends string> = string & {
|
|
25
25
|
readonly __table: TName;
|
|
@@ -37,66 +37,105 @@ interface WhereOperators<T> {
|
|
|
37
37
|
ne?: T;
|
|
38
38
|
notIn?: T[];
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* A value stored as a plain JS object: not a scalar (an `Id<T>` is a branded string, which is an `object` to
|
|
42
|
+
* TypeScript but has no keys worth offering), an array, a date, bytes, a bigint or a function.
|
|
43
|
+
*/
|
|
44
|
+
type IsPlainObject<V> = V extends string | number | boolean | bigint | symbol ? false : V extends ReadonlyArray<unknown> ? false : V extends Date | Uint8Array | ArrayBuffer | bigint | ((...arguments_: never[]) => unknown) ? false : V extends object ? true : false;
|
|
45
|
+
/** The dotted paths below an object, to three steps. */
|
|
46
|
+
type SubPaths<V, Depth extends unknown[]> = Depth["length"] extends 3 ? never : [IsPlainObject<V>] extends [true] ? { [K in keyof V & string]-?: K | `${K}.${SubPaths<NonNullable<V[K]>, [...Depth, 0]>}`; }[keyof V & string] : never;
|
|
47
|
+
/** The type at the end of dotted path `P` below `V`. */
|
|
48
|
+
type PathValue<V, P extends string> = P extends `${infer Head}.${infer Rest}` ? Head extends keyof V ? PathValue<NonNullable<V[Head]>, Rest> : never : P extends keyof V ? V[P] : never;
|
|
49
|
+
/**
|
|
50
|
+
* Every dotted path into an object-valued column of `Doc` (`"profile.address.city"`), at most four segments
|
|
51
|
+
* counting the column. A column that is not a plain object contributes none.
|
|
52
|
+
*/
|
|
53
|
+
type ColumnPaths<Doc> = { [K in keyof Doc & string]-?: [IsPlainObject<NonNullable<Doc[K]>>] extends [true] ? `${K}.${SubPaths<NonNullable<Doc[K]>, []>}` : never; }[keyof Doc & string];
|
|
54
|
+
/** The type at the end of a {@link ColumnPaths} path. */
|
|
55
|
+
type DocPathValue<Doc, P extends string> = P extends `${infer Column}.${infer Rest}` ? Column extends keyof Doc ? PathValue<NonNullable<Doc[Column]>, Rest> : never : never;
|
|
56
|
+
type ScalarLeaf = boolean | number | string | null | undefined;
|
|
57
|
+
/** The dotted paths into an object column that end at a string, number or boolean. */
|
|
58
|
+
type ScalarPaths<Doc> = { [P in ColumnPaths<Doc>]: [DocPathValue<Doc, P>] extends [ScalarLeaf] ? P : never; }[ColumnPaths<Doc>];
|
|
59
|
+
/**
|
|
60
|
+
* `where` keys that reach into an object column: `{ "profile.plan": "pro" }`. Only paths that end at a string,
|
|
61
|
+
* number or boolean are offered — an object, array, date, bigint or bytes leaf has no single SQL comparison.
|
|
62
|
+
*
|
|
63
|
+
* `unknown` (the identity for `&`) when there are none, never `{}`: intersecting with an empty object type
|
|
64
|
+
* switches off the weak-type check that rejects a `where` key naming nothing.
|
|
65
|
+
*/
|
|
66
|
+
type PathWhere<Doc, Paths extends string = ScalarPaths<Doc>> = [Paths] extends [never] ? unknown : { [P in Paths]?: DocPathValue<Doc, P> | WhereOperators<DocPathValue<Doc, P>>; };
|
|
67
|
+
/** `patch` keys that set one value inside an object column, keeping its siblings: `{ "profile.plan": "pro" }`. */
|
|
68
|
+
type PathPatch<Doc, Paths extends string = ColumnPaths<Doc>> = [Paths] extends [never] ? unknown : { [P in Paths]?: DocPathValue<Doc, P>; };
|
|
40
69
|
/** A typed `where` tree over a document's columns. */
|
|
41
|
-
type Where<TDocument> = { [K in keyof TDocument]?: TDocument[K] | WhereOperators<TDocument[K]
|
|
70
|
+
type Where<TDocument> = PathWhere<TDocument> & { [K in keyof TDocument]?: TDocument[K] | WhereOperators<TDocument[K]>; } & {
|
|
42
71
|
AND?: Where<TDocument>[];
|
|
43
72
|
NOT?: Where<TDocument>;
|
|
44
73
|
OR?: Where<TDocument>[];
|
|
45
74
|
};
|
|
46
|
-
/**
|
|
47
|
-
|
|
75
|
+
/**
|
|
76
|
+
* One ordering entry; `orderBy` is an ordered list. `{ field: "asc" | "desc" }` sorts NULLs first
|
|
77
|
+
* ascending and last descending. `{ field: { sort: "desc", nulls: "first" } }` places them yourself.
|
|
78
|
+
*
|
|
79
|
+
* A placement other than the default can change the query plan (see Pagination in the docs), so
|
|
80
|
+
* measure a hot read. It has no effect on a column that cannot hold NULL. Keep the same `orderBy` for every page of a
|
|
81
|
+
* paginated read, as a cursor is only valid against the ordering that minted it.
|
|
82
|
+
*/
|
|
83
|
+
type OrderBy<TDocument> = Partial<Record<keyof TDocument | ScalarPaths<TDocument>, "asc" | "desc" | {
|
|
84
|
+
nulls: "first" | "last";
|
|
85
|
+
sort: "asc" | "desc";
|
|
86
|
+
}>>;
|
|
48
87
|
interface QueryArgs<TDocument> {
|
|
49
88
|
cursor?: null | string;
|
|
50
89
|
limit?: number;
|
|
51
90
|
orderBy?: OrderBy<TDocument>[];
|
|
52
91
|
/**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
92
|
+
* Project each returned row down to these columns (plus the system fields
|
|
93
|
+
* `_id`/`_creationTime`, always retained). Trims wire payload for wide rows;
|
|
94
|
+
* relations requested via `with` are still attached. Reactivity is unaffected —
|
|
95
|
+
* the engine still reads the whole row to track dependencies.
|
|
96
|
+
*/
|
|
58
97
|
select?: ReadonlyArray<keyof TDocument & string>;
|
|
59
98
|
where?: Where<TDocument>;
|
|
60
99
|
}
|
|
61
100
|
/**
|
|
62
|
-
* A to-one relation predicate node. `is` matches rows whose related record
|
|
63
|
-
* satisfies `W`; `isNot` matches rows whose related record fails `W` *or* has
|
|
64
|
-
* no related record at all (a null/dangling FK) — Prisma's semantics.
|
|
65
|
-
*/
|
|
101
|
+
* A to-one relation predicate node. `is` matches rows whose related record
|
|
102
|
+
* satisfies `W`; `isNot` matches rows whose related record fails `W` *or* has
|
|
103
|
+
* no related record at all (a null/dangling FK) — Prisma's semantics.
|
|
104
|
+
*/
|
|
66
105
|
interface OneRelationWhere<W> {
|
|
67
106
|
is?: W;
|
|
68
107
|
isNot?: W;
|
|
69
108
|
}
|
|
70
109
|
/**
|
|
71
|
-
* A to-many relation predicate node. `some` ⇒ at least one related row matches
|
|
72
|
-
* `W`; `none` ⇒ no related row matches (childless parents included); `every` ⇒
|
|
73
|
-
* every *readable* related row matches (vacuously true for childless parents).
|
|
74
|
-
*/
|
|
110
|
+
* A to-many relation predicate node. `some` ⇒ at least one related row matches
|
|
111
|
+
* `W`; `none` ⇒ no related row matches (childless parents included); `every` ⇒
|
|
112
|
+
* every *readable* related row matches (vacuously true for childless parents).
|
|
113
|
+
*/
|
|
75
114
|
interface ManyRelationWhere<W> {
|
|
76
115
|
every?: W;
|
|
77
116
|
none?: W;
|
|
78
117
|
some?: W;
|
|
79
118
|
}
|
|
80
119
|
/**
|
|
81
|
-
* The relation-predicate portion of {@link WhereOf}: each declared relation on
|
|
82
|
-
* `T` contributes a kind-dispatched node — `one` → `{ is?; isNot? }`, `many` →
|
|
83
|
-
* `{ some?; none?; every? }` — whose inner type is the target table's own
|
|
84
|
-
* relation-aware `where` (so multi-hop predicates type-check inside-out).
|
|
85
|
-
*/
|
|
120
|
+
* The relation-predicate portion of {@link WhereOf}: each declared relation on
|
|
121
|
+
* `T` contributes a kind-dispatched node — `one` → `{ is?; isNot? }`, `many` →
|
|
122
|
+
* `{ some?; none?; every? }` — whose inner type is the target table's own
|
|
123
|
+
* relation-aware `where` (so multi-hop predicates type-check inside-out).
|
|
124
|
+
*/
|
|
86
125
|
type RelationWhere<DM, REL extends Record<keyof DM, object>, T extends keyof DM> = { [K in keyof REL[T]]?: REL[T][K] extends {
|
|
87
126
|
__relationKind: "one";
|
|
88
127
|
__target: infer Target extends keyof DM;
|
|
89
128
|
} ? OneRelationWhere<WhereOf<DM, REL, Target>> : REL[T][K] extends {
|
|
90
129
|
__relationKind: "many";
|
|
91
130
|
__target: infer Target extends keyof DM;
|
|
92
|
-
} ? ManyRelationWhere<WhereOf<DM, REL, Target>> : never };
|
|
131
|
+
} ? ManyRelationWhere<WhereOf<DM, REL, Target>> : never; };
|
|
93
132
|
/**
|
|
94
|
-
* Relation-aware `where` tree — the column predicates of {@link Where} plus
|
|
95
|
-
* Prisma-style relation predicates resolved by the `@lunora/do` pre-resolver.
|
|
96
|
-
* `Where
|
|
97
|
-
* table facade threads `REL` through this richer form.
|
|
98
|
-
*/
|
|
99
|
-
type WhereOf<DM, REL extends Record<keyof DM, object>, T extends keyof DM> = RelationWhere<DM, REL, T> & { [K in keyof DM[T]]?: DM[T][K] | WhereOperators<DM[T][K]
|
|
133
|
+
* Relation-aware `where` tree — the column predicates of {@link Where} plus
|
|
134
|
+
* Prisma-style relation predicates resolved by the `@lunora/do` pre-resolver.
|
|
135
|
+
* `Where<DM[T]>` stays the column-only structural mirror for back-compat; the
|
|
136
|
+
* table facade threads `REL` through this richer form.
|
|
137
|
+
*/
|
|
138
|
+
type WhereOf<DM, REL extends Record<keyof DM, object>, T extends keyof DM> = PathWhere<DM[T]> & RelationWhere<DM, REL, T> & { [K in keyof DM[T]]?: DM[T][K] | WhereOperators<DM[T][K]>; } & {
|
|
100
139
|
AND?: WhereOf<DM, REL, T>[];
|
|
101
140
|
NOT?: WhereOf<DM, REL, T>;
|
|
102
141
|
OR?: WhereOf<DM, REL, T>[];
|
|
@@ -105,10 +144,10 @@ type WhereOf<DM, REL extends Record<keyof DM, object>, T extends keyof DM> = Rel
|
|
|
105
144
|
interface QueryArgsOf<DM, REL extends Record<keyof DM, object>, T extends keyof DM> {
|
|
106
145
|
cursor?: null | string;
|
|
107
146
|
/**
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
147
|
+
* Include soft-deleted rows (`.softDelete()` tables only). Default hides them;
|
|
148
|
+
* `true` returns deleted rows alongside live ones. No effect on a table
|
|
149
|
+
* without `.softDelete()`.
|
|
150
|
+
*/
|
|
112
151
|
includeDeleted?: boolean;
|
|
113
152
|
limit?: number;
|
|
114
153
|
orderBy?: OrderBy<DM[T]>[];
|
|
@@ -128,11 +167,11 @@ type NestedSelectArgument<WK> = WK extends {
|
|
|
128
167
|
select: infer S;
|
|
129
168
|
} ? S : undefined;
|
|
130
169
|
/**
|
|
131
|
-
* The `with` argument for table `T`: each relation can be `true` (load with no
|
|
132
|
-
* refinements) or an object. `many` relations accept `where`/`orderBy`/`limit`/
|
|
133
|
-
* `select` plus a nested `with`; `one` relations accept `select` + a nested
|
|
134
|
-
* `with`. The reserved `_count` key requests per-relation aggregate counts.
|
|
135
|
-
*/
|
|
170
|
+
* The `with` argument for table `T`: each relation can be `true` (load with no
|
|
171
|
+
* refinements) or an object. `many` relations accept `where`/`orderBy`/`limit`/
|
|
172
|
+
* `select` plus a nested `with`; `one` relations accept `select` + a nested
|
|
173
|
+
* `with`. The reserved `_count` key requests per-relation aggregate counts.
|
|
174
|
+
*/
|
|
136
175
|
type WithArg<DM, REL extends Record<keyof DM, object>, T extends keyof DM> = { [K in keyof REL[T]]?: REL[T][K] extends {
|
|
137
176
|
__relationKind: "many";
|
|
138
177
|
__target: infer Target extends keyof DM;
|
|
@@ -144,15 +183,15 @@ type WithArg<DM, REL extends Record<keyof DM, object>, T extends keyof DM> = { [
|
|
|
144
183
|
} ? boolean | {
|
|
145
184
|
select?: ReadonlyArray<keyof DM[Target] & string>;
|
|
146
185
|
with?: WithArg<DM, REL, Target>;
|
|
147
|
-
} : never } & {
|
|
148
|
-
_count?: { [K in keyof REL[T]]?: true };
|
|
186
|
+
} : never; } & {
|
|
187
|
+
_count?: { [K in keyof REL[T]]?: true; };
|
|
149
188
|
};
|
|
150
189
|
/**
|
|
151
|
-
* Resolve a single relation descriptor + its with-value to the loaded type,
|
|
152
|
-
* threading the nested `select` tuple into the projected child shape (the 5th
|
|
153
|
-
* `LoadWith` arg) so `with: { author: { select: ["name"] } }` narrows the loaded
|
|
154
|
-
* `author` to the selected columns + system fields.
|
|
155
|
-
*/
|
|
190
|
+
* Resolve a single relation descriptor + its with-value to the loaded type,
|
|
191
|
+
* threading the nested `select` tuple into the projected child shape (the 5th
|
|
192
|
+
* `LoadWith` arg) so `with: { author: { select: ["name"] } }` narrows the loaded
|
|
193
|
+
* `author` to the selected columns + system fields.
|
|
194
|
+
*/
|
|
156
195
|
type LoadRelation<DM, REL extends Record<keyof DM, object>, R, WK> = R extends {
|
|
157
196
|
__relationKind: "one";
|
|
158
197
|
__target: infer Target extends keyof DM;
|
|
@@ -161,53 +200,53 @@ type LoadRelation<DM, REL extends Record<keyof DM, object>, R, WK> = R extends {
|
|
|
161
200
|
__target: infer Target extends keyof DM;
|
|
162
201
|
} ? LoadWith<DM, REL, Target, NestedWithArgument<WK>, NestedSelectArgument<WK>>[] : never;
|
|
163
202
|
/** The relation keys of `W` that were actually requested (not `false`/`undefined`). */
|
|
164
|
-
type LoadedRelations<DM, REL extends Record<keyof DM, object>, T extends keyof DM, W> = { [K in keyof W as K extends keyof REL[T] ? (W[K] extends false | undefined ? never : K) : never]: K extends keyof REL[T] ? LoadRelation<DM, REL, REL[T][K], W[K]> : never };
|
|
203
|
+
type LoadedRelations<DM, REL extends Record<keyof DM, object>, T extends keyof DM, W> = { [K in keyof W as K extends keyof REL[T] ? (W[K] extends false | undefined ? never : K) : never]: K extends keyof REL[T] ? LoadRelation<DM, REL, REL[T][K], W[K]> : never; };
|
|
165
204
|
/** The `_count` projection of `W`, if any. */
|
|
166
205
|
type LoadedCount<W> = W extends {
|
|
167
206
|
_count: infer C;
|
|
168
207
|
} ? {
|
|
169
|
-
_count: { [K in keyof C]: number };
|
|
208
|
+
_count: { [K in keyof C]: number; };
|
|
170
209
|
} : {};
|
|
171
210
|
/** System columns a `select` projection always retains, so cursors and by-id reuse keep working. */
|
|
172
211
|
type SelectAlwaysKeep<DM, T extends keyof DM> = ("_creationTime" | "_id") & keyof DM[T];
|
|
173
212
|
/**
|
|
174
|
-
* `DM[T]` narrowed to the columns named by a `select` tuple `S` (plus the system
|
|
175
|
-
* fields). `undefined` (the default — no `select`) keeps the full document.
|
|
176
|
-
*/
|
|
213
|
+
* `DM[T]` narrowed to the columns named by a `select` tuple `S` (plus the system
|
|
214
|
+
* fields). `undefined` (the default — no `select`) keeps the full document.
|
|
215
|
+
*/
|
|
177
216
|
type ProjectDoc<DM, T extends keyof DM, S> = S extends ReadonlyArray<infer K> ? (K extends keyof DM[T] ? Pick<DM[T], (K & keyof DM[T]) | SelectAlwaysKeep<DM, T>> : DM[T]) : DM[T];
|
|
178
217
|
/**
|
|
179
|
-
* `Doc
|
|
180
|
-
* when a `select` tuple `S` is supplied, to its projected columns. `S` defaults
|
|
181
|
-
* to `undefined` so the 4-argument form (the codegen-emitted callers) keeps the
|
|
182
|
-
* full document.
|
|
183
|
-
*/
|
|
218
|
+
* `Doc<T>` narrowed to exactly the relations requested in the with-arg `W` and,
|
|
219
|
+
* when a `select` tuple `S` is supplied, to its projected columns. `S` defaults
|
|
220
|
+
* to `undefined` so the 4-argument form (the codegen-emitted callers) keeps the
|
|
221
|
+
* full document.
|
|
222
|
+
*/
|
|
184
223
|
type LoadWith<DM, REL extends Record<keyof DM, object>, T extends keyof DM, W, S = undefined> = LoadedCount<W> & LoadedRelations<DM, REL, T, W> & ProjectDoc<DM, T, S>;
|
|
185
224
|
/** Reducer applied by an aggregate (`avg`/`count`/`max`/`min`/`sum`). */
|
|
186
225
|
type AggregateOp = "avg" | "count" | "max" | "min" | "sum";
|
|
187
226
|
/**
|
|
188
|
-
* Query-options shape shared by every aggregate reader. The RLS-aware ctx
|
|
189
|
-
* populates `baseWhere` so it composes here without a hard import.
|
|
190
|
-
* `restrictsCounts: true` flips `count()` into a thrown `COUNT_RLS_UNSUPPORTED`
|
|
191
|
-
* `LunoraError` rather than silently undercount.
|
|
192
|
-
*/
|
|
227
|
+
* Query-options shape shared by every aggregate reader. The RLS-aware ctx
|
|
228
|
+
* populates `baseWhere` so it composes here without a hard import.
|
|
229
|
+
* `restrictsCounts: true` flips `count()` into a thrown `COUNT_RLS_UNSUPPORTED`
|
|
230
|
+
* `LunoraError` rather than silently undercount.
|
|
231
|
+
*/
|
|
193
232
|
interface RestrictableQueryOptions<TDocument> {
|
|
194
233
|
baseWhere?: Where<TDocument>;
|
|
195
234
|
restrictsCounts?: boolean;
|
|
196
235
|
where?: Where<TDocument>;
|
|
197
236
|
}
|
|
198
237
|
/**
|
|
199
|
-
* Relation-aware twin of {@link RestrictableQueryOptions}. The `@lunora/do`
|
|
200
|
-
* pre-resolver now resolves relation predicates on the `count`/`aggregate`/
|
|
201
|
-
* `groupBy` paths too (semijoin), so the typed surface threads `REL` through
|
|
202
|
-
* `where`/`baseWhere` to match. `rank`/`rankPage` stay column-only — they use
|
|
203
|
-
* `where` solely to pin a partition and fail closed on a relation predicate.
|
|
204
|
-
*/
|
|
238
|
+
* Relation-aware twin of {@link RestrictableQueryOptions}. The `@lunora/do`
|
|
239
|
+
* pre-resolver now resolves relation predicates on the `count`/`aggregate`/
|
|
240
|
+
* `groupBy` paths too (semijoin), so the typed surface threads `REL` through
|
|
241
|
+
* `where`/`baseWhere` to match. `rank`/`rankPage` stay column-only — they use
|
|
242
|
+
* `where` solely to pin a partition and fail closed on a relation predicate.
|
|
243
|
+
*/
|
|
205
244
|
interface RestrictableQueryOptionsOf<DM, REL extends Record<keyof DM, object>, T extends keyof DM> {
|
|
206
245
|
baseWhere?: WhereOf<DM, REL, T>;
|
|
207
246
|
restrictsCounts?: boolean;
|
|
208
247
|
where?: WhereOf<DM, REL, T>;
|
|
209
248
|
}
|
|
210
|
-
/** Args for `ctx.db
|
|
249
|
+
/** Args for `ctx.db.<table>.aggregate({ op, field?, where? })`. */
|
|
211
250
|
interface TableAggregateOptions<TDocument> extends RestrictableQueryOptions<TDocument> {
|
|
212
251
|
field?: keyof TDocument & string;
|
|
213
252
|
op: AggregateOp;
|
|
@@ -217,7 +256,7 @@ interface TableAggregateOptionsOf<DM, REL extends Record<keyof DM, object>, T ex
|
|
|
217
256
|
field?: keyof DM[T] & string;
|
|
218
257
|
op: AggregateOp;
|
|
219
258
|
}
|
|
220
|
-
/** Args for `ctx.db
|
|
259
|
+
/** Args for `ctx.db.<table>.groupBy({ by, agg?, where? })`. */
|
|
221
260
|
interface TableGroupByOptions<TDocument> extends RestrictableQueryOptions<TDocument> {
|
|
222
261
|
agg?: {
|
|
223
262
|
field?: keyof TDocument & string;
|
|
@@ -238,7 +277,7 @@ interface GroupByEntry<TDocument> {
|
|
|
238
277
|
key: Partial<TDocument>;
|
|
239
278
|
value: null | number;
|
|
240
279
|
}
|
|
241
|
-
/** Args for `ctx.db
|
|
280
|
+
/** Args for `ctx.db.<table>.rank(name, args)`. `row` is either an id or a row doc. */
|
|
242
281
|
interface TableRankOptions<TDocument> extends RestrictableQueryOptions<TDocument> {
|
|
243
282
|
row: string | TDocument;
|
|
244
283
|
}
|
|
@@ -247,7 +286,7 @@ interface RankResult {
|
|
|
247
286
|
position: number;
|
|
248
287
|
total: number;
|
|
249
288
|
}
|
|
250
|
-
/** Args for `ctx.db
|
|
289
|
+
/** Args for `ctx.db.<table>.rankPage(name, args)`. */
|
|
251
290
|
interface TableRankPageOptions<TDocument> extends RestrictableQueryOptions<TDocument> {
|
|
252
291
|
cursor?: null | string;
|
|
253
292
|
take?: number;
|
|
@@ -259,44 +298,109 @@ interface RankPage<TDocument> {
|
|
|
259
298
|
page: TDocument[];
|
|
260
299
|
}
|
|
261
300
|
/**
|
|
262
|
-
* Builder passed to `.withSearchIndex(name, q => …)`. `.search(field, query)`
|
|
263
|
-
* runs the full-text match against the index's searchable field; `.eq(field,
|
|
264
|
-
* value)` narrows by a declared filter field. Field names are constrained to
|
|
265
|
-
* the table's columns.
|
|
266
|
-
*/
|
|
301
|
+
* Builder passed to `.withSearchIndex(name, q => …)`. `.search(field, query)`
|
|
302
|
+
* runs the full-text match against the index's searchable field; `.eq(field,
|
|
303
|
+
* value)` narrows by a declared filter field. Field names are constrained to
|
|
304
|
+
* the table's columns.
|
|
305
|
+
*/
|
|
267
306
|
interface SearchFilterBuilder<TDocument> {
|
|
268
307
|
eq: <F extends keyof TDocument & string>(field: F, value: TDocument[F]) => SearchFilterBuilder<TDocument>;
|
|
269
308
|
search: (field: keyof TDocument & string, query: string) => SearchFilterBuilder<TDocument>;
|
|
270
309
|
}
|
|
271
310
|
/**
|
|
272
|
-
* Chainable reader returned by `.withSearchIndex()` — rows come back ordered
|
|
273
|
-
* by relevance. `.paginate()` is intentionally absent (a relevance-ordered
|
|
274
|
-
* search can't keyset-paginate); cap the result set with `.take(n)`.
|
|
275
|
-
*/
|
|
311
|
+
* Chainable reader returned by `.withSearchIndex()` — rows come back ordered
|
|
312
|
+
* by relevance. `.paginate()` is intentionally absent (a relevance-ordered
|
|
313
|
+
* search can't keyset-paginate); cap the result set with `.take(n)`.
|
|
314
|
+
*/
|
|
276
315
|
interface SearchReader<TDocument> {
|
|
277
316
|
collect: () => Promise<TDocument[]>;
|
|
317
|
+
/**
|
|
318
|
+
* Like `.collect()`, but pairs each matched document with the relevance
|
|
319
|
+
* score the FTS engine already computed to produce the descending order —
|
|
320
|
+
* `.collect()` throws it away after sorting by it, this surfaces it
|
|
321
|
+
* instead. Ordered by `score` descending, same order as `.collect()`.
|
|
322
|
+
*/
|
|
323
|
+
collectWithScores: () => Promise<{
|
|
324
|
+
document: TDocument;
|
|
325
|
+
score: number;
|
|
326
|
+
}[]>;
|
|
278
327
|
first: () => Promise<TDocument | null>;
|
|
279
328
|
take: (limit: number) => Promise<TDocument[]>;
|
|
280
329
|
unique: () => Promise<TDocument | null>;
|
|
281
330
|
}
|
|
282
|
-
/**
|
|
283
|
-
interface
|
|
331
|
+
/** A latitude/longitude point (WGS84 decimal degrees) accepted by geo queries. */
|
|
332
|
+
interface GeoPointInput {
|
|
333
|
+
lat: number;
|
|
334
|
+
lng: number;
|
|
335
|
+
}
|
|
336
|
+
/** An axis-aligned latitude/longitude bounding box (`sw`/`ne` corners). */
|
|
337
|
+
interface GeoBoundingBox {
|
|
338
|
+
ne: GeoPointInput;
|
|
339
|
+
sw: GeoPointInput;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Builder passed to `.withGeoIndex(name, q => …)`. Call exactly one of
|
|
343
|
+
* `.near(point, radiusMeters)` (proximity, nearest-first) or `.within(box)`
|
|
344
|
+
* (bounding-box).
|
|
345
|
+
*/
|
|
346
|
+
interface GeoFilterBuilder {
|
|
347
|
+
near: (point: GeoPointInput, radiusMeters: number) => GeoFilterBuilder;
|
|
348
|
+
within: (box: GeoBoundingBox) => GeoFilterBuilder;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Chainable reader returned by `.withGeoIndex()`. `.near()` results come back
|
|
352
|
+
* ordered nearest-first; `.within()` results by row creation time. `.paginate()`
|
|
353
|
+
* is intentionally absent — cap the result set with `.take(n)`.
|
|
354
|
+
*/
|
|
355
|
+
interface GeoReader<TDocument> {
|
|
356
|
+
collect: () => Promise<TDocument[]>;
|
|
357
|
+
/**
|
|
358
|
+
* Like `.collect()`, but pairs each matched document with the distance
|
|
359
|
+
* (in meters) from `.near()`'s point that the read already computed to
|
|
360
|
+
* produce the nearest-first order. `.within()` box matches have no
|
|
361
|
+
* point-distance metric, so `distanceMeters` is `null` for those rows
|
|
362
|
+
* rather than a misleading `0`.
|
|
363
|
+
*/
|
|
364
|
+
collectWithScores: () => Promise<{
|
|
365
|
+
distanceMeters: null | number;
|
|
366
|
+
document: TDocument;
|
|
367
|
+
}[]>;
|
|
368
|
+
first: () => Promise<TDocument | null>;
|
|
369
|
+
take: (limit: number) => Promise<TDocument[]>;
|
|
370
|
+
unique: () => Promise<TDocument | null>;
|
|
371
|
+
}
|
|
372
|
+
/** Read-only typed table accessor exposed on `QueryCtx.db.<table>`. */
|
|
373
|
+
interface TableReaderFacade<DM, REL extends Record<keyof DM, object>, RANK extends Record<keyof DM, string>, SEARCH extends Record<keyof DM, string>, T extends keyof DM, GEO extends Record<keyof DM, string> = Record<keyof DM, never>> {
|
|
284
374
|
/**
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
375
|
+
* Reduce rows in this table to a scalar (`avg`/`max`/`min`/`sum` — `count`
|
|
376
|
+
* lives on its own method). Routes through a declared `aggregateIndex` when
|
|
377
|
+
* the planner can prove the request is answerable; otherwise scans.
|
|
378
|
+
*/
|
|
289
379
|
aggregate: (options: TableAggregateOptionsOf<DM, REL, T>) => Promise<null | number>;
|
|
290
380
|
/**
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
381
|
+
* Count rows. The planner routes `where` keys that match a declared
|
|
382
|
+
* `aggregateIndex.by` set to the indexed counter (no scan); otherwise
|
|
383
|
+
* falls back to a SCAN. Accepts either a bare `where` tree or the broader
|
|
384
|
+
* `RestrictableQueryOptions` shape; the latter is the seam the RLS layer
|
|
385
|
+
* uses to inject `baseWhere` and `restrictsCounts`.
|
|
386
|
+
*/
|
|
297
387
|
count: (where?: RestrictableQueryOptionsOf<DM, REL, T> | WhereOf<DM, REL, T>) => Promise<number>;
|
|
298
388
|
/** `true` when at least one row matches `where` (any row when omitted). RLS-filtered exactly like `findFirst`. */
|
|
299
389
|
exists: (where?: WhereOf<DM, REL, T>) => Promise<boolean>;
|
|
390
|
+
/**
|
|
391
|
+
* Describe how this table would run `findMany(args)` without running it: the SQL, its bound
|
|
392
|
+
* parameters, and the database's own plan, one line per step. Use it to check that a filter is
|
|
393
|
+
* served by an index (`SEARCH … USING INDEX by_room`) rather than a scan (`SCAN …`).
|
|
394
|
+
*
|
|
395
|
+
* Honors RLS and soft delete exactly like the read, so the plan is the one your caller would get.
|
|
396
|
+
* Explaining does not subscribe a live query to this table. A relation filter in `where` is resolved
|
|
397
|
+
* first with an ordinary read of the related table, which does subscribe to that table, since the ids
|
|
398
|
+
* it matched are part of `params`. Takes no `with` or `select`: neither changes the statement.
|
|
399
|
+
*
|
|
400
|
+
* `params` carries values your RLS policies injected and, for a relation filter, the ids it matched.
|
|
401
|
+
* Keep the result on the server rather than returning it to a client.
|
|
402
|
+
*/
|
|
403
|
+
explain: (args?: QueryArgsOf<DM, REL, T>) => Promise<QueryPlan>;
|
|
300
404
|
findFirst: <W extends WithArg<DM, REL, T> = {}, S extends ReadonlyArray<keyof DM[T] & string> | undefined = undefined>(args?: QueryArgsOf<DM, REL, T> & {
|
|
301
405
|
select?: S;
|
|
302
406
|
with?: W;
|
|
@@ -309,47 +413,67 @@ interface TableReaderFacade<DM, REL extends Record<keyof DM, object>, RANK exten
|
|
|
309
413
|
select?: S;
|
|
310
414
|
with?: W;
|
|
311
415
|
}) => Promise<QueryPage<LoadWith<DM, REL, T, W, S>>>;
|
|
416
|
+
/**
|
|
417
|
+
* The one row matching `where`, or `null` when none does — and a `NOT_UNIQUE`
|
|
418
|
+
* error when a SECOND one matches, so a broken uniqueness invariant surfaces at
|
|
419
|
+
* the read rather than as a wrong answer downstream. `findFirst` drops that
|
|
420
|
+
* assertion; this is the ORM spelling of the fluent reader's `.unique()`.
|
|
421
|
+
*
|
|
422
|
+
* `limit`/`cursor` are not accepted rather than silently ignored: paging a read
|
|
423
|
+
* that must match at most one row is the assertion being dropped again.
|
|
424
|
+
*/
|
|
425
|
+
findUnique: <W extends WithArg<DM, REL, T> = {}, S extends ReadonlyArray<keyof DM[T] & string> | undefined = undefined>(args?: Omit<QueryArgsOf<DM, REL, T>, "cursor" | "limit"> & {
|
|
426
|
+
select?: S;
|
|
427
|
+
with?: W;
|
|
428
|
+
}) => Promise<LoadWith<DM, REL, T, W, S> | null>;
|
|
312
429
|
get: (id: Id<string & T>) => Promise<DM[T] | null>;
|
|
313
430
|
/**
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
431
|
+
* Group rows by the named keys and apply `agg` per group (defaults to
|
|
432
|
+
* `count`). Answered from the counter table when an aggregate index's
|
|
433
|
+
* `by` matches `options.by` exactly; otherwise scans.
|
|
434
|
+
*/
|
|
318
435
|
groupBy: (options: TableGroupByOptionsOf<DM, REL, T>) => Promise<ReadonlyArray<GroupByEntry<DM[T]>>>;
|
|
319
436
|
/**
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
437
|
+
* Return the 1-based position of `options.row` within its partition
|
|
438
|
+
* under the declared rankIndex `indexName`, plus the partition's total
|
|
439
|
+
* row count. `null` when the row isn't in the index. Honors the same
|
|
440
|
+
* `baseWhere` / `restrictsCounts` RLS seam as `count()`.
|
|
441
|
+
*/
|
|
325
442
|
rank: (indexName: RANK[T], options: TableRankOptions<DM[T]>) => Promise<null | RankResult>;
|
|
326
443
|
/**
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
444
|
+
* Walk the rank companion in declared sort order — sorted pagination
|
|
445
|
+
* accelerator. `options.where` may pin the partition; `cursor`/`take`
|
|
446
|
+
* follow the Convex-style keyset shape.
|
|
447
|
+
*/
|
|
331
448
|
rankPage: (indexName: RANK[T], options?: TableRankPageOptions<DM[T]>) => Promise<RankPage<DM[T]>>;
|
|
332
449
|
/**
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
450
|
+
* Restrict the query to a declared `.geoIndex()` and run a proximity /
|
|
451
|
+
* bounding-box match. `indexName` is constrained to this table's geo indexes
|
|
452
|
+
* (`never` when it declares none). Returns a distance-ordered reader —
|
|
453
|
+
* finish with `.take(n)` / `.collect()`.
|
|
454
|
+
*/
|
|
455
|
+
withGeoIndex: (indexName: GEO[T], build: (q: GeoFilterBuilder) => GeoFilterBuilder) => GeoReader<DM[T]>;
|
|
456
|
+
/**
|
|
457
|
+
* Restrict the query to a declared `.searchIndex()` and run a full-text
|
|
458
|
+
* match. `indexName` is constrained to this table's search indexes
|
|
459
|
+
* (`never` when it declares none). Returns a relevance-ordered reader —
|
|
460
|
+
* finish with `.take(n)` / `.collect()`.
|
|
461
|
+
*/
|
|
338
462
|
withSearchIndex: (indexName: SEARCH[T], search: (q: SearchFilterBuilder<DM[T]>) => SearchFilterBuilder<DM[T]>) => SearchReader<DM[T]>;
|
|
339
463
|
}
|
|
340
|
-
/** Read-write typed table accessor exposed on `MutationCtx.db
|
|
341
|
-
interface TableWriterFacade<DM, IM extends Record<keyof DM, object>, REL extends Record<keyof DM, object>, RANK extends Record<keyof DM, string>, SEARCH extends Record<keyof DM, string>, T extends keyof DM> extends TableReaderFacade<DM, REL, RANK, SEARCH, T> {
|
|
464
|
+
/** Read-write typed table accessor exposed on `MutationCtx.db.<table>` / `ActionCtx.db.<table>`. */
|
|
465
|
+
interface TableWriterFacade<DM, IM extends Record<keyof DM, object>, REL extends Record<keyof DM, object>, RANK extends Record<keyof DM, string>, SEARCH extends Record<keyof DM, string>, T extends keyof DM, GEO extends Record<keyof DM, string> = Record<keyof DM, never>> extends TableReaderFacade<DM, REL, RANK, SEARCH, T, GEO> {
|
|
342
466
|
/**
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
467
|
+
* Delete a row by id. On a `.softDelete()` table this flips the marker column
|
|
468
|
+
* (and cascades as a soft delete) instead of removing the row; use
|
|
469
|
+
* {@link TableWriterFacade.hardDelete} to force physical removal.
|
|
470
|
+
*/
|
|
347
471
|
delete: (id: Id<string & T>) => Promise<void>;
|
|
348
472
|
/**
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
473
|
+
* Delete many rows in this table. Pass an array of ids (requested count is
|
|
474
|
+
* returned; unknown ids are no-ops) or `{ where }` to delete matching rows
|
|
475
|
+
* (actual removed count is returned). Atomic within a mutation.
|
|
476
|
+
*/
|
|
353
477
|
deleteMany: {
|
|
354
478
|
(ids: ReadonlyArray<Id<string & T>>, options?: {
|
|
355
479
|
limit?: number;
|
|
@@ -366,23 +490,23 @@ interface TableWriterFacade<DM, IM extends Record<keyof DM, object>, REL extends
|
|
|
366
490
|
/** Physically remove a row (and physically cascade `onDelete`), bypassing `.softDelete()`. Same as `delete()` on a non-soft table. */
|
|
367
491
|
hardDelete: (id: Id<string & T>) => Promise<void>;
|
|
368
492
|
/**
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
493
|
+
* Insert a document, returning its minted id. With `{ skipDuplicates: true }`
|
|
494
|
+
* a UNIQUE-constraint breach resolves to `null` (the row already exists)
|
|
495
|
+
* instead of throwing — the return type widens to `Id | null` on that overload.
|
|
496
|
+
*/
|
|
373
497
|
insert: {
|
|
374
|
-
(values: IM
|
|
498
|
+
(values: InsertValues<DM, IM, REL, T>, options: {
|
|
375
499
|
skipDuplicates: true;
|
|
376
500
|
}): Promise<Id<string & T> | null>;
|
|
377
|
-
(values: IM
|
|
501
|
+
(values: InsertValues<DM, IM, REL, T>, options?: {
|
|
378
502
|
skipDuplicates?: boolean;
|
|
379
503
|
}): Promise<Id<string & T>>;
|
|
380
504
|
};
|
|
381
505
|
/**
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
506
|
+
* Insert many documents into this table in one call, returning the minted ids
|
|
507
|
+
* in input order. With `{ skipDuplicates: true }`, UNIQUE breaches resolve to
|
|
508
|
+
* `null` for that row instead of failing the batch. Atomic within a mutation.
|
|
509
|
+
*/
|
|
386
510
|
insertMany: {
|
|
387
511
|
(values: ReadonlyArray<IM[T]>, options: {
|
|
388
512
|
limit?: number;
|
|
@@ -393,16 +517,16 @@ interface TableWriterFacade<DM, IM extends Record<keyof DM, object>, REL extends
|
|
|
393
517
|
skipDuplicates?: boolean;
|
|
394
518
|
}): Promise<Id<string & T>[]>;
|
|
395
519
|
};
|
|
396
|
-
patch: (id: Id<string & T>, values:
|
|
520
|
+
patch: (id: Id<string & T>, values: PatchValues<IM[T]> & RelationWrites<DM, REL, T>) => Promise<void>;
|
|
397
521
|
/**
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
522
|
+
* Patch many rows in this table. Pass an array of `{ id, values }` or
|
|
523
|
+
* `{ where, values }` to patch matching rows with the same values. Returns
|
|
524
|
+
* the actual patched count. Atomic within a mutation.
|
|
525
|
+
*/
|
|
402
526
|
patchMany: {
|
|
403
527
|
(patches: ReadonlyArray<{
|
|
404
528
|
id: Id<string & T>;
|
|
405
|
-
values:
|
|
529
|
+
values: PatchValues<IM[T]>;
|
|
406
530
|
}>, options?: {
|
|
407
531
|
limit?: number;
|
|
408
532
|
}): Promise<{
|
|
@@ -410,7 +534,7 @@ interface TableWriterFacade<DM, IM extends Record<keyof DM, object>, REL extends
|
|
|
410
534
|
}>;
|
|
411
535
|
(args: {
|
|
412
536
|
limit?: number;
|
|
413
|
-
values:
|
|
537
|
+
values: PatchValues<IM[T]>;
|
|
414
538
|
where: Partial<DM[T]>;
|
|
415
539
|
}): Promise<{
|
|
416
540
|
patched: number;
|
|
@@ -420,15 +544,15 @@ interface TableWriterFacade<DM, IM extends Record<keyof DM, object>, REL extends
|
|
|
420
544
|
/** Un-soft-delete a row by id: clears the `.softDelete()` marker so list reads see it again. Throws on a non-soft table. */
|
|
421
545
|
restore: (id: Id<string & T>) => Promise<void>;
|
|
422
546
|
/**
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
547
|
+
* Insert when no existing row matches `target`, otherwise patch the match with
|
|
548
|
+
* `update` (defaulting to `create`). `target` names a `.unique()` column (or a
|
|
549
|
+
* tuple) used to look it up. Returns the row id and whether it was `created`.
|
|
550
|
+
* Composes `findFirst` + `insert`/`patch`, so RLS gates each step.
|
|
551
|
+
*/
|
|
428
552
|
upsert: (args: {
|
|
429
|
-
create: IM
|
|
553
|
+
create: InsertValues<DM, IM, REL, T>;
|
|
430
554
|
target: UpsertTargetOf<DM, T>;
|
|
431
|
-
update?:
|
|
555
|
+
update?: PatchValues<IM[T]> & RelationWrites<DM, REL, T>;
|
|
432
556
|
}) => Promise<{
|
|
433
557
|
created: boolean;
|
|
434
558
|
id: Id<string & T>;
|
|
@@ -436,8 +560,8 @@ interface TableWriterFacade<DM, IM extends Record<keyof DM, object>, REL extends
|
|
|
436
560
|
/** Sequential {@link TableWriterFacade.upsert} over many rows sharing one `target`; one result per input row, in order. */
|
|
437
561
|
upsertMany: (args: {
|
|
438
562
|
rows: ReadonlyArray<{
|
|
439
|
-
create: IM
|
|
440
|
-
update?:
|
|
563
|
+
create: InsertValues<DM, IM, REL, T>;
|
|
564
|
+
update?: PatchValues<IM[T]> & RelationWrites<DM, REL, T>;
|
|
441
565
|
}>;
|
|
442
566
|
target: UpsertTargetOf<DM, T>;
|
|
443
567
|
}) => Promise<{
|
|
@@ -447,8 +571,106 @@ interface TableWriterFacade<DM, IM extends Record<keyof DM, object>, REL extends
|
|
|
447
571
|
}
|
|
448
572
|
/** Conflict target for `upsert`/`upsertMany`: one column of table `T`, or a tuple of them. */
|
|
449
573
|
type UpsertTargetOf<DM, T extends keyof DM> = ReadonlyArray<keyof DM[T] & string> | (keyof DM[T] & string);
|
|
450
|
-
/**
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
574
|
+
/**
|
|
575
|
+
* An atomic update for a number column, applied to the value the row holds when the
|
|
576
|
+
* write runs: `{ increment: 1 }`, `{ decrement: 1 }`, `{ multiply: 2 }`, `{ divide: 2 }`.
|
|
577
|
+
* The column must already hold a finite number; a result that is not finite is rejected.
|
|
578
|
+
*/
|
|
579
|
+
type NumberFieldOperator = {
|
|
580
|
+
decrement: number;
|
|
581
|
+
divide?: never;
|
|
582
|
+
increment?: never;
|
|
583
|
+
multiply?: never;
|
|
584
|
+
} | {
|
|
585
|
+
decrement?: never;
|
|
586
|
+
divide: number;
|
|
587
|
+
increment?: never;
|
|
588
|
+
multiply?: never;
|
|
589
|
+
} | {
|
|
590
|
+
decrement?: never;
|
|
591
|
+
divide?: never;
|
|
592
|
+
increment: number;
|
|
593
|
+
multiply?: never;
|
|
594
|
+
} | {
|
|
595
|
+
decrement?: never;
|
|
596
|
+
divide?: never;
|
|
597
|
+
increment?: never;
|
|
598
|
+
multiply: number;
|
|
599
|
+
};
|
|
600
|
+
/** An atomic update for a boolean column: `{ toggle: true }` flips the stored value. */
|
|
601
|
+
interface BooleanFieldOperator {
|
|
602
|
+
toggle: true;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* The values a `patch` accepts: any column's own type, and for a number or boolean
|
|
606
|
+
* column also an atomic operator ({@link NumberFieldOperator}, {@link BooleanFieldOperator}).
|
|
607
|
+
*/
|
|
608
|
+
type PatchValues<Row> = PathPatch<Row> & { [K in keyof Row]?: Row[K] | OperatorFor<NonNullable<Row[K]>>; };
|
|
609
|
+
/**
|
|
610
|
+
* The operator a column of type `T` takes: arithmetic when `T` is exactly `number` (so a literal union
|
|
611
|
+
* such as `1 | 2` is out — the result could leave the set — and so is `number | string`, which the
|
|
612
|
+
* store does not treat as a number column), `{ toggle: true }` when it is exactly `boolean`.
|
|
613
|
+
*/
|
|
614
|
+
type OperatorFor<T> = [T] extends [number] ? [number] extends [T] ? NumberFieldOperator : never : [T] extends [boolean] ? [boolean] extends [T] ? BooleanFieldOperator : never : never;
|
|
615
|
+
/** What `explain` returns: the SQL a read compiles to, its bound parameters, and the database's plan for it. */
|
|
616
|
+
interface QueryPlan {
|
|
617
|
+
/** The bound parameters, in order. */
|
|
618
|
+
params: unknown[];
|
|
619
|
+
/** One line per plan step, as the database reports it. */
|
|
620
|
+
plan: string[];
|
|
621
|
+
/** The statement the read compiles to. */
|
|
622
|
+
sql: string;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Nested write for a to-one relation: point the row at `Target` (`connect`), or clear the link (`disconnect`, which
|
|
626
|
+
* needs a foreign key that can be empty). One or the other, not both.
|
|
627
|
+
*/
|
|
628
|
+
type ToOneRelationWrite<TargetId> = {
|
|
629
|
+
connect: TargetId;
|
|
630
|
+
disconnect?: never;
|
|
631
|
+
} | {
|
|
632
|
+
connect?: never;
|
|
633
|
+
disconnect: true;
|
|
634
|
+
};
|
|
635
|
+
/**
|
|
636
|
+
* Nested write for a to-many relation: point `Target` rows at this one (`connect`), release them (`disconnect`, which
|
|
637
|
+
* needs a foreign key that can be empty), or replace the whole list (`set`: rows not named are released). `set`
|
|
638
|
+
* cannot be combined with the other two.
|
|
639
|
+
*/
|
|
640
|
+
type ToManyRelationWrite<TargetId> = {
|
|
641
|
+
connect?: never;
|
|
642
|
+
disconnect?: never;
|
|
643
|
+
set: ReadonlyArray<TargetId>;
|
|
644
|
+
} | {
|
|
645
|
+
connect?: ReadonlyArray<TargetId>;
|
|
646
|
+
disconnect?: ReadonlyArray<TargetId>;
|
|
647
|
+
set?: never;
|
|
648
|
+
};
|
|
649
|
+
/**
|
|
650
|
+
* Nested relation writes accepted by `insert`, `patch` and `upsert`, keyed by relation name:
|
|
651
|
+
* `{ author: { connect: userId } }`, `{ posts: { set: [a, b] } }`. A table with no relations contributes none
|
|
652
|
+
* (`unknown`, the identity for `&`, never `{}`: an empty object type would switch off the weak-type check).
|
|
653
|
+
*/
|
|
654
|
+
type RelationWrites<DM, REL extends Record<keyof DM, object>, T extends keyof DM> = [keyof REL[T]] extends [never] ? unknown : { [K in keyof REL[T]]?: REL[T][K] extends {
|
|
655
|
+
__relationKind: "one";
|
|
656
|
+
__target: infer Target extends keyof DM;
|
|
657
|
+
} ? ToOneRelationWrite<Id<string & Target>> : REL[T][K] extends {
|
|
658
|
+
__relationKind: "many";
|
|
659
|
+
__target: infer Target extends keyof DM;
|
|
660
|
+
} ? ToManyRelationWrite<Id<string & Target>> : never; };
|
|
661
|
+
/** The foreign-key columns of a table's to-one relations — the keys a nested `connect` supplies. */
|
|
662
|
+
type OneRelationFields<RelT> = { [K in keyof RelT]: RelT[K] extends {
|
|
663
|
+
__field: infer Field extends string;
|
|
664
|
+
__relationKind: "one";
|
|
665
|
+
} ? Field : never; }[keyof RelT];
|
|
666
|
+
/**
|
|
667
|
+
* What `insert` accepts: the row as it is, or the row without its to-one foreign keys plus the nested writes that
|
|
668
|
+
* supply them. Leaving a key out with no `connect` to supply it is not caught by the type — the store's own
|
|
669
|
+
* validation rejects the missing field.
|
|
670
|
+
*/
|
|
671
|
+
type InsertValues<DM, IM extends Record<keyof DM, object>, REL extends Record<keyof DM, object>, T extends keyof DM> = IM[T] | (Omit<IM[T], OneRelationFields<REL[T]>> & RelationWrites<DM, REL, T>);
|
|
672
|
+
/** Per-table read facade — `ctx.db.<table>` on a `QueryCtx`. */
|
|
673
|
+
type DatabaseReaderFacade<DM, REL extends Record<keyof DM, object>, RANK extends Record<keyof DM, string>, SEARCH extends Record<keyof DM, string>, GEO extends Record<keyof DM, string> = Record<keyof DM, never>> = { readonly [T in keyof DM]: TableReaderFacade<DM, REL, RANK, SEARCH, T, GEO>; };
|
|
674
|
+
/** Per-table read-write facade — `ctx.db.<table>` on a `MutationCtx` / `ActionCtx`. */
|
|
675
|
+
type DatabaseWriterFacade<DM, IM extends Record<keyof DM, object>, REL extends Record<keyof DM, object>, RANK extends Record<keyof DM, string>, SEARCH extends Record<keyof DM, string>, GEO extends Record<keyof DM, string> = Record<keyof DM, never>> = { readonly [T in keyof DM]: TableWriterFacade<DM, IM, REL, RANK, SEARCH, T, GEO>; };
|
|
676
|
+
export { AggregateOp, DatabaseReaderFacade, DatabaseWriterFacade, GeoBoundingBox, GeoFilterBuilder, GeoPointInput, GeoReader, GroupByEntry, Id, InsertValues, LoadWith, ManyRelationWhere, OneRelationWhere, OrderBy, PatchValues, QueryArgs, QueryArgsOf, QueryPage, QueryPlan, RankPage, RankResult, RelationWrites, RestrictableQueryOptions, RestrictableQueryOptionsOf, SearchFilterBuilder, SearchReader, TableAggregateOptions, TableAggregateOptionsOf, TableGroupByOptions, TableGroupByOptionsOf, TableRankOptions, TableRankPageOptions, TableReaderFacade, TableWriterFacade, ToManyRelationWrite, ToOneRelationWrite, UpsertTargetOf, Where, WhereOf, WhereOperators, WithArg };
|