kitcn 0.0.1 → 0.12.1
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/bin/intent.js +3 -0
- package/dist/aggregate/index.d.ts +388 -0
- package/dist/aggregate/index.js +37 -0
- package/dist/api-entry-BckXqaLb.js +66 -0
- package/dist/auth/client/index.d.ts +37 -0
- package/dist/auth/client/index.js +217 -0
- package/dist/auth/config/index.d.ts +45 -0
- package/dist/auth/config/index.js +24 -0
- package/dist/auth/generated/index.d.ts +2 -0
- package/dist/auth/generated/index.js +3 -0
- package/dist/auth/http/index.d.ts +64 -0
- package/dist/auth/http/index.js +461 -0
- package/dist/auth/index.d.ts +221 -0
- package/dist/auth/index.js +1398 -0
- package/dist/auth/nextjs/index.d.ts +50 -0
- package/dist/auth/nextjs/index.js +81 -0
- package/dist/auth-store-Cljlmdmi.js +197 -0
- package/dist/builder-CBdG5W6A.js +1974 -0
- package/dist/caller-factory-cTXNvYdz.js +216 -0
- package/dist/cli.mjs +13264 -0
- package/dist/codegen-lF80HSWu.mjs +3416 -0
- package/dist/context-utils-HPC5nXzx.d.ts +17 -0
- package/dist/create-schema-odyF4kCy.js +156 -0
- package/dist/create-schema-orm-DOyiNDCx.js +246 -0
- package/dist/crpc/index.d.ts +105 -0
- package/dist/crpc/index.js +169 -0
- package/dist/customFunctions-C0voKmtx.js +144 -0
- package/dist/error-BZEnI7Sq.js +41 -0
- package/dist/generated-contract-disabled-Cih4eITO.js +50 -0
- package/dist/generated-contract-disabled-D-sOFy92.d.ts +354 -0
- package/dist/http-types-DqJubRPJ.d.ts +292 -0
- package/dist/meta-utils-0Pu0Nrap.js +117 -0
- package/dist/middleware-BUybuv9n.d.ts +34 -0
- package/dist/middleware-C2qTZ3V7.js +84 -0
- package/dist/orm/index.d.ts +17 -0
- package/dist/orm/index.js +10713 -0
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.js +3 -0
- package/dist/procedure-caller-DtxLmGwA.d.ts +1467 -0
- package/dist/procedure-caller-MWcxhQDv.js +349 -0
- package/dist/query-context-B8o6-8kC.js +1518 -0
- package/dist/query-context-CFZqIvD7.d.ts +42 -0
- package/dist/query-options-Dw7cOyXl.js +121 -0
- package/dist/ratelimit/index.d.ts +269 -0
- package/dist/ratelimit/index.js +856 -0
- package/dist/ratelimit/react/index.d.ts +76 -0
- package/dist/ratelimit/react/index.js +183 -0
- package/dist/react/index.d.ts +1284 -0
- package/dist/react/index.js +2526 -0
- package/dist/rsc/index.d.ts +276 -0
- package/dist/rsc/index.js +233 -0
- package/dist/runtime-CtvJPkur.js +2453 -0
- package/dist/server/index.d.ts +5 -0
- package/dist/server/index.js +6 -0
- package/dist/solid/index.d.ts +1221 -0
- package/dist/solid/index.js +2940 -0
- package/dist/transformer-DtDhR3Lc.js +194 -0
- package/dist/types-BTb_4BaU.d.ts +42 -0
- package/dist/types-BiJE7qxR.d.ts +4 -0
- package/dist/types-DEJpkIhw.d.ts +88 -0
- package/dist/types-HhO_R6pd.d.ts +213 -0
- package/dist/validators-B7oIJCAp.js +279 -0
- package/dist/validators-vzRKjBJC.d.ts +88 -0
- package/dist/watcher.mjs +96 -0
- package/dist/where-clause-compiler-DdjN63Io.d.ts +4756 -0
- package/package.json +107 -34
- package/skills/convex/SKILL.md +486 -0
- package/skills/convex/references/features/aggregates.md +353 -0
- package/skills/convex/references/features/auth-admin.md +446 -0
- package/skills/convex/references/features/auth-organizations.md +1141 -0
- package/skills/convex/references/features/auth-polar.md +579 -0
- package/skills/convex/references/features/auth.md +470 -0
- package/skills/convex/references/features/create-plugins.md +153 -0
- package/skills/convex/references/features/http.md +676 -0
- package/skills/convex/references/features/migrations.md +162 -0
- package/skills/convex/references/features/orm.md +1166 -0
- package/skills/convex/references/features/react.md +657 -0
- package/skills/convex/references/features/scheduling.md +267 -0
- package/skills/convex/references/features/testing.md +209 -0
- package/skills/convex/references/setup/auth.md +501 -0
- package/skills/convex/references/setup/biome.md +190 -0
- package/skills/convex/references/setup/doc-guidelines.md +145 -0
- package/skills/convex/references/setup/index.md +761 -0
- package/skills/convex/references/setup/next.md +116 -0
- package/skills/convex/references/setup/react.md +175 -0
- package/skills/convex/references/setup/server.md +473 -0
- package/skills/convex/references/setup/start.md +67 -0
- package/LICENSE +0 -21
- package/README.md +0 -0
- package/dist/index.d.mts +0 -5
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs +0 -6
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,1518 @@
|
|
|
1
|
+
import { compareValues, convexToJson, jsonToConvex } from "convex/values";
|
|
2
|
+
|
|
3
|
+
//#region src/orm/filter-expression.ts
|
|
4
|
+
/**
|
|
5
|
+
* Unique symbol for FilterExpression brand
|
|
6
|
+
* Prevents structural typing - only expressions created by factory functions are valid
|
|
7
|
+
*/
|
|
8
|
+
const FilterExpressionBrand = Symbol("FilterExpression");
|
|
9
|
+
/**
|
|
10
|
+
* Create a field reference
|
|
11
|
+
* Used internally by operator functions
|
|
12
|
+
*/
|
|
13
|
+
function fieldRef(fieldName) {
|
|
14
|
+
return {
|
|
15
|
+
__brand: "FieldReference",
|
|
16
|
+
fieldName
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Type guard for FieldReference
|
|
21
|
+
*/
|
|
22
|
+
function isFieldReference(value) {
|
|
23
|
+
return value && typeof value === "object" && value.__brand === "FieldReference";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create a column wrapper
|
|
27
|
+
* Used internally by query builder's _createColumnProxies
|
|
28
|
+
*/
|
|
29
|
+
function column(builder, columnName) {
|
|
30
|
+
return {
|
|
31
|
+
builder,
|
|
32
|
+
columnName
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function resolveColumn(col) {
|
|
36
|
+
if (col && typeof col === "object" && "columnName" in col) return col;
|
|
37
|
+
if (isFieldReference(col)) return {
|
|
38
|
+
builder: col,
|
|
39
|
+
columnName: col.fieldName
|
|
40
|
+
};
|
|
41
|
+
const builder = col;
|
|
42
|
+
const columnName = builder?.config?.name;
|
|
43
|
+
if (!columnName) throw new Error("Column builder is missing a column name");
|
|
44
|
+
return column(builder, columnName);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Internal binary expression implementation
|
|
48
|
+
* Private class - only accessible via factory functions
|
|
49
|
+
*/
|
|
50
|
+
var BinaryExpressionImpl = class {
|
|
51
|
+
[FilterExpressionBrand] = true;
|
|
52
|
+
type = "binary";
|
|
53
|
+
constructor(operator, operands) {
|
|
54
|
+
this.operator = operator;
|
|
55
|
+
this.operands = operands;
|
|
56
|
+
}
|
|
57
|
+
accept(visitor) {
|
|
58
|
+
return visitor.visitBinary(this);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Internal logical expression implementation
|
|
63
|
+
* Private class - only accessible via factory functions
|
|
64
|
+
*/
|
|
65
|
+
var LogicalExpressionImpl = class {
|
|
66
|
+
[FilterExpressionBrand] = true;
|
|
67
|
+
type = "logical";
|
|
68
|
+
constructor(operator, operands) {
|
|
69
|
+
this.operator = operator;
|
|
70
|
+
this.operands = operands;
|
|
71
|
+
}
|
|
72
|
+
accept(visitor) {
|
|
73
|
+
return visitor.visitLogical(this);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Internal unary expression implementation
|
|
78
|
+
* Private class - only accessible via factory functions
|
|
79
|
+
*/
|
|
80
|
+
var UnaryExpressionImpl = class {
|
|
81
|
+
[FilterExpressionBrand] = true;
|
|
82
|
+
type = "unary";
|
|
83
|
+
constructor(operator, operands) {
|
|
84
|
+
this.operator = operator;
|
|
85
|
+
this.operands = operands;
|
|
86
|
+
}
|
|
87
|
+
accept(visitor) {
|
|
88
|
+
return visitor.visitUnary(this);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Equality operator: field == value
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* const filter = eq(cols.name, 'Alice');
|
|
96
|
+
*/
|
|
97
|
+
function eq(col, value) {
|
|
98
|
+
return new BinaryExpressionImpl("eq", [fieldRef(resolveColumn(col).columnName), value]);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Not equal operator: field != value
|
|
102
|
+
*/
|
|
103
|
+
function ne(col, value) {
|
|
104
|
+
return new BinaryExpressionImpl("ne", [fieldRef(resolveColumn(col).columnName), value]);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Greater than operator: field > value
|
|
108
|
+
*/
|
|
109
|
+
function gt(col, value) {
|
|
110
|
+
return new BinaryExpressionImpl("gt", [fieldRef(resolveColumn(col).columnName), value]);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Greater than or equal operator: field >= value
|
|
114
|
+
*/
|
|
115
|
+
function gte(col, value) {
|
|
116
|
+
return new BinaryExpressionImpl("gte", [fieldRef(resolveColumn(col).columnName), value]);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Less than operator: field < value
|
|
120
|
+
*/
|
|
121
|
+
function lt(col, value) {
|
|
122
|
+
return new BinaryExpressionImpl("lt", [fieldRef(resolveColumn(col).columnName), value]);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Less than or equal operator: field <= value
|
|
126
|
+
*/
|
|
127
|
+
function lte(col, value) {
|
|
128
|
+
return new BinaryExpressionImpl("lte", [fieldRef(resolveColumn(col).columnName), value]);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Between operator: field BETWEEN min AND max (inclusive)
|
|
132
|
+
*
|
|
133
|
+
* Sugar for and(gte(field, min), lte(field, max)).
|
|
134
|
+
*/
|
|
135
|
+
function between(col, min, max) {
|
|
136
|
+
return and(gte(col, min), lte(col, max));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Not between operator: field < min OR field > max
|
|
140
|
+
*
|
|
141
|
+
* Sugar for or(lt(field, min), gt(field, max)).
|
|
142
|
+
*/
|
|
143
|
+
function notBetween(col, min, max) {
|
|
144
|
+
return or(lt(col, min), gt(col, max));
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* LIKE operator: SQL-style pattern matching with % wildcards
|
|
148
|
+
* Note: Implemented as post-filter (Convex has no native LIKE)
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* const users = await db.query.users.findMany({
|
|
152
|
+
* where: like(users.name, '%alice%'),
|
|
153
|
+
* });
|
|
154
|
+
*/
|
|
155
|
+
function like(col, pattern) {
|
|
156
|
+
return new BinaryExpressionImpl("like", [fieldRef(resolveColumn(col).columnName), pattern]);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* ILIKE operator: Case-insensitive LIKE
|
|
160
|
+
* Note: Implemented as post-filter (Convex has no native LIKE)
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* const users = await db.query.users.findMany({
|
|
164
|
+
* where: ilike(users.name, '%ALICE%'),
|
|
165
|
+
* });
|
|
166
|
+
*/
|
|
167
|
+
function ilike(col, pattern) {
|
|
168
|
+
return new BinaryExpressionImpl("ilike", [fieldRef(resolveColumn(col).columnName), pattern]);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* NOT LIKE operator: Negated LIKE pattern
|
|
172
|
+
*/
|
|
173
|
+
function notLike(col, pattern) {
|
|
174
|
+
return new BinaryExpressionImpl("notLike", [fieldRef(resolveColumn(col).columnName), pattern]);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* NOT ILIKE operator: Negated case-insensitive LIKE
|
|
178
|
+
*/
|
|
179
|
+
function notIlike(col, pattern) {
|
|
180
|
+
return new BinaryExpressionImpl("notIlike", [fieldRef(resolveColumn(col).columnName), pattern]);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* startsWith operator: Check if string starts with prefix
|
|
184
|
+
* Optimized for prefix matching
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* const users = await db.query.users.findMany({
|
|
188
|
+
* where: startsWith(users.email, 'admin@'),
|
|
189
|
+
* });
|
|
190
|
+
*/
|
|
191
|
+
function startsWith(col, prefix) {
|
|
192
|
+
return new BinaryExpressionImpl("startsWith", [fieldRef(resolveColumn(col).columnName), prefix]);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* endsWith operator: Check if string ends with suffix
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* const users = await db.query.users.findMany({
|
|
199
|
+
* where: endsWith(users.email, '@example.com'),
|
|
200
|
+
* });
|
|
201
|
+
*/
|
|
202
|
+
function endsWith(col, suffix) {
|
|
203
|
+
return new BinaryExpressionImpl("endsWith", [fieldRef(resolveColumn(col).columnName), suffix]);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* contains operator: Check if string contains substring
|
|
207
|
+
* Can use search index for optimization when available
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* const posts = await db.query.posts.findMany({
|
|
211
|
+
* where: contains(posts.title, 'javascript'),
|
|
212
|
+
* });
|
|
213
|
+
*/
|
|
214
|
+
function contains(col, substring) {
|
|
215
|
+
return new BinaryExpressionImpl("contains", [fieldRef(resolveColumn(col).columnName), substring]);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Logical AND: all expressions must be true
|
|
219
|
+
* Filters out undefined expressions (following Drizzle pattern)
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* const filter = and(
|
|
223
|
+
* eq(fieldRef('age'), 25),
|
|
224
|
+
* eq(fieldRef('name'), 'Alice')
|
|
225
|
+
* );
|
|
226
|
+
*/
|
|
227
|
+
function and(...expressions) {
|
|
228
|
+
const defined = expressions.filter((expr) => expr !== void 0);
|
|
229
|
+
if (defined.length === 0) return;
|
|
230
|
+
if (defined.length === 1) return defined[0];
|
|
231
|
+
return new LogicalExpressionImpl("and", defined);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Logical OR: at least one expression must be true
|
|
235
|
+
* Filters out undefined expressions (following Drizzle pattern)
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* const filter = or(
|
|
239
|
+
* eq(fieldRef('status'), 'active'),
|
|
240
|
+
* eq(fieldRef('status'), 'pending')
|
|
241
|
+
* );
|
|
242
|
+
*/
|
|
243
|
+
function or(...expressions) {
|
|
244
|
+
const defined = expressions.filter((expr) => expr !== void 0);
|
|
245
|
+
if (defined.length === 0) return;
|
|
246
|
+
if (defined.length === 1) return defined[0];
|
|
247
|
+
return new LogicalExpressionImpl("or", defined);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Logical NOT: negates expression
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* const filter = not(eq(fieldRef('isDeleted'), true));
|
|
254
|
+
*/
|
|
255
|
+
function not(expression) {
|
|
256
|
+
return new UnaryExpressionImpl("not", [expression]);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Array membership operator: field IN array
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* const filter = inArray(cols.status, ['active', 'pending']);
|
|
263
|
+
*/
|
|
264
|
+
function inArray(col, values) {
|
|
265
|
+
return new BinaryExpressionImpl("inArray", [fieldRef(resolveColumn(col).columnName), values]);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Array exclusion operator: field NOT IN array
|
|
269
|
+
* Validates array is non-empty at construction time
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* const filter = notInArray(cols.role, ['admin', 'moderator']);
|
|
273
|
+
*/
|
|
274
|
+
function notInArray(col, values) {
|
|
275
|
+
if (!Array.isArray(values) || values.length === 0) throw new Error("notInArray requires a non-empty array of values");
|
|
276
|
+
return new BinaryExpressionImpl("notInArray", [fieldRef(resolveColumn(col).columnName), values]);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Array contains operator: field @> array
|
|
280
|
+
*/
|
|
281
|
+
function arrayContains(col, values) {
|
|
282
|
+
return new BinaryExpressionImpl("arrayContains", [fieldRef(resolveColumn(col).columnName), values]);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Array contained operator: field <@ array
|
|
286
|
+
*/
|
|
287
|
+
function arrayContained(col, values) {
|
|
288
|
+
return new BinaryExpressionImpl("arrayContained", [fieldRef(resolveColumn(col).columnName), values]);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Array overlaps operator: field && array
|
|
292
|
+
*/
|
|
293
|
+
function arrayOverlaps(col, values) {
|
|
294
|
+
return new BinaryExpressionImpl("arrayOverlaps", [fieldRef(resolveColumn(col).columnName), values]);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Null check operator: field IS NULL
|
|
298
|
+
* Type validation: Only works with nullable fields
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* const filter = isNull(cols.deletedAt);
|
|
302
|
+
*/
|
|
303
|
+
function isNull(col) {
|
|
304
|
+
return new UnaryExpressionImpl("isNull", [fieldRef(resolveColumn(col).columnName)]);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Not null check operator: field IS NOT NULL
|
|
308
|
+
* Type validation: Only works with nullable fields
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* const filter = isNotNull(cols.deletedAt);
|
|
312
|
+
*/
|
|
313
|
+
function isNotNull(col) {
|
|
314
|
+
return new UnaryExpressionImpl("isNotNull", [fieldRef(resolveColumn(col).columnName)]);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
//#endregion
|
|
318
|
+
//#region src/orm/unset-token.ts
|
|
319
|
+
const unsetToken = Symbol.for("kitcn/orm/unsetToken");
|
|
320
|
+
function isUnsetToken(value) {
|
|
321
|
+
return value === unsetToken;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region src/orm/stream.ts
|
|
326
|
+
function makeExclusive(boundType) {
|
|
327
|
+
if (boundType === "gt" || boundType === "gte") return "gt";
|
|
328
|
+
return "lt";
|
|
329
|
+
}
|
|
330
|
+
/** Split a range query between two index keys into a series of range queries
|
|
331
|
+
* that should be executed in sequence. This is necessary because Convex only
|
|
332
|
+
* supports range queries of the form
|
|
333
|
+
* q.eq("f1", v).eq("f2", v).lt("f3", v).gt("f3", v).
|
|
334
|
+
* i.e. all fields must be equal except for the last field, which can have
|
|
335
|
+
* two inequalities.
|
|
336
|
+
*
|
|
337
|
+
* For example, the range from >[1, 2, 3] to <=[1, 3, 2] would be split into
|
|
338
|
+
* the following queries:
|
|
339
|
+
* 1. q.eq("f1", 1).eq("f2", 2).gt("f3", 3)
|
|
340
|
+
* 2. q.eq("f1", 1).gt("f2", 2).lt("f2", 3)
|
|
341
|
+
* 3. q.eq("f1", 1).eq("f2", 3).lte("f3", 2)
|
|
342
|
+
*/
|
|
343
|
+
function splitRange(indexFields, order, startBound, endBound, startBoundType, endBoundType) {
|
|
344
|
+
const commonPrefix = [];
|
|
345
|
+
let workingIndexFields = indexFields.slice();
|
|
346
|
+
let workingStartBound = startBound.slice();
|
|
347
|
+
let workingEndBound = endBound.slice();
|
|
348
|
+
let workingStartBoundType = startBoundType;
|
|
349
|
+
let workingEndBoundType = endBoundType;
|
|
350
|
+
while (workingStartBound.length > 0 && workingEndBound.length > 0 && compareValues(workingStartBound[0], workingEndBound[0]) === 0) {
|
|
351
|
+
const indexField = workingIndexFields[0];
|
|
352
|
+
workingIndexFields = workingIndexFields.slice(1);
|
|
353
|
+
const eqBound = workingStartBound[0];
|
|
354
|
+
workingStartBound = workingStartBound.slice(1);
|
|
355
|
+
workingEndBound = workingEndBound.slice(1);
|
|
356
|
+
commonPrefix.push([
|
|
357
|
+
"eq",
|
|
358
|
+
indexField,
|
|
359
|
+
eqBound
|
|
360
|
+
]);
|
|
361
|
+
}
|
|
362
|
+
const makeCompare = (boundType, key) => {
|
|
363
|
+
const range = commonPrefix.slice();
|
|
364
|
+
let i = 0;
|
|
365
|
+
for (; i < key.length - 1; i++) range.push([
|
|
366
|
+
"eq",
|
|
367
|
+
workingIndexFields[i],
|
|
368
|
+
key[i]
|
|
369
|
+
]);
|
|
370
|
+
if (i < key.length) range.push([
|
|
371
|
+
boundType,
|
|
372
|
+
workingIndexFields[i],
|
|
373
|
+
key[i]
|
|
374
|
+
]);
|
|
375
|
+
return range;
|
|
376
|
+
};
|
|
377
|
+
const startRanges = [];
|
|
378
|
+
while (workingStartBound.length > 1) {
|
|
379
|
+
startRanges.push(makeCompare(workingStartBoundType, workingStartBound));
|
|
380
|
+
workingStartBoundType = makeExclusive(workingStartBoundType);
|
|
381
|
+
workingStartBound = workingStartBound.slice(0, -1);
|
|
382
|
+
}
|
|
383
|
+
const endRanges = [];
|
|
384
|
+
while (workingEndBound.length > 1) {
|
|
385
|
+
endRanges.push(makeCompare(workingEndBoundType, workingEndBound));
|
|
386
|
+
workingEndBoundType = makeExclusive(workingEndBoundType);
|
|
387
|
+
workingEndBound = workingEndBound.slice(0, -1);
|
|
388
|
+
}
|
|
389
|
+
endRanges.reverse();
|
|
390
|
+
let middleRange;
|
|
391
|
+
if (workingEndBound.length === 0) middleRange = makeCompare(workingStartBoundType, workingStartBound);
|
|
392
|
+
else if (workingStartBound.length === 0) middleRange = makeCompare(workingEndBoundType, workingEndBound);
|
|
393
|
+
else {
|
|
394
|
+
const startValue = workingStartBound[0];
|
|
395
|
+
const endValue = workingEndBound[0];
|
|
396
|
+
middleRange = commonPrefix.slice();
|
|
397
|
+
middleRange.push([
|
|
398
|
+
workingStartBoundType,
|
|
399
|
+
workingIndexFields[0],
|
|
400
|
+
startValue
|
|
401
|
+
]);
|
|
402
|
+
middleRange.push([
|
|
403
|
+
workingEndBoundType,
|
|
404
|
+
workingIndexFields[0],
|
|
405
|
+
endValue
|
|
406
|
+
]);
|
|
407
|
+
}
|
|
408
|
+
const ranges = [
|
|
409
|
+
...startRanges,
|
|
410
|
+
middleRange,
|
|
411
|
+
...endRanges
|
|
412
|
+
];
|
|
413
|
+
if (order === "desc") ranges.reverse();
|
|
414
|
+
return ranges;
|
|
415
|
+
}
|
|
416
|
+
function rangeToQuery(range) {
|
|
417
|
+
return (q) => {
|
|
418
|
+
let query = q;
|
|
419
|
+
for (const [boundType, field, value] of range) query = query[boundType](field, value);
|
|
420
|
+
return query;
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Get the ordered list of fields for a given table's index based on the schema.
|
|
425
|
+
*
|
|
426
|
+
* - For "by_creation_time", returns ["_creationTime", "_id"].
|
|
427
|
+
* - For "by_id", returns ["_id"].
|
|
428
|
+
* - Otherwise, looks up the named index in the schema and returns its fields
|
|
429
|
+
* followed by ["_creationTime", "_id"].
|
|
430
|
+
* e.g. for an index defined like `.index("abc", ["a", "b"])`,
|
|
431
|
+
* returns ["a", "b", "_creationTime", "_id"].
|
|
432
|
+
*/
|
|
433
|
+
function getIndexFields(table, index, schema) {
|
|
434
|
+
const indexDescriptor = String(index ?? "by_creation_time");
|
|
435
|
+
if (indexDescriptor === "by_creation_time") return ["_creationTime", "_id"];
|
|
436
|
+
if (indexDescriptor === "by_id") return ["_id"];
|
|
437
|
+
if (!schema) throw new Error("schema is required to infer index fields");
|
|
438
|
+
const indexInfo = schema.tables[table].indexes.find((index) => index.indexDescriptor === indexDescriptor);
|
|
439
|
+
if (!indexInfo) throw new Error(`Index ${indexDescriptor} not found in table ${table}`);
|
|
440
|
+
const fields = indexInfo.fields.slice();
|
|
441
|
+
fields.push("_creationTime");
|
|
442
|
+
fields.push("_id");
|
|
443
|
+
return fields;
|
|
444
|
+
}
|
|
445
|
+
function getIndexKey(doc, indexFields) {
|
|
446
|
+
const key = [];
|
|
447
|
+
for (const field of indexFields) {
|
|
448
|
+
let obj = doc;
|
|
449
|
+
for (const subfield of field.split(".")) obj = obj[subfield];
|
|
450
|
+
key.push(obj);
|
|
451
|
+
}
|
|
452
|
+
return key;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* A "stream" is an async iterable of query results, ordered by an index on a table.
|
|
456
|
+
*
|
|
457
|
+
* Use it as you would use `ctx.db`.
|
|
458
|
+
* If using pagination in a reactive query, see the warnings on the `paginator`
|
|
459
|
+
* function. TL;DR: you need to pass in `endCursor` to prevent holes or overlaps
|
|
460
|
+
* between pages.
|
|
461
|
+
*
|
|
462
|
+
* Once you have a stream, you can use `mergeStreams` or `filterStream` to make
|
|
463
|
+
* more streams. Then use `queryStream` to convert it into an OrderedQuery,
|
|
464
|
+
* so you can call `.paginate()`, `.collect()`, etc.
|
|
465
|
+
*/
|
|
466
|
+
function stream(db, schema) {
|
|
467
|
+
return new StreamDatabaseReader(db, schema);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* A "QueryStream" is an async iterable of query results, ordered by indexed fields.
|
|
471
|
+
*/
|
|
472
|
+
var QueryStream = class {
|
|
473
|
+
/**
|
|
474
|
+
* Create a new stream with a TypeScript filter applied.
|
|
475
|
+
*
|
|
476
|
+
* This is similar to `db.query(tableName).filter(predicate)`, but it's more
|
|
477
|
+
* general because it can call arbitrary TypeScript code, including more
|
|
478
|
+
* database queries.
|
|
479
|
+
*
|
|
480
|
+
* All documents filtered out are still considered "read" from the database;
|
|
481
|
+
* they are just excluded from the output stream.
|
|
482
|
+
*
|
|
483
|
+
* In contrast to `filter` from legacy helper filtering, this filterWith
|
|
484
|
+
* is applied *before* any pagination. That means if the filter excludes a lot
|
|
485
|
+
* of documents, the `.paginate()` method will read a lot of documents until
|
|
486
|
+
* it gets as many documents as it wants. If you run into issues with reading
|
|
487
|
+
* too much data, you can pass `maxScan` to `paginate()`.
|
|
488
|
+
*/
|
|
489
|
+
filterWith(predicate) {
|
|
490
|
+
const order = this.getOrder();
|
|
491
|
+
return new FlatMapStream(this, async (doc) => {
|
|
492
|
+
return new SingletonStream(await predicate(doc) ? doc : null, order, [], [], []);
|
|
493
|
+
}, []);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Create a new stream where each element is the result of applying the mapper
|
|
497
|
+
* function to the elements of the original stream.
|
|
498
|
+
*
|
|
499
|
+
* Similar to how [1, 2, 3].map(x => x * 2) => [2, 4, 6]
|
|
500
|
+
*/
|
|
501
|
+
map(mapper) {
|
|
502
|
+
const order = this.getOrder();
|
|
503
|
+
return new FlatMapStream(this, async (doc) => {
|
|
504
|
+
return new SingletonStream(await mapper(doc), order, [], [], []);
|
|
505
|
+
}, []);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Similar to flatMap on an array, but iterate over a stream, and the for each
|
|
509
|
+
* element, iterate over the stream created by the mapper function.
|
|
510
|
+
*
|
|
511
|
+
* Ordered by the original stream order, then the mapped stream. Similar to
|
|
512
|
+
* how ["a", "b"].flatMap(letter => [letter, letter]) => ["a", "a", "b", "b"]
|
|
513
|
+
*
|
|
514
|
+
* @param mapper A function that takes a document and returns a new stream.
|
|
515
|
+
* @param mappedIndexFields The index fields of the streams created by mapper.
|
|
516
|
+
* @returns A stream of documents returned by the mapper streams,
|
|
517
|
+
* grouped by the documents in the original stream.
|
|
518
|
+
*/
|
|
519
|
+
flatMap(mapper, mappedIndexFields) {
|
|
520
|
+
normalizeIndexFields(mappedIndexFields);
|
|
521
|
+
return new FlatMapStream(this, mapper, mappedIndexFields);
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Get the first item from the original stream for each distinct value of the
|
|
525
|
+
* selected index fields.
|
|
526
|
+
*
|
|
527
|
+
* e.g. if the stream has an equality filter on `a`, and index fields `[a, b, c]`,
|
|
528
|
+
* we can do `stream.distinct(["b"])` to get a stream of the first item for
|
|
529
|
+
* each distinct value of `b`.
|
|
530
|
+
* Similarly, you could do `stream.distinct(["a", "b"])` with the same result,
|
|
531
|
+
* or `stream.distinct(["a", "b", "c"])` to get the original stream.
|
|
532
|
+
*
|
|
533
|
+
* This stream efficiently skips past items with the same value for the selected
|
|
534
|
+
* distinct index fields.
|
|
535
|
+
*
|
|
536
|
+
* This can be used to perform a loose index scan.
|
|
537
|
+
*/
|
|
538
|
+
distinct(distinctIndexFields) {
|
|
539
|
+
return new DistinctStream(this, distinctIndexFields);
|
|
540
|
+
}
|
|
541
|
+
filter(_predicate) {
|
|
542
|
+
throw new Error("Cannot call .filter() directly on a query stream. Use .filterWith() for filtering or .collect() if you want to convert the stream to an array first.");
|
|
543
|
+
}
|
|
544
|
+
async paginate(opts) {
|
|
545
|
+
if (opts.limit === 0) {
|
|
546
|
+
if (opts.cursor === null) throw new Error(".paginate called with cursor of null and 0 for limit. This is not supported, as null is not a valid continueCursor. Advice: avoid calling paginate entirely in these cases.");
|
|
547
|
+
return {
|
|
548
|
+
page: [],
|
|
549
|
+
isDone: false,
|
|
550
|
+
continueCursor: opts.cursor
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
const order = this.getOrder();
|
|
554
|
+
let newStartKey = {
|
|
555
|
+
key: [],
|
|
556
|
+
inclusive: true
|
|
557
|
+
};
|
|
558
|
+
if (opts.cursor !== null) newStartKey = {
|
|
559
|
+
key: deserializeCursor(opts.cursor),
|
|
560
|
+
inclusive: false
|
|
561
|
+
};
|
|
562
|
+
let newEndKey = {
|
|
563
|
+
key: [],
|
|
564
|
+
inclusive: true
|
|
565
|
+
};
|
|
566
|
+
const maxRowsToRead = opts.maxScan;
|
|
567
|
+
const softMaxRowsToRead = opts.limit + 1;
|
|
568
|
+
let maxRows = opts.limit;
|
|
569
|
+
if (opts.endCursor) {
|
|
570
|
+
newEndKey = {
|
|
571
|
+
key: deserializeCursor(opts.endCursor),
|
|
572
|
+
inclusive: true
|
|
573
|
+
};
|
|
574
|
+
maxRows = void 0;
|
|
575
|
+
}
|
|
576
|
+
const newLowerBound = order === "asc" ? newStartKey : newEndKey;
|
|
577
|
+
const newUpperBound = order === "asc" ? newEndKey : newStartKey;
|
|
578
|
+
const narrowStream = this.narrow({
|
|
579
|
+
lowerBound: newLowerBound.key,
|
|
580
|
+
lowerBoundInclusive: newLowerBound.inclusive,
|
|
581
|
+
upperBound: newUpperBound.key,
|
|
582
|
+
upperBoundInclusive: newUpperBound.inclusive
|
|
583
|
+
});
|
|
584
|
+
const page = [];
|
|
585
|
+
const indexKeys = [];
|
|
586
|
+
let hasMore = opts.endCursor && opts.endCursor !== "[]";
|
|
587
|
+
let continueCursor = opts.endCursor ?? "[]";
|
|
588
|
+
for await (const [doc, indexKey] of narrowStream.iterWithKeys()) {
|
|
589
|
+
if (doc !== null) page.push(doc);
|
|
590
|
+
indexKeys.push(indexKey);
|
|
591
|
+
if (maxRows !== void 0 && page.length >= maxRows || maxRowsToRead !== void 0 && indexKeys.length >= maxRowsToRead) {
|
|
592
|
+
hasMore = true;
|
|
593
|
+
continueCursor = serializeCursor(indexKey);
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
let pageStatus;
|
|
598
|
+
let splitCursor;
|
|
599
|
+
if (indexKeys.length === maxRowsToRead) {
|
|
600
|
+
pageStatus = "SplitRequired";
|
|
601
|
+
splitCursor = indexKeys[Math.floor((indexKeys.length - 1) / 2)];
|
|
602
|
+
} else if (indexKeys.length >= softMaxRowsToRead) {
|
|
603
|
+
pageStatus = "SplitRecommended";
|
|
604
|
+
splitCursor = indexKeys[Math.floor((indexKeys.length - 1) / 2)];
|
|
605
|
+
}
|
|
606
|
+
return {
|
|
607
|
+
page,
|
|
608
|
+
isDone: !hasMore,
|
|
609
|
+
continueCursor,
|
|
610
|
+
pageStatus,
|
|
611
|
+
splitCursor: splitCursor ? serializeCursor(splitCursor) : void 0
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
async collect() {
|
|
615
|
+
return await this.take(Number.POSITIVE_INFINITY);
|
|
616
|
+
}
|
|
617
|
+
async take(n) {
|
|
618
|
+
const results = [];
|
|
619
|
+
for await (const [doc, _] of this.iterWithKeys()) {
|
|
620
|
+
if (doc === null) continue;
|
|
621
|
+
results.push(doc);
|
|
622
|
+
if (results.length === n) break;
|
|
623
|
+
}
|
|
624
|
+
return results;
|
|
625
|
+
}
|
|
626
|
+
async unique() {
|
|
627
|
+
const docs = await this.take(2);
|
|
628
|
+
if (docs.length === 2) throw new Error("Query is not unique");
|
|
629
|
+
return docs[0] ?? null;
|
|
630
|
+
}
|
|
631
|
+
async uniqueOrThrow() {
|
|
632
|
+
const doc = await this.unique();
|
|
633
|
+
if (doc === null) throw new Error("Query returned no results");
|
|
634
|
+
return doc;
|
|
635
|
+
}
|
|
636
|
+
async first() {
|
|
637
|
+
return (await this.take(1))[0] ?? null;
|
|
638
|
+
}
|
|
639
|
+
async firstOrThrow() {
|
|
640
|
+
const doc = await this.first();
|
|
641
|
+
if (doc === null) throw new Error("Query returned no results");
|
|
642
|
+
return doc;
|
|
643
|
+
}
|
|
644
|
+
[Symbol.asyncIterator]() {
|
|
645
|
+
const iterator = this.iterWithKeys()[Symbol.asyncIterator]();
|
|
646
|
+
return { async next() {
|
|
647
|
+
const result = await iterator.next();
|
|
648
|
+
if (result.done) return {
|
|
649
|
+
done: true,
|
|
650
|
+
value: void 0
|
|
651
|
+
};
|
|
652
|
+
return {
|
|
653
|
+
done: false,
|
|
654
|
+
value: result.value[0]
|
|
655
|
+
};
|
|
656
|
+
} };
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
var StreamDatabaseReader = class {
|
|
660
|
+
system;
|
|
661
|
+
db;
|
|
662
|
+
schema;
|
|
663
|
+
constructor(db, schema) {
|
|
664
|
+
this.db = db;
|
|
665
|
+
this.schema = schema;
|
|
666
|
+
this.system = db.system;
|
|
667
|
+
}
|
|
668
|
+
query(tableName) {
|
|
669
|
+
return new StreamQueryInitializer(this, tableName);
|
|
670
|
+
}
|
|
671
|
+
get(_id) {
|
|
672
|
+
throw new Error("get() not supported for `paginator`");
|
|
673
|
+
}
|
|
674
|
+
normalizeId(_tableName, _id) {
|
|
675
|
+
throw new Error("normalizeId() not supported for `paginator`.");
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
var StreamableQuery = class extends QueryStream {};
|
|
679
|
+
var StreamQueryInitializer = class extends StreamableQuery {
|
|
680
|
+
parent;
|
|
681
|
+
table;
|
|
682
|
+
constructor(parent, table) {
|
|
683
|
+
super();
|
|
684
|
+
this.parent = parent;
|
|
685
|
+
this.table = table;
|
|
686
|
+
}
|
|
687
|
+
fullTableScan() {
|
|
688
|
+
return this.withIndex("by_creation_time");
|
|
689
|
+
}
|
|
690
|
+
withIndex(indexName, indexRange) {
|
|
691
|
+
const q = new ReflectIndexRange(getIndexFields(this.table, indexName, this.parent.schema));
|
|
692
|
+
if (indexRange) indexRange(q);
|
|
693
|
+
return new StreamQuery(this, indexName, q, indexRange);
|
|
694
|
+
}
|
|
695
|
+
withSearchIndex(_indexName, _searchFilter) {
|
|
696
|
+
throw new Error("Cannot paginate withSearchIndex");
|
|
697
|
+
}
|
|
698
|
+
inner() {
|
|
699
|
+
return this.fullTableScan();
|
|
700
|
+
}
|
|
701
|
+
order(order) {
|
|
702
|
+
return this.inner().order(order);
|
|
703
|
+
}
|
|
704
|
+
reflect() {
|
|
705
|
+
return this.inner().reflect();
|
|
706
|
+
}
|
|
707
|
+
iterWithKeys() {
|
|
708
|
+
return this.inner().iterWithKeys();
|
|
709
|
+
}
|
|
710
|
+
getOrder() {
|
|
711
|
+
return this.inner().getOrder();
|
|
712
|
+
}
|
|
713
|
+
getEqualityIndexFilter() {
|
|
714
|
+
return this.inner().getEqualityIndexFilter();
|
|
715
|
+
}
|
|
716
|
+
getIndexFields() {
|
|
717
|
+
return this.inner().getIndexFields();
|
|
718
|
+
}
|
|
719
|
+
narrow(indexBounds) {
|
|
720
|
+
return this.inner().narrow(indexBounds);
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
var StreamQuery = class extends StreamableQuery {
|
|
724
|
+
parent;
|
|
725
|
+
index;
|
|
726
|
+
q;
|
|
727
|
+
indexRange;
|
|
728
|
+
constructor(parent, index, q, indexRange) {
|
|
729
|
+
super();
|
|
730
|
+
this.parent = parent;
|
|
731
|
+
this.index = index;
|
|
732
|
+
this.q = q;
|
|
733
|
+
this.indexRange = indexRange;
|
|
734
|
+
}
|
|
735
|
+
order(order) {
|
|
736
|
+
return new OrderedStreamQuery(this, order);
|
|
737
|
+
}
|
|
738
|
+
inner() {
|
|
739
|
+
return this.order("asc");
|
|
740
|
+
}
|
|
741
|
+
reflect() {
|
|
742
|
+
return this.inner().reflect();
|
|
743
|
+
}
|
|
744
|
+
iterWithKeys() {
|
|
745
|
+
return this.inner().iterWithKeys();
|
|
746
|
+
}
|
|
747
|
+
getOrder() {
|
|
748
|
+
return this.inner().getOrder();
|
|
749
|
+
}
|
|
750
|
+
getEqualityIndexFilter() {
|
|
751
|
+
return this.inner().getEqualityIndexFilter();
|
|
752
|
+
}
|
|
753
|
+
getIndexFields() {
|
|
754
|
+
return this.inner().getIndexFields();
|
|
755
|
+
}
|
|
756
|
+
narrow(indexBounds) {
|
|
757
|
+
return this.inner().narrow(indexBounds);
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
var OrderedStreamQuery = class extends StreamableQuery {
|
|
761
|
+
parent;
|
|
762
|
+
order;
|
|
763
|
+
constructor(parent, order) {
|
|
764
|
+
super();
|
|
765
|
+
this.parent = parent;
|
|
766
|
+
this.order = order;
|
|
767
|
+
}
|
|
768
|
+
reflect() {
|
|
769
|
+
return {
|
|
770
|
+
db: this.parent.parent.parent.db,
|
|
771
|
+
schema: this.parent.parent.parent.schema,
|
|
772
|
+
table: this.parent.parent.table,
|
|
773
|
+
index: this.parent.index,
|
|
774
|
+
indexFields: this.parent.q.indexFields,
|
|
775
|
+
order: this.order,
|
|
776
|
+
bounds: {
|
|
777
|
+
lowerBound: this.parent.q.lowerBoundIndexKey ?? [],
|
|
778
|
+
lowerBoundInclusive: this.parent.q.lowerBoundInclusive,
|
|
779
|
+
upperBound: this.parent.q.upperBoundIndexKey ?? [],
|
|
780
|
+
upperBoundInclusive: this.parent.q.upperBoundInclusive
|
|
781
|
+
},
|
|
782
|
+
indexRange: this.parent.indexRange
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* inner() is as if you had used ctx.db to construct the query.
|
|
787
|
+
*/
|
|
788
|
+
inner() {
|
|
789
|
+
const { db, table, index, order, indexRange } = this.reflect();
|
|
790
|
+
return db.query(table).withIndex(index, indexRange).order(order);
|
|
791
|
+
}
|
|
792
|
+
iterWithKeys() {
|
|
793
|
+
const { indexFields } = this.reflect();
|
|
794
|
+
const iterable = this.inner();
|
|
795
|
+
return { [Symbol.asyncIterator]() {
|
|
796
|
+
const iterator = iterable[Symbol.asyncIterator]();
|
|
797
|
+
return { async next() {
|
|
798
|
+
const result = await iterator.next();
|
|
799
|
+
if (result.done) return {
|
|
800
|
+
done: true,
|
|
801
|
+
value: void 0
|
|
802
|
+
};
|
|
803
|
+
return {
|
|
804
|
+
done: false,
|
|
805
|
+
value: [result.value, getIndexKey(result.value, indexFields)]
|
|
806
|
+
};
|
|
807
|
+
} };
|
|
808
|
+
} };
|
|
809
|
+
}
|
|
810
|
+
getOrder() {
|
|
811
|
+
return this.order;
|
|
812
|
+
}
|
|
813
|
+
getEqualityIndexFilter() {
|
|
814
|
+
return this.parent.q.equalityIndexFilter;
|
|
815
|
+
}
|
|
816
|
+
getIndexFields() {
|
|
817
|
+
return this.parent.q.indexFields;
|
|
818
|
+
}
|
|
819
|
+
narrow(indexBounds) {
|
|
820
|
+
const { db, table, index, order, bounds, schema } = this.reflect();
|
|
821
|
+
let maxLowerBound = bounds.lowerBound;
|
|
822
|
+
let maxLowerBoundInclusive = bounds.lowerBoundInclusive;
|
|
823
|
+
if (compareKeys({
|
|
824
|
+
value: indexBounds.lowerBound,
|
|
825
|
+
kind: indexBounds.lowerBoundInclusive ? "predecessor" : "successor"
|
|
826
|
+
}, {
|
|
827
|
+
value: bounds.lowerBound,
|
|
828
|
+
kind: bounds.lowerBoundInclusive ? "predecessor" : "successor"
|
|
829
|
+
}) > 0) {
|
|
830
|
+
maxLowerBound = indexBounds.lowerBound;
|
|
831
|
+
maxLowerBoundInclusive = indexBounds.lowerBoundInclusive;
|
|
832
|
+
}
|
|
833
|
+
let minUpperBound = bounds.upperBound;
|
|
834
|
+
let minUpperBoundInclusive = bounds.upperBoundInclusive;
|
|
835
|
+
if (compareKeys({
|
|
836
|
+
value: indexBounds.upperBound,
|
|
837
|
+
kind: indexBounds.upperBoundInclusive ? "successor" : "predecessor"
|
|
838
|
+
}, {
|
|
839
|
+
value: bounds.upperBound,
|
|
840
|
+
kind: bounds.upperBoundInclusive ? "successor" : "predecessor"
|
|
841
|
+
}) < 0) {
|
|
842
|
+
minUpperBound = indexBounds.upperBound;
|
|
843
|
+
minUpperBoundInclusive = indexBounds.upperBoundInclusive;
|
|
844
|
+
}
|
|
845
|
+
return streamIndexRange(db, schema, table, index, {
|
|
846
|
+
lowerBound: maxLowerBound,
|
|
847
|
+
lowerBoundInclusive: maxLowerBoundInclusive,
|
|
848
|
+
upperBound: minUpperBound,
|
|
849
|
+
upperBoundInclusive: minUpperBoundInclusive
|
|
850
|
+
}, order);
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
/**
|
|
854
|
+
* Create a stream of documents using the given index and bounds.
|
|
855
|
+
*/
|
|
856
|
+
function streamIndexRange(db, schema, table, index, bounds, order) {
|
|
857
|
+
return new ConcatStreams(...splitRange(getIndexFields(table, index, schema), order, bounds.lowerBound, bounds.upperBound, bounds.lowerBoundInclusive ? "gte" : "gt", bounds.upperBoundInclusive ? "lte" : "lt").map((splitBound) => stream(db, schema).query(table).withIndex(index, rangeToQuery(splitBound)).order(order)));
|
|
858
|
+
}
|
|
859
|
+
var ReflectIndexRange = class {
|
|
860
|
+
#hasSuffix = false;
|
|
861
|
+
lowerBoundIndexKey = void 0;
|
|
862
|
+
lowerBoundInclusive = true;
|
|
863
|
+
upperBoundIndexKey = void 0;
|
|
864
|
+
upperBoundInclusive = true;
|
|
865
|
+
equalityIndexFilter = [];
|
|
866
|
+
indexFields;
|
|
867
|
+
constructor(indexFields) {
|
|
868
|
+
this.indexFields = indexFields;
|
|
869
|
+
}
|
|
870
|
+
eq(field, value) {
|
|
871
|
+
if (!this.#canLowerBound(field) || !this.#canUpperBound(field)) throw new Error(`Cannot use eq on field '${field}'`);
|
|
872
|
+
this.lowerBoundIndexKey = this.lowerBoundIndexKey ?? [];
|
|
873
|
+
this.lowerBoundIndexKey.push(value);
|
|
874
|
+
this.upperBoundIndexKey = this.upperBoundIndexKey ?? [];
|
|
875
|
+
this.upperBoundIndexKey.push(value);
|
|
876
|
+
this.equalityIndexFilter.push(value);
|
|
877
|
+
return this;
|
|
878
|
+
}
|
|
879
|
+
lt(field, value) {
|
|
880
|
+
if (!this.#canUpperBound(field)) throw new Error(`Cannot use lt on field '${field}'`);
|
|
881
|
+
this.upperBoundIndexKey = this.upperBoundIndexKey ?? [];
|
|
882
|
+
this.upperBoundIndexKey.push(value);
|
|
883
|
+
this.upperBoundInclusive = false;
|
|
884
|
+
this.#hasSuffix = true;
|
|
885
|
+
return this;
|
|
886
|
+
}
|
|
887
|
+
lte(field, value) {
|
|
888
|
+
if (!this.#canUpperBound(field)) throw new Error(`Cannot use lte on field '${field}'`);
|
|
889
|
+
this.upperBoundIndexKey = this.upperBoundIndexKey ?? [];
|
|
890
|
+
this.upperBoundIndexKey.push(value);
|
|
891
|
+
this.#hasSuffix = true;
|
|
892
|
+
return this;
|
|
893
|
+
}
|
|
894
|
+
gt(field, value) {
|
|
895
|
+
if (!this.#canLowerBound(field)) throw new Error(`Cannot use gt on field '${field}'`);
|
|
896
|
+
this.lowerBoundIndexKey = this.lowerBoundIndexKey ?? [];
|
|
897
|
+
this.lowerBoundIndexKey.push(value);
|
|
898
|
+
this.lowerBoundInclusive = false;
|
|
899
|
+
this.#hasSuffix = true;
|
|
900
|
+
return this;
|
|
901
|
+
}
|
|
902
|
+
gte(field, value) {
|
|
903
|
+
if (!this.#canLowerBound(field)) throw new Error(`Cannot use gte on field '${field}'`);
|
|
904
|
+
this.lowerBoundIndexKey = this.lowerBoundIndexKey ?? [];
|
|
905
|
+
this.lowerBoundIndexKey.push(value);
|
|
906
|
+
this.#hasSuffix = true;
|
|
907
|
+
return this;
|
|
908
|
+
}
|
|
909
|
+
#canLowerBound(field) {
|
|
910
|
+
const currentLowerBoundLength = this.lowerBoundIndexKey?.length ?? 0;
|
|
911
|
+
const currentUpperBoundLength = this.upperBoundIndexKey?.length ?? 0;
|
|
912
|
+
if (currentLowerBoundLength > currentUpperBoundLength) return false;
|
|
913
|
+
if (currentLowerBoundLength === currentUpperBoundLength && this.#hasSuffix) return false;
|
|
914
|
+
return currentLowerBoundLength < this.indexFields.length && this.indexFields[currentLowerBoundLength] === field;
|
|
915
|
+
}
|
|
916
|
+
#canUpperBound(field) {
|
|
917
|
+
const currentLowerBoundLength = this.lowerBoundIndexKey?.length ?? 0;
|
|
918
|
+
const currentUpperBoundLength = this.upperBoundIndexKey?.length ?? 0;
|
|
919
|
+
if (currentUpperBoundLength > currentLowerBoundLength) return false;
|
|
920
|
+
if (currentLowerBoundLength === currentUpperBoundLength && this.#hasSuffix) return false;
|
|
921
|
+
return currentUpperBoundLength < this.indexFields.length && this.indexFields[currentUpperBoundLength] === field;
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
/**
|
|
925
|
+
* Merge multiple streams, provided in any order, into a single stream.
|
|
926
|
+
*
|
|
927
|
+
* The streams will be merged into a stream of documents ordered by the index keys,
|
|
928
|
+
* i.e. by "author" (then by the implicit "_creationTime").
|
|
929
|
+
*
|
|
930
|
+
* e.g. ```ts
|
|
931
|
+
* mergedStream([
|
|
932
|
+
* stream(db, schema).query("messages").withIndex("by_author", q => q.eq("author", "user3")),
|
|
933
|
+
* stream(db, schema).query("messages").withIndex("by_author", q => q.eq("author", "user1")),
|
|
934
|
+
* stream(db, schema).query("messages").withIndex("by_author", q => q.eq("author", "user2")),
|
|
935
|
+
* ], ["author"])
|
|
936
|
+
* ```
|
|
937
|
+
*
|
|
938
|
+
* returns a stream of messages for user1, then user2, then user3.
|
|
939
|
+
*
|
|
940
|
+
* You can also use `orderByIndexFields` to change the indexed fields before merging, which changes the order of the merged stream.
|
|
941
|
+
* This only works if the streams are already ordered by `orderByIndexFields`,
|
|
942
|
+
* which happens if each does a .eq(field, value) on all index fields before `orderByIndexFields`.
|
|
943
|
+
*
|
|
944
|
+
* e.g. if the "by_author" index is defined as being ordered by ["author", "_creationTime"],
|
|
945
|
+
* and each query does an equality lookup on "author", each individual query before merging is in fact ordered by "_creationTime".
|
|
946
|
+
*
|
|
947
|
+
* e.g. ```ts
|
|
948
|
+
* mergedStream([
|
|
949
|
+
* stream(db, schema).query("messages").withIndex("by_author", q => q.eq("author", "user3")),
|
|
950
|
+
* stream(db, schema).query("messages").withIndex("by_author", q => q.eq("author", "user1")),
|
|
951
|
+
* stream(db, schema).query("messages").withIndex("by_author", q => q.eq("author", "user2")),
|
|
952
|
+
* ], ["_creationTime"])
|
|
953
|
+
* ```
|
|
954
|
+
*
|
|
955
|
+
* This returns a stream of messages from all three users, sorted by creation time.
|
|
956
|
+
*/
|
|
957
|
+
function mergedStream(streams, orderByIndexFields) {
|
|
958
|
+
return new MergedStream(streams, orderByIndexFields);
|
|
959
|
+
}
|
|
960
|
+
var MergedStream = class MergedStream extends QueryStream {
|
|
961
|
+
#order;
|
|
962
|
+
#streams;
|
|
963
|
+
#equalityIndexFilter;
|
|
964
|
+
#indexFields;
|
|
965
|
+
constructor(streams, orderByIndexFields) {
|
|
966
|
+
super();
|
|
967
|
+
if (streams.length === 0) throw new Error("Cannot union empty array of streams");
|
|
968
|
+
this.#order = allSame(streams.map((stream) => stream.getOrder()), "Cannot merge streams with different orders");
|
|
969
|
+
this.#streams = streams.map((stream) => new OrderByStream(stream, orderByIndexFields));
|
|
970
|
+
this.#indexFields = allSame(this.#streams.map((stream) => stream.getIndexFields()), "Cannot merge streams with different index fields. Consider using .orderBy()");
|
|
971
|
+
this.#equalityIndexFilter = commonPrefix(this.#streams.map((stream) => stream.getEqualityIndexFilter()));
|
|
972
|
+
}
|
|
973
|
+
iterWithKeys() {
|
|
974
|
+
const iterables = this.#streams.map((stream) => stream.iterWithKeys());
|
|
975
|
+
const comparisonInversion = this.#order === "asc" ? 1 : -1;
|
|
976
|
+
return { [Symbol.asyncIterator]() {
|
|
977
|
+
const iterators = iterables.map((iterable) => iterable[Symbol.asyncIterator]());
|
|
978
|
+
const results = Array.from({ length: iterators.length }, () => ({
|
|
979
|
+
done: false,
|
|
980
|
+
value: void 0
|
|
981
|
+
}));
|
|
982
|
+
return { async next() {
|
|
983
|
+
await Promise.all(iterators.map(async (iterator, i) => {
|
|
984
|
+
if (!results[i].done && !results[i].value) results[i] = await iterator.next();
|
|
985
|
+
}));
|
|
986
|
+
let minIndexKeyAndIndex;
|
|
987
|
+
for (let i = 0; i < results.length; i++) {
|
|
988
|
+
const result = results[i];
|
|
989
|
+
if (result.done || !result.value) continue;
|
|
990
|
+
const [_, resultIndexKey] = result.value;
|
|
991
|
+
if (minIndexKeyAndIndex === void 0) {
|
|
992
|
+
minIndexKeyAndIndex = [resultIndexKey, i];
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
const [prevMin, _prevMinIndex] = minIndexKeyAndIndex;
|
|
996
|
+
if (compareKeys({
|
|
997
|
+
value: resultIndexKey,
|
|
998
|
+
kind: "exact"
|
|
999
|
+
}, {
|
|
1000
|
+
value: prevMin,
|
|
1001
|
+
kind: "exact"
|
|
1002
|
+
}) * comparisonInversion < 0) minIndexKeyAndIndex = [resultIndexKey, i];
|
|
1003
|
+
}
|
|
1004
|
+
if (minIndexKeyAndIndex === void 0) return {
|
|
1005
|
+
done: true,
|
|
1006
|
+
value: void 0
|
|
1007
|
+
};
|
|
1008
|
+
const [_, minIndex] = minIndexKeyAndIndex;
|
|
1009
|
+
const result = results[minIndex].value;
|
|
1010
|
+
results[minIndex].value = void 0;
|
|
1011
|
+
return {
|
|
1012
|
+
done: false,
|
|
1013
|
+
value: result
|
|
1014
|
+
};
|
|
1015
|
+
} };
|
|
1016
|
+
} };
|
|
1017
|
+
}
|
|
1018
|
+
getOrder() {
|
|
1019
|
+
return this.#order;
|
|
1020
|
+
}
|
|
1021
|
+
getEqualityIndexFilter() {
|
|
1022
|
+
return this.#equalityIndexFilter;
|
|
1023
|
+
}
|
|
1024
|
+
getIndexFields() {
|
|
1025
|
+
return this.#indexFields;
|
|
1026
|
+
}
|
|
1027
|
+
narrow(indexBounds) {
|
|
1028
|
+
return new MergedStream(this.#streams.map((stream) => stream.narrow(indexBounds)), this.#indexFields);
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
function allSame(values, errorMessage) {
|
|
1032
|
+
const first = values[0];
|
|
1033
|
+
for (const value of values) if (compareValues(value, first)) throw new Error(errorMessage);
|
|
1034
|
+
return first;
|
|
1035
|
+
}
|
|
1036
|
+
function commonPrefix(values) {
|
|
1037
|
+
let commonPrefix = values[0];
|
|
1038
|
+
for (const value of values) for (let i = 0; i < commonPrefix.length; i++) if (i >= value.length || compareValues(commonPrefix[i], value[i])) {
|
|
1039
|
+
commonPrefix = commonPrefix.slice(0, i);
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
1042
|
+
return commonPrefix;
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Concatenate multiple streams into a single stream.
|
|
1046
|
+
* This assumes that the streams correspond to disjoint index ranges,
|
|
1047
|
+
* and are provided in the same order as the index ranges.
|
|
1048
|
+
*
|
|
1049
|
+
* e.g. ```ts
|
|
1050
|
+
* new ConcatStreams(
|
|
1051
|
+
* stream(db, schema).query("messages").withIndex("by_author", q => q.eq("author", "user1")),
|
|
1052
|
+
* stream(db, schema).query("messages").withIndex("by_author", q => q.eq("author", "user2")),
|
|
1053
|
+
* )
|
|
1054
|
+
* ```
|
|
1055
|
+
*
|
|
1056
|
+
* is valid, but if the stream arguments were reversed, or the queries were
|
|
1057
|
+
* `.order("desc")`, it would be invalid.
|
|
1058
|
+
*
|
|
1059
|
+
* It's not recommended to use `ConcatStreams` directly, since it has the same
|
|
1060
|
+
* behavior as `MergedStream`, but with fewer runtime checks.
|
|
1061
|
+
*/
|
|
1062
|
+
var ConcatStreams = class ConcatStreams extends QueryStream {
|
|
1063
|
+
#order;
|
|
1064
|
+
#streams;
|
|
1065
|
+
#equalityIndexFilter;
|
|
1066
|
+
#indexFields;
|
|
1067
|
+
constructor(...streams) {
|
|
1068
|
+
super();
|
|
1069
|
+
this.#streams = streams;
|
|
1070
|
+
if (streams.length === 0) throw new Error("Cannot concat empty array of streams");
|
|
1071
|
+
this.#order = allSame(streams.map((stream) => stream.getOrder()), "Cannot concat streams with different orders. Consider using .orderBy()");
|
|
1072
|
+
this.#indexFields = allSame(streams.map((stream) => stream.getIndexFields()), "Cannot concat streams with different index fields. Consider using .orderBy()");
|
|
1073
|
+
this.#equalityIndexFilter = commonPrefix(streams.map((stream) => stream.getEqualityIndexFilter()));
|
|
1074
|
+
}
|
|
1075
|
+
iterWithKeys() {
|
|
1076
|
+
const iterables = this.#streams.map((stream) => stream.iterWithKeys());
|
|
1077
|
+
const comparisonInversion = this.#order === "asc" ? 1 : -1;
|
|
1078
|
+
let previousIndexKey;
|
|
1079
|
+
return { [Symbol.asyncIterator]() {
|
|
1080
|
+
const iterators = iterables.map((iterable) => iterable[Symbol.asyncIterator]());
|
|
1081
|
+
return { async next() {
|
|
1082
|
+
while (iterators.length > 0) {
|
|
1083
|
+
const result = await iterators[0].next();
|
|
1084
|
+
if (result.done) iterators.shift();
|
|
1085
|
+
else {
|
|
1086
|
+
const [_, indexKey] = result.value;
|
|
1087
|
+
if (previousIndexKey !== void 0 && compareKeys({
|
|
1088
|
+
value: previousIndexKey,
|
|
1089
|
+
kind: "exact"
|
|
1090
|
+
}, {
|
|
1091
|
+
value: indexKey,
|
|
1092
|
+
kind: "exact"
|
|
1093
|
+
}) * comparisonInversion > 0) throw new Error(`ConcatStreams in wrong order: ${JSON.stringify(previousIndexKey)}, ${JSON.stringify(indexKey)}`);
|
|
1094
|
+
previousIndexKey = indexKey;
|
|
1095
|
+
return result;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
return {
|
|
1099
|
+
done: true,
|
|
1100
|
+
value: void 0
|
|
1101
|
+
};
|
|
1102
|
+
} };
|
|
1103
|
+
} };
|
|
1104
|
+
}
|
|
1105
|
+
getOrder() {
|
|
1106
|
+
return this.#order;
|
|
1107
|
+
}
|
|
1108
|
+
getEqualityIndexFilter() {
|
|
1109
|
+
return this.#equalityIndexFilter;
|
|
1110
|
+
}
|
|
1111
|
+
getIndexFields() {
|
|
1112
|
+
return this.#indexFields;
|
|
1113
|
+
}
|
|
1114
|
+
narrow(indexBounds) {
|
|
1115
|
+
return new ConcatStreams(...this.#streams.map((stream) => stream.narrow(indexBounds)));
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
var FlatMapStreamIterator = class {
|
|
1119
|
+
#outerStream;
|
|
1120
|
+
#outerIterator;
|
|
1121
|
+
#currentOuterItem = null;
|
|
1122
|
+
#mapper;
|
|
1123
|
+
#mappedIndexFields;
|
|
1124
|
+
constructor(outerStream, mapper, mappedIndexFields) {
|
|
1125
|
+
this.#outerIterator = outerStream.iterWithKeys()[Symbol.asyncIterator]();
|
|
1126
|
+
this.#outerStream = outerStream;
|
|
1127
|
+
this.#mapper = mapper;
|
|
1128
|
+
this.#mappedIndexFields = mappedIndexFields;
|
|
1129
|
+
}
|
|
1130
|
+
singletonSkipInnerStream() {
|
|
1131
|
+
const indexKey = this.#mappedIndexFields.map(() => null);
|
|
1132
|
+
return new SingletonStream(null, this.#outerStream.getOrder(), this.#mappedIndexFields, indexKey, indexKey);
|
|
1133
|
+
}
|
|
1134
|
+
async setCurrentOuterItem(item) {
|
|
1135
|
+
const [t, indexKey] = item;
|
|
1136
|
+
let innerStream;
|
|
1137
|
+
if (t === null) innerStream = this.singletonSkipInnerStream();
|
|
1138
|
+
else {
|
|
1139
|
+
innerStream = await this.#mapper(t);
|
|
1140
|
+
if (!equalIndexFields(innerStream.getIndexFields(), this.#mappedIndexFields)) throw new Error(`FlatMapStream: inner stream has different index fields than expected: ${JSON.stringify(innerStream.getIndexFields())} vs ${JSON.stringify(this.#mappedIndexFields)}`);
|
|
1141
|
+
if (innerStream.getOrder() !== this.#outerStream.getOrder()) throw new Error(`FlatMapStream: inner stream has different order than outer stream: ${innerStream.getOrder()} vs ${this.#outerStream.getOrder()}`);
|
|
1142
|
+
}
|
|
1143
|
+
this.#currentOuterItem = {
|
|
1144
|
+
t,
|
|
1145
|
+
indexKey,
|
|
1146
|
+
innerIterator: innerStream.iterWithKeys()[Symbol.asyncIterator](),
|
|
1147
|
+
count: 0
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
async next() {
|
|
1151
|
+
if (this.#currentOuterItem === null) {
|
|
1152
|
+
const result = await this.#outerIterator.next();
|
|
1153
|
+
if (result.done) return {
|
|
1154
|
+
done: true,
|
|
1155
|
+
value: void 0
|
|
1156
|
+
};
|
|
1157
|
+
await this.setCurrentOuterItem(result.value);
|
|
1158
|
+
return await this.next();
|
|
1159
|
+
}
|
|
1160
|
+
const result = await this.#currentOuterItem.innerIterator.next();
|
|
1161
|
+
if (result.done) {
|
|
1162
|
+
if (this.#currentOuterItem.count > 0) this.#currentOuterItem = null;
|
|
1163
|
+
else this.#currentOuterItem.innerIterator = this.singletonSkipInnerStream().iterWithKeys()[Symbol.asyncIterator]();
|
|
1164
|
+
return await this.next();
|
|
1165
|
+
}
|
|
1166
|
+
const [u, indexKey] = result.value;
|
|
1167
|
+
this.#currentOuterItem.count++;
|
|
1168
|
+
return {
|
|
1169
|
+
done: false,
|
|
1170
|
+
value: [u, [...this.#currentOuterItem.indexKey, ...indexKey]]
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
var FlatMapStream = class FlatMapStream extends QueryStream {
|
|
1175
|
+
#stream;
|
|
1176
|
+
#mapper;
|
|
1177
|
+
#mappedIndexFields;
|
|
1178
|
+
constructor(stream, mapper, mappedIndexFields) {
|
|
1179
|
+
super();
|
|
1180
|
+
this.#stream = stream;
|
|
1181
|
+
this.#mapper = mapper;
|
|
1182
|
+
this.#mappedIndexFields = mappedIndexFields;
|
|
1183
|
+
}
|
|
1184
|
+
iterWithKeys() {
|
|
1185
|
+
const outerStream = this.#stream;
|
|
1186
|
+
const mapper = this.#mapper;
|
|
1187
|
+
const mappedIndexFields = this.#mappedIndexFields;
|
|
1188
|
+
return { [Symbol.asyncIterator]() {
|
|
1189
|
+
return new FlatMapStreamIterator(outerStream, mapper, mappedIndexFields);
|
|
1190
|
+
} };
|
|
1191
|
+
}
|
|
1192
|
+
getOrder() {
|
|
1193
|
+
return this.#stream.getOrder();
|
|
1194
|
+
}
|
|
1195
|
+
getEqualityIndexFilter() {
|
|
1196
|
+
return this.#stream.getEqualityIndexFilter();
|
|
1197
|
+
}
|
|
1198
|
+
getIndexFields() {
|
|
1199
|
+
return [...this.#stream.getIndexFields(), ...this.#mappedIndexFields];
|
|
1200
|
+
}
|
|
1201
|
+
narrow(indexBounds) {
|
|
1202
|
+
const outerLength = this.#stream.getIndexFields().length;
|
|
1203
|
+
const outerLowerBound = indexBounds.lowerBound.slice(0, outerLength);
|
|
1204
|
+
const outerUpperBound = indexBounds.upperBound.slice(0, outerLength);
|
|
1205
|
+
const innerLowerBound = indexBounds.lowerBound.slice(outerLength);
|
|
1206
|
+
const innerUpperBound = indexBounds.upperBound.slice(outerLength);
|
|
1207
|
+
const outerIndexBounds = {
|
|
1208
|
+
lowerBound: outerLowerBound,
|
|
1209
|
+
lowerBoundInclusive: innerLowerBound.length === 0 ? indexBounds.lowerBoundInclusive : true,
|
|
1210
|
+
upperBound: outerUpperBound,
|
|
1211
|
+
upperBoundInclusive: innerUpperBound.length === 0 ? indexBounds.upperBoundInclusive : true
|
|
1212
|
+
};
|
|
1213
|
+
const innerIndexBounds = {
|
|
1214
|
+
lowerBound: innerLowerBound,
|
|
1215
|
+
lowerBoundInclusive: innerLowerBound.length === 0 ? true : indexBounds.lowerBoundInclusive,
|
|
1216
|
+
upperBound: innerUpperBound,
|
|
1217
|
+
upperBoundInclusive: innerUpperBound.length === 0 ? true : indexBounds.upperBoundInclusive
|
|
1218
|
+
};
|
|
1219
|
+
return new FlatMapStream(this.#stream.narrow(outerIndexBounds), async (t) => {
|
|
1220
|
+
return (await this.#mapper(t)).narrow(innerIndexBounds);
|
|
1221
|
+
}, this.#mappedIndexFields);
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
var SingletonStream = class SingletonStream extends QueryStream {
|
|
1225
|
+
#value;
|
|
1226
|
+
#order;
|
|
1227
|
+
#indexFields;
|
|
1228
|
+
#indexKey;
|
|
1229
|
+
#equalityIndexFilter;
|
|
1230
|
+
constructor(value, order, indexFields, indexKey, equalityIndexFilter) {
|
|
1231
|
+
super();
|
|
1232
|
+
this.#value = value;
|
|
1233
|
+
this.#order = order;
|
|
1234
|
+
this.#indexFields = indexFields;
|
|
1235
|
+
this.#indexKey = indexKey;
|
|
1236
|
+
this.#equalityIndexFilter = equalityIndexFilter;
|
|
1237
|
+
if (indexKey.length !== indexFields.length) throw new Error(`indexKey must have the same length as indexFields: ${JSON.stringify(indexKey)} vs ${JSON.stringify(indexFields)}`);
|
|
1238
|
+
}
|
|
1239
|
+
iterWithKeys() {
|
|
1240
|
+
const value = this.#value;
|
|
1241
|
+
const indexKey = this.#indexKey;
|
|
1242
|
+
return { [Symbol.asyncIterator]() {
|
|
1243
|
+
let sent = false;
|
|
1244
|
+
return { async next() {
|
|
1245
|
+
if (sent) return {
|
|
1246
|
+
done: true,
|
|
1247
|
+
value: void 0
|
|
1248
|
+
};
|
|
1249
|
+
sent = true;
|
|
1250
|
+
return {
|
|
1251
|
+
done: false,
|
|
1252
|
+
value: [value, indexKey]
|
|
1253
|
+
};
|
|
1254
|
+
} };
|
|
1255
|
+
} };
|
|
1256
|
+
}
|
|
1257
|
+
getOrder() {
|
|
1258
|
+
return this.#order;
|
|
1259
|
+
}
|
|
1260
|
+
getIndexFields() {
|
|
1261
|
+
return this.#indexFields;
|
|
1262
|
+
}
|
|
1263
|
+
getEqualityIndexFilter() {
|
|
1264
|
+
return this.#equalityIndexFilter;
|
|
1265
|
+
}
|
|
1266
|
+
narrow(indexBounds) {
|
|
1267
|
+
const compareLowerBound = compareKeys({
|
|
1268
|
+
value: indexBounds.lowerBound,
|
|
1269
|
+
kind: indexBounds.lowerBoundInclusive ? "exact" : "successor"
|
|
1270
|
+
}, {
|
|
1271
|
+
value: this.#indexKey,
|
|
1272
|
+
kind: "exact"
|
|
1273
|
+
});
|
|
1274
|
+
const compareUpperBound = compareKeys({
|
|
1275
|
+
value: this.#indexKey,
|
|
1276
|
+
kind: "exact"
|
|
1277
|
+
}, {
|
|
1278
|
+
value: indexBounds.upperBound,
|
|
1279
|
+
kind: indexBounds.upperBoundInclusive ? "exact" : "predecessor"
|
|
1280
|
+
});
|
|
1281
|
+
if (compareLowerBound <= 0 && compareUpperBound <= 0) return new SingletonStream(this.#value, this.#order, this.#indexFields, this.#indexKey, this.#equalityIndexFilter);
|
|
1282
|
+
return new EmptyStream(this.#order, this.#indexFields);
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
/**
|
|
1286
|
+
* This is a completely empty stream that yields no values, and in particular
|
|
1287
|
+
* does not count towards maxScan.
|
|
1288
|
+
* Compare to SingletonStream(null, ...), which yields no values but does count
|
|
1289
|
+
* towards maxScan.
|
|
1290
|
+
*/
|
|
1291
|
+
var EmptyStream = class extends QueryStream {
|
|
1292
|
+
#order;
|
|
1293
|
+
#indexFields;
|
|
1294
|
+
constructor(order, indexFields) {
|
|
1295
|
+
super();
|
|
1296
|
+
this.#order = order;
|
|
1297
|
+
this.#indexFields = indexFields;
|
|
1298
|
+
}
|
|
1299
|
+
iterWithKeys() {
|
|
1300
|
+
return { [Symbol.asyncIterator]() {
|
|
1301
|
+
return { async next() {
|
|
1302
|
+
return {
|
|
1303
|
+
done: true,
|
|
1304
|
+
value: void 0
|
|
1305
|
+
};
|
|
1306
|
+
} };
|
|
1307
|
+
} };
|
|
1308
|
+
}
|
|
1309
|
+
getOrder() {
|
|
1310
|
+
return this.#order;
|
|
1311
|
+
}
|
|
1312
|
+
getIndexFields() {
|
|
1313
|
+
return this.#indexFields;
|
|
1314
|
+
}
|
|
1315
|
+
getEqualityIndexFilter() {
|
|
1316
|
+
return [];
|
|
1317
|
+
}
|
|
1318
|
+
narrow(_indexBounds) {
|
|
1319
|
+
return this;
|
|
1320
|
+
}
|
|
1321
|
+
};
|
|
1322
|
+
function normalizeIndexFields(indexFields) {
|
|
1323
|
+
if (!indexFields.includes("_creationTime") && (indexFields.length !== 1 || indexFields[0] !== "_id")) indexFields.push("_creationTime");
|
|
1324
|
+
if (!indexFields.includes("_id")) indexFields.push("_id");
|
|
1325
|
+
}
|
|
1326
|
+
function* getOrderingIndexFields(stream) {
|
|
1327
|
+
const streamEqualityIndexLength = stream.getEqualityIndexFilter().length;
|
|
1328
|
+
const streamIndexFields = stream.getIndexFields();
|
|
1329
|
+
for (let i = 0; i <= streamEqualityIndexLength; i++) yield streamIndexFields.slice(i);
|
|
1330
|
+
}
|
|
1331
|
+
var OrderByStream = class OrderByStream extends QueryStream {
|
|
1332
|
+
#staticFilter;
|
|
1333
|
+
#stream;
|
|
1334
|
+
#indexFields;
|
|
1335
|
+
constructor(stream, indexFields) {
|
|
1336
|
+
super();
|
|
1337
|
+
this.#stream = stream;
|
|
1338
|
+
this.#indexFields = indexFields;
|
|
1339
|
+
normalizeIndexFields(this.#indexFields);
|
|
1340
|
+
const streamIndexFields = stream.getIndexFields();
|
|
1341
|
+
if (!Array.from(getOrderingIndexFields(stream)).some((orderingIndexFields) => equalIndexFields(orderingIndexFields, indexFields))) throw new Error(`indexFields must be some sequence of fields the stream is ordered by: ${JSON.stringify(indexFields)}, ${JSON.stringify(streamIndexFields)} (${stream.getEqualityIndexFilter().length} equality fields)`);
|
|
1342
|
+
this.#staticFilter = stream.getEqualityIndexFilter().slice(0, streamIndexFields.length - indexFields.length);
|
|
1343
|
+
}
|
|
1344
|
+
getOrder() {
|
|
1345
|
+
return this.#stream.getOrder();
|
|
1346
|
+
}
|
|
1347
|
+
getEqualityIndexFilter() {
|
|
1348
|
+
return this.#stream.getEqualityIndexFilter().slice(this.#staticFilter.length);
|
|
1349
|
+
}
|
|
1350
|
+
getIndexFields() {
|
|
1351
|
+
return this.#indexFields;
|
|
1352
|
+
}
|
|
1353
|
+
iterWithKeys() {
|
|
1354
|
+
const iterable = this.#stream.iterWithKeys();
|
|
1355
|
+
const staticFilter = this.#staticFilter;
|
|
1356
|
+
return { [Symbol.asyncIterator]() {
|
|
1357
|
+
const iterator = iterable[Symbol.asyncIterator]();
|
|
1358
|
+
return { async next() {
|
|
1359
|
+
const result = await iterator.next();
|
|
1360
|
+
if (result.done) return result;
|
|
1361
|
+
const [doc, indexKey] = result.value;
|
|
1362
|
+
return {
|
|
1363
|
+
done: false,
|
|
1364
|
+
value: [doc, indexKey.slice(staticFilter.length)]
|
|
1365
|
+
};
|
|
1366
|
+
} };
|
|
1367
|
+
} };
|
|
1368
|
+
}
|
|
1369
|
+
narrow(indexBounds) {
|
|
1370
|
+
return new OrderByStream(this.#stream.narrow({
|
|
1371
|
+
lowerBound: [...this.#staticFilter, ...indexBounds.lowerBound],
|
|
1372
|
+
lowerBoundInclusive: indexBounds.lowerBoundInclusive,
|
|
1373
|
+
upperBound: [...this.#staticFilter, ...indexBounds.upperBound],
|
|
1374
|
+
upperBoundInclusive: indexBounds.upperBoundInclusive
|
|
1375
|
+
}), this.#indexFields);
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
var DistinctStream = class DistinctStream extends QueryStream {
|
|
1379
|
+
#distinctIndexFieldsLength;
|
|
1380
|
+
#stream;
|
|
1381
|
+
#distinctIndexFields;
|
|
1382
|
+
constructor(stream, distinctIndexFields) {
|
|
1383
|
+
super();
|
|
1384
|
+
this.#stream = stream;
|
|
1385
|
+
this.#distinctIndexFields = distinctIndexFields;
|
|
1386
|
+
let distinctIndexFieldsLength;
|
|
1387
|
+
for (const orderingIndexFields of getOrderingIndexFields(stream)) if (equalIndexFields(orderingIndexFields.slice(0, distinctIndexFields.length), distinctIndexFields)) {
|
|
1388
|
+
distinctIndexFieldsLength = stream.getIndexFields().length - orderingIndexFields.length + distinctIndexFields.length;
|
|
1389
|
+
break;
|
|
1390
|
+
}
|
|
1391
|
+
if (distinctIndexFieldsLength === void 0) throw new Error(`distinctIndexFields must be a prefix of the stream's ordering index fields: ${JSON.stringify(distinctIndexFields)}, ${JSON.stringify(stream.getIndexFields())} (${stream.getEqualityIndexFilter().length} equality fields)`);
|
|
1392
|
+
this.#distinctIndexFieldsLength = distinctIndexFieldsLength;
|
|
1393
|
+
}
|
|
1394
|
+
iterWithKeys() {
|
|
1395
|
+
const stream = this.#stream;
|
|
1396
|
+
const distinctIndexFieldsLength = this.#distinctIndexFieldsLength;
|
|
1397
|
+
return { [Symbol.asyncIterator]() {
|
|
1398
|
+
let currentStream = stream;
|
|
1399
|
+
let currentIterator = currentStream.iterWithKeys()[Symbol.asyncIterator]();
|
|
1400
|
+
return { async next() {
|
|
1401
|
+
const result = await currentIterator.next();
|
|
1402
|
+
if (result.done) return {
|
|
1403
|
+
done: true,
|
|
1404
|
+
value: void 0
|
|
1405
|
+
};
|
|
1406
|
+
const [doc, indexKey] = result.value;
|
|
1407
|
+
if (doc === null) return {
|
|
1408
|
+
done: false,
|
|
1409
|
+
value: [null, indexKey]
|
|
1410
|
+
};
|
|
1411
|
+
const distinctIndexKey = indexKey.slice(0, distinctIndexFieldsLength);
|
|
1412
|
+
if (stream.getOrder() === "asc") currentStream = currentStream.narrow({
|
|
1413
|
+
lowerBound: distinctIndexKey,
|
|
1414
|
+
lowerBoundInclusive: false,
|
|
1415
|
+
upperBound: [],
|
|
1416
|
+
upperBoundInclusive: true
|
|
1417
|
+
});
|
|
1418
|
+
else currentStream = currentStream.narrow({
|
|
1419
|
+
lowerBound: [],
|
|
1420
|
+
lowerBoundInclusive: true,
|
|
1421
|
+
upperBound: distinctIndexKey,
|
|
1422
|
+
upperBoundInclusive: false
|
|
1423
|
+
});
|
|
1424
|
+
currentIterator = currentStream.iterWithKeys()[Symbol.asyncIterator]();
|
|
1425
|
+
return result;
|
|
1426
|
+
} };
|
|
1427
|
+
} };
|
|
1428
|
+
}
|
|
1429
|
+
narrow(indexBounds) {
|
|
1430
|
+
const indexBoundsPrefix = {
|
|
1431
|
+
...indexBounds,
|
|
1432
|
+
lowerBound: indexBounds.lowerBound.slice(0, this.#distinctIndexFieldsLength),
|
|
1433
|
+
upperBound: indexBounds.upperBound.slice(0, this.#distinctIndexFieldsLength)
|
|
1434
|
+
};
|
|
1435
|
+
return new DistinctStream(this.#stream.narrow(indexBoundsPrefix), this.#distinctIndexFields);
|
|
1436
|
+
}
|
|
1437
|
+
getOrder() {
|
|
1438
|
+
return this.#stream.getOrder();
|
|
1439
|
+
}
|
|
1440
|
+
getIndexFields() {
|
|
1441
|
+
return this.#stream.getIndexFields();
|
|
1442
|
+
}
|
|
1443
|
+
getEqualityIndexFilter() {
|
|
1444
|
+
return this.#stream.getEqualityIndexFilter();
|
|
1445
|
+
}
|
|
1446
|
+
};
|
|
1447
|
+
function equalIndexFields(indexFields1, indexFields2) {
|
|
1448
|
+
if (indexFields1.length !== indexFields2.length) return false;
|
|
1449
|
+
for (let i = 0; i < indexFields1.length; i++) if (indexFields1[i] !== indexFields2[i]) return false;
|
|
1450
|
+
return true;
|
|
1451
|
+
}
|
|
1452
|
+
function getValueAtIndex(v, index) {
|
|
1453
|
+
if (index >= v.length) return;
|
|
1454
|
+
return {
|
|
1455
|
+
kind: "found",
|
|
1456
|
+
value: v[index]
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
function compareDanglingSuffix(shorterKeyKind, longerKeyKind, shorterKey, longerKey) {
|
|
1460
|
+
if (shorterKeyKind === "exact" && longerKeyKind === "exact") throw new Error(`Exact keys are not the same length: ${JSON.stringify(shorterKey.value)}, ${JSON.stringify(longerKey.value)}`);
|
|
1461
|
+
if (shorterKeyKind === "exact") throw new Error(`Exact key is shorter than prefix: ${JSON.stringify(shorterKey.value)}, ${JSON.stringify(longerKey.value)}`);
|
|
1462
|
+
if (shorterKeyKind === "predecessor" && longerKeyKind === "successor") return -1;
|
|
1463
|
+
if (shorterKeyKind === "successor" && longerKeyKind === "predecessor") return 1;
|
|
1464
|
+
if (shorterKeyKind === "predecessor" && longerKeyKind === "predecessor") return -1;
|
|
1465
|
+
if (shorterKeyKind === "successor" && longerKeyKind === "successor") return 1;
|
|
1466
|
+
if (shorterKeyKind === "predecessor" && longerKeyKind === "exact") return -1;
|
|
1467
|
+
if (shorterKeyKind === "successor" && longerKeyKind === "exact") return 1;
|
|
1468
|
+
throw new Error(`Unexpected key kinds: ${shorterKeyKind}, ${longerKeyKind}`);
|
|
1469
|
+
}
|
|
1470
|
+
function compareKeys(key1, key2) {
|
|
1471
|
+
let i = 0;
|
|
1472
|
+
while (i < Math.max(key1.value.length, key2.value.length)) {
|
|
1473
|
+
const v1 = getValueAtIndex(key1.value, i);
|
|
1474
|
+
const v2 = getValueAtIndex(key2.value, i);
|
|
1475
|
+
if (v1 === void 0) return compareDanglingSuffix(key1.kind, key2.kind, key1, key2);
|
|
1476
|
+
if (v2 === void 0) return -1 * compareDanglingSuffix(key2.kind, key1.kind, key2, key1);
|
|
1477
|
+
const result = compareValues(v1.value, v2.value);
|
|
1478
|
+
if (result !== 0) return result;
|
|
1479
|
+
i++;
|
|
1480
|
+
}
|
|
1481
|
+
if (key1.kind === key2.kind) return 0;
|
|
1482
|
+
if (key1.kind === "exact") {
|
|
1483
|
+
if (key2.kind === "successor") return -1;
|
|
1484
|
+
return 1;
|
|
1485
|
+
}
|
|
1486
|
+
if (key1.kind === "predecessor") return -1;
|
|
1487
|
+
if (key1.kind === "successor") return 1;
|
|
1488
|
+
throw new Error(`Unexpected key kind: ${key1.kind}`);
|
|
1489
|
+
}
|
|
1490
|
+
function serializeCursor(key) {
|
|
1491
|
+
return JSON.stringify(convexToJson(key.map((v) => v === void 0 ? "undefined" : typeof v === "string" && v.endsWith("undefined") ? `_${v}` : v)));
|
|
1492
|
+
}
|
|
1493
|
+
function deserializeCursor(cursor) {
|
|
1494
|
+
let parsed;
|
|
1495
|
+
try {
|
|
1496
|
+
parsed = JSON.parse(cursor);
|
|
1497
|
+
} catch {
|
|
1498
|
+
throw new Error("Invalid pagination cursor for stream-backed pagination. Use the continueCursor returned by the same findMany query shape.");
|
|
1499
|
+
}
|
|
1500
|
+
return jsonToConvex(parsed).map((v) => {
|
|
1501
|
+
if (typeof v === "string") {
|
|
1502
|
+
if (v === "undefined") return;
|
|
1503
|
+
if (v.endsWith("undefined")) return v.slice(1);
|
|
1504
|
+
}
|
|
1505
|
+
return v;
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
//#endregion
|
|
1510
|
+
//#region src/orm/query-context.ts
|
|
1511
|
+
async function getByIdWithOrmQueryFallback(ctx, tableName, id) {
|
|
1512
|
+
const ormTableQuery = ctx.orm?.query?.[tableName];
|
|
1513
|
+
if (ormTableQuery?.findFirst) return await ormTableQuery.findFirst({ where: { id } });
|
|
1514
|
+
return await ctx.db.get(id);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
//#endregion
|
|
1518
|
+
export { ne as A, inArray as C, like as D, isNull as E, notLike as F, or as I, startsWith as L, notBetween as M, notIlike as N, lt as O, notInArray as P, ilike as S, isNotNull as T, endsWith as _, mergedStream as a, gt as b, isUnsetToken as c, arrayContained as d, arrayContains as f, contains as g, column as h, getIndexFields as i, not as j, lte as k, unsetToken as l, between as m, EmptyStream as n, stream as o, arrayOverlaps as p, QueryStream as r, streamIndexRange as s, getByIdWithOrmQueryFallback as t, and as u, eq as v, isFieldReference as w, gte as x, fieldRef as y };
|