convex-ents 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/deletion.d.ts +4 -2
- package/dist/deletion.js +111 -1094
- package/dist/deletion.js.map +1 -1
- package/dist/index.js +111 -50
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/deletion.js
CHANGED
|
@@ -28,1062 +28,14 @@ var import_values = require("convex/values");
|
|
|
28
28
|
|
|
29
29
|
// src/writer.ts
|
|
30
30
|
var import_server = require("convex/server");
|
|
31
|
-
var WriterImplBase = class _WriterImplBase {
|
|
32
|
-
constructor(ctx, entDefinitions, table) {
|
|
33
|
-
this.ctx = ctx;
|
|
34
|
-
this.entDefinitions = entDefinitions;
|
|
35
|
-
this.table = table;
|
|
36
|
-
}
|
|
37
|
-
async deleteId(id, behavior) {
|
|
38
|
-
await this.checkReadAndWriteRule("delete", id, void 0);
|
|
39
|
-
const deletionConfig = getDeletionConfig(this.entDefinitions, this.table);
|
|
40
|
-
const isDeletingSoftly = behavior !== "hard" && deletionConfig !== void 0 && (deletionConfig.type === "soft" || deletionConfig.type === "scheduled");
|
|
41
|
-
if (behavior === "soft" && !isDeletingSoftly) {
|
|
42
|
-
throw new Error(
|
|
43
|
-
`Cannot soft delete document with ID "${id}" in table "${this.table}" because it does not have an "allowSoft", "soft" or "scheduled" deletion behavior configured.`
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
const edges = {};
|
|
47
|
-
await Promise.all(
|
|
48
|
-
Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map(
|
|
49
|
-
async (edgeDefinition) => {
|
|
50
|
-
const key = edgeDefinition.name;
|
|
51
|
-
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "ref" || edgeDefinition.cardinality === "multiple" && edgeDefinition.type === "field") {
|
|
52
|
-
if (!isDeletingSoftly || edgeDefinition.deletion === "soft") {
|
|
53
|
-
const remove = (await this.ctx.db.query(edgeDefinition.to).withIndex(
|
|
54
|
-
edgeDefinition.ref,
|
|
55
|
-
(q) => q.eq(edgeDefinition.ref, id)
|
|
56
|
-
).collect()).map((doc) => doc._id);
|
|
57
|
-
edges[key] = { remove };
|
|
58
|
-
}
|
|
59
|
-
} else if (edgeDefinition.cardinality === "multiple") {
|
|
60
|
-
if (!isDeletingSoftly) {
|
|
61
|
-
const removeEdges = (await this.ctx.db.query(edgeDefinition.table).withIndex(
|
|
62
|
-
edgeDefinition.field,
|
|
63
|
-
(q) => q.eq(edgeDefinition.field, id)
|
|
64
|
-
).collect()).concat(
|
|
65
|
-
edgeDefinition.symmetric ? await this.ctx.db.query(edgeDefinition.table).withIndex(
|
|
66
|
-
edgeDefinition.ref,
|
|
67
|
-
(q) => q.eq(edgeDefinition.ref, id)
|
|
68
|
-
).collect() : []
|
|
69
|
-
).map((doc) => doc._id);
|
|
70
|
-
edges[key] = { removeEdges };
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
)
|
|
75
|
-
);
|
|
76
|
-
const deletionTime = +/* @__PURE__ */ new Date();
|
|
77
|
-
if (isDeletingSoftly) {
|
|
78
|
-
await this.ctx.db.patch(id, { deletionTime });
|
|
79
|
-
} else {
|
|
80
|
-
try {
|
|
81
|
-
await this.ctx.db.delete(id);
|
|
82
|
-
} catch (e) {
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
await this.writeEdges(id, edges, isDeletingSoftly);
|
|
86
|
-
if (deletionConfig !== void 0 && deletionConfig.type === "scheduled") {
|
|
87
|
-
const fnRef = this.ctx.scheduledDelete ?? (0, import_server.makeFunctionReference)(
|
|
88
|
-
"functions:scheduledDelete"
|
|
89
|
-
);
|
|
90
|
-
await this.ctx.scheduler.runAfter(deletionConfig.delayMs ?? 0, fnRef, {
|
|
91
|
-
origin: {
|
|
92
|
-
id,
|
|
93
|
-
table: this.table,
|
|
94
|
-
deletionTime
|
|
95
|
-
},
|
|
96
|
-
inProgress: false,
|
|
97
|
-
stack: []
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
return id;
|
|
101
|
-
}
|
|
102
|
-
async deletedIdIn(id, table, cascadingSoft) {
|
|
103
|
-
await new _WriterImplBase(this.ctx, this.entDefinitions, table).deleteId(
|
|
104
|
-
id,
|
|
105
|
-
cascadingSoft ? "soft" : "hard"
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
async writeEdges(docId, changes, deleteSoftly) {
|
|
109
|
-
await Promise.all(
|
|
110
|
-
Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map(
|
|
111
|
-
async (edgeDefinition) => {
|
|
112
|
-
const idOrIds = changes[edgeDefinition.name];
|
|
113
|
-
if (idOrIds === void 0) {
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "ref" || edgeDefinition.cardinality === "multiple" && edgeDefinition.type === "field") {
|
|
117
|
-
if (idOrIds.remove !== void 0 && idOrIds.remove.length > 0) {
|
|
118
|
-
await Promise.all(
|
|
119
|
-
idOrIds.remove.map(
|
|
120
|
-
(id) => this.deletedIdIn(
|
|
121
|
-
id,
|
|
122
|
-
edgeDefinition.to,
|
|
123
|
-
(deleteSoftly ?? false) && edgeDefinition.deletion === "soft"
|
|
124
|
-
)
|
|
125
|
-
)
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
if (idOrIds.add !== void 0 && idOrIds.add.length > 0) {
|
|
129
|
-
await Promise.all(
|
|
130
|
-
idOrIds.add.map(
|
|
131
|
-
async (id) => this.ctx.db.patch(id, {
|
|
132
|
-
[edgeDefinition.ref]: docId
|
|
133
|
-
})
|
|
134
|
-
)
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
} else if (edgeDefinition.cardinality === "multiple") {
|
|
138
|
-
if ((idOrIds.removeEdges ?? []).length > 0) {
|
|
139
|
-
await Promise.all(
|
|
140
|
-
idOrIds.removeEdges.map(async (id) => {
|
|
141
|
-
try {
|
|
142
|
-
await this.ctx.db.delete(id);
|
|
143
|
-
} catch (e) {
|
|
144
|
-
}
|
|
145
|
-
})
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
if (idOrIds.add !== void 0) {
|
|
149
|
-
await Promise.all(
|
|
150
|
-
idOrIds.add.map(async (id) => {
|
|
151
|
-
await this.ctx.db.insert(edgeDefinition.table, {
|
|
152
|
-
[edgeDefinition.field]: docId,
|
|
153
|
-
[edgeDefinition.ref]: id
|
|
154
|
-
});
|
|
155
|
-
if (edgeDefinition.symmetric) {
|
|
156
|
-
await this.ctx.db.insert(edgeDefinition.table, {
|
|
157
|
-
[edgeDefinition.field]: id,
|
|
158
|
-
[edgeDefinition.ref]: docId
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
})
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
)
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
async checkUniqueness(value, id) {
|
|
170
|
-
await Promise.all(
|
|
171
|
-
Object.values(
|
|
172
|
-
this.entDefinitions[this.table].fields
|
|
173
|
-
).map(async (fieldDefinition) => {
|
|
174
|
-
if (fieldDefinition.unique) {
|
|
175
|
-
const key = fieldDefinition.name;
|
|
176
|
-
const fieldValue = value[key];
|
|
177
|
-
const existing = await this.ctx.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique();
|
|
178
|
-
if (existing !== null && (id === void 0 || existing._id !== id)) {
|
|
179
|
-
throw new Error(
|
|
180
|
-
`In table "${this.table}" cannot create a duplicate document with field "${key}" of value \`${fieldValue}\`, existing document with ID "${existing._id}" already has it.`
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
})
|
|
185
|
-
);
|
|
186
|
-
await Promise.all(
|
|
187
|
-
Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map(
|
|
188
|
-
async (edgeDefinition) => {
|
|
189
|
-
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "field" && edgeDefinition.unique) {
|
|
190
|
-
const key = edgeDefinition.field;
|
|
191
|
-
if (value[key] === void 0) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
const existing = await this.ctx.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique();
|
|
195
|
-
if (existing !== null && (id === void 0 || existing._id !== id)) {
|
|
196
|
-
throw new Error(
|
|
197
|
-
`In table "${this.table}" cannot create a duplicate 1:1 edge "${edgeDefinition.name}" to ID "${value[key]}", existing document with ID "${existing._id}" already has it.`
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
)
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
fieldsOnly(value) {
|
|
206
|
-
const fields = {};
|
|
207
|
-
Object.keys(value).forEach((key) => {
|
|
208
|
-
const edgeDefinition = getEdgeDefinitions(
|
|
209
|
-
this.entDefinitions,
|
|
210
|
-
this.table
|
|
211
|
-
)[key];
|
|
212
|
-
if (edgeDefinition === void 0) {
|
|
213
|
-
fields[key] = value[key];
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
return fields;
|
|
217
|
-
}
|
|
218
|
-
async checkReadAndWriteRule(operation, id, value) {
|
|
219
|
-
if (id !== void 0) {
|
|
220
|
-
const readPolicy = getReadRule(this.entDefinitions, this.table);
|
|
221
|
-
if (readPolicy !== void 0) {
|
|
222
|
-
const doc = await this.ctx.db.get(id);
|
|
223
|
-
if (doc === null) {
|
|
224
|
-
throw new Error(
|
|
225
|
-
`Cannot update document with ID "${id}" in table "${this.table} because it does not exist"`
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
const decision2 = await readPolicy(doc);
|
|
229
|
-
if (!decision2) {
|
|
230
|
-
throw new Error(
|
|
231
|
-
`Cannot update document with ID "${id}" from table "${this.table}"`
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
const writePolicy = getWriteRule(this.entDefinitions, this.table);
|
|
237
|
-
if (writePolicy === void 0) {
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
const ent = id === void 0 ? void 0 : entWrapper(
|
|
241
|
-
await this.ctx.db.get(id),
|
|
242
|
-
this.ctx,
|
|
243
|
-
this.entDefinitions,
|
|
244
|
-
this.table
|
|
245
|
-
);
|
|
246
|
-
const { _id, _creationTime, ...safeValue } = value ?? {};
|
|
247
|
-
const decision = await writePolicy({
|
|
248
|
-
operation,
|
|
249
|
-
ent,
|
|
250
|
-
value: value !== void 0 ? safeValue : void 0
|
|
251
|
-
});
|
|
252
|
-
if (!decision) {
|
|
253
|
-
if (id === void 0) {
|
|
254
|
-
throw new Error(
|
|
255
|
-
`Cannot insert into table "${this.table}": \`${JSON.stringify(
|
|
256
|
-
value
|
|
257
|
-
)}\``
|
|
258
|
-
);
|
|
259
|
-
} else if (value === void 0) {
|
|
260
|
-
throw new Error(
|
|
261
|
-
`Cannot delete from table "${this.table}" with ID "${id}"`
|
|
262
|
-
);
|
|
263
|
-
} else {
|
|
264
|
-
throw new Error(
|
|
265
|
-
`Cannot update document with ID "${id}" in table "${this.table}" with: \`${JSON.stringify(value)}\``
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
31
|
|
|
272
32
|
// src/functions.ts
|
|
273
|
-
var PromiseQueryOrNullImpl = class _PromiseQueryOrNullImpl extends Promise {
|
|
274
|
-
constructor(ctx, entDefinitions, table, retrieve) {
|
|
275
|
-
super(() => {
|
|
276
|
-
});
|
|
277
|
-
this.ctx = ctx;
|
|
278
|
-
this.entDefinitions = entDefinitions;
|
|
279
|
-
this.table = table;
|
|
280
|
-
this.retrieve = retrieve;
|
|
281
|
-
}
|
|
282
|
-
filter(predicate) {
|
|
283
|
-
return new _PromiseQueryOrNullImpl(
|
|
284
|
-
this.ctx,
|
|
285
|
-
this.entDefinitions,
|
|
286
|
-
this.table,
|
|
287
|
-
async () => {
|
|
288
|
-
const query = await this.retrieve();
|
|
289
|
-
if (query === null) {
|
|
290
|
-
return null;
|
|
291
|
-
}
|
|
292
|
-
return query.filter(predicate);
|
|
293
|
-
}
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
map(callbackFn) {
|
|
297
|
-
return new PromiseArrayImpl(async () => {
|
|
298
|
-
const array = await this;
|
|
299
|
-
if (array === null) {
|
|
300
|
-
return null;
|
|
301
|
-
}
|
|
302
|
-
return await Promise.all(array.map(callbackFn));
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
order(order, indexName) {
|
|
306
|
-
return new _PromiseQueryOrNullImpl(
|
|
307
|
-
this.ctx,
|
|
308
|
-
this.entDefinitions,
|
|
309
|
-
this.table,
|
|
310
|
-
async () => {
|
|
311
|
-
const query = await this.retrieve();
|
|
312
|
-
if (query === null) {
|
|
313
|
-
return null;
|
|
314
|
-
}
|
|
315
|
-
if (indexName !== void 0) {
|
|
316
|
-
return query.withIndex(indexName).order(order);
|
|
317
|
-
}
|
|
318
|
-
return query.order(order);
|
|
319
|
-
}
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
|
-
paginate(paginationOpts) {
|
|
323
|
-
return new PromisePaginationResultOrNullImpl(
|
|
324
|
-
this.ctx,
|
|
325
|
-
this.entDefinitions,
|
|
326
|
-
this.table,
|
|
327
|
-
this.retrieve,
|
|
328
|
-
paginationOpts
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
take(n) {
|
|
332
|
-
return new PromiseEntsOrNullImpl(
|
|
333
|
-
this.ctx,
|
|
334
|
-
this.entDefinitions,
|
|
335
|
-
this.table,
|
|
336
|
-
async () => {
|
|
337
|
-
return await this._take(n);
|
|
338
|
-
},
|
|
339
|
-
false
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
first() {
|
|
343
|
-
return new PromiseEntOrNullImpl(
|
|
344
|
-
this.ctx,
|
|
345
|
-
this.entDefinitions,
|
|
346
|
-
this.table,
|
|
347
|
-
async () => {
|
|
348
|
-
const docs = await this._take(1);
|
|
349
|
-
if (docs === null) {
|
|
350
|
-
return nullRetriever;
|
|
351
|
-
}
|
|
352
|
-
const [doc] = docs;
|
|
353
|
-
return loadedRetriever(doc);
|
|
354
|
-
},
|
|
355
|
-
false
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
firstX() {
|
|
359
|
-
return new PromiseEntWriterImpl(
|
|
360
|
-
this.ctx,
|
|
361
|
-
this.entDefinitions,
|
|
362
|
-
this.table,
|
|
363
|
-
async () => {
|
|
364
|
-
const docs = await this._take(1);
|
|
365
|
-
if (docs === null) {
|
|
366
|
-
return nullRetriever;
|
|
367
|
-
}
|
|
368
|
-
const [doc] = docs;
|
|
369
|
-
if (doc === void 0) {
|
|
370
|
-
throw new Error("Query returned no documents");
|
|
371
|
-
}
|
|
372
|
-
return loadedRetriever(doc);
|
|
373
|
-
},
|
|
374
|
-
false
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
unique() {
|
|
378
|
-
return new PromiseEntOrNullImpl(
|
|
379
|
-
this.ctx,
|
|
380
|
-
this.entDefinitions,
|
|
381
|
-
this.table,
|
|
382
|
-
async () => {
|
|
383
|
-
const docs = await this._take(2);
|
|
384
|
-
if (docs === null) {
|
|
385
|
-
return nullRetriever;
|
|
386
|
-
}
|
|
387
|
-
if (docs.length === 0) {
|
|
388
|
-
return nullRetriever;
|
|
389
|
-
}
|
|
390
|
-
if (docs.length === 2) {
|
|
391
|
-
throw new Error("unique() query returned more than one result");
|
|
392
|
-
}
|
|
393
|
-
const [doc] = docs;
|
|
394
|
-
return loadedRetriever(doc);
|
|
395
|
-
},
|
|
396
|
-
false
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
uniqueX() {
|
|
400
|
-
return new PromiseEntWriterImpl(
|
|
401
|
-
this.ctx,
|
|
402
|
-
this.entDefinitions,
|
|
403
|
-
this.table,
|
|
404
|
-
async () => {
|
|
405
|
-
const docs = await this._take(2);
|
|
406
|
-
if (docs === null) {
|
|
407
|
-
return nullRetriever;
|
|
408
|
-
}
|
|
409
|
-
if (docs.length === 0) {
|
|
410
|
-
throw new Error("Query returned no documents");
|
|
411
|
-
}
|
|
412
|
-
if (docs.length === 2) {
|
|
413
|
-
throw new Error("unique() query returned more than one result");
|
|
414
|
-
}
|
|
415
|
-
const [doc] = docs;
|
|
416
|
-
return loadedRetriever(doc);
|
|
417
|
-
},
|
|
418
|
-
true
|
|
419
|
-
);
|
|
420
|
-
}
|
|
421
|
-
async docs() {
|
|
422
|
-
const query = await this.retrieve();
|
|
423
|
-
if (query === null) {
|
|
424
|
-
return null;
|
|
425
|
-
}
|
|
426
|
-
const docs = await query.collect();
|
|
427
|
-
return filterByReadRule(
|
|
428
|
-
this.ctx,
|
|
429
|
-
this.entDefinitions,
|
|
430
|
-
this.table,
|
|
431
|
-
docs,
|
|
432
|
-
false
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
then(onfulfilled, onrejected) {
|
|
436
|
-
return this.docs().then(
|
|
437
|
-
(documents) => documents === null ? null : documents.map(
|
|
438
|
-
(doc) => entWrapper(doc, this.ctx, this.entDefinitions, this.table)
|
|
439
|
-
)
|
|
440
|
-
).then(onfulfilled, onrejected);
|
|
441
|
-
}
|
|
442
|
-
async _take(n) {
|
|
443
|
-
const query = await this.retrieve();
|
|
444
|
-
if (query === null) {
|
|
445
|
-
return null;
|
|
446
|
-
}
|
|
447
|
-
const readPolicy = getReadRule(this.entDefinitions, this.table);
|
|
448
|
-
if (readPolicy === void 0) {
|
|
449
|
-
return await query.take(n);
|
|
450
|
-
}
|
|
451
|
-
let numItems = n;
|
|
452
|
-
const docs = [];
|
|
453
|
-
let hasMore = true;
|
|
454
|
-
const iterator = query[Symbol.asyncIterator]();
|
|
455
|
-
while (hasMore && docs.length < n) {
|
|
456
|
-
const page = [];
|
|
457
|
-
for (let i = 0; i < numItems; i++) {
|
|
458
|
-
const { done, value } = await iterator.next();
|
|
459
|
-
if (done) {
|
|
460
|
-
hasMore = false;
|
|
461
|
-
break;
|
|
462
|
-
}
|
|
463
|
-
page.push(value);
|
|
464
|
-
}
|
|
465
|
-
docs.push(
|
|
466
|
-
...(await filterByReadRule(
|
|
467
|
-
this.ctx,
|
|
468
|
-
this.entDefinitions,
|
|
469
|
-
this.table,
|
|
470
|
-
page,
|
|
471
|
-
false
|
|
472
|
-
)).slice(0, n - docs.length)
|
|
473
|
-
);
|
|
474
|
-
numItems = Math.min(64, numItems * 2);
|
|
475
|
-
}
|
|
476
|
-
return docs;
|
|
477
|
-
}
|
|
478
|
-
};
|
|
479
|
-
var PromisePaginationResultOrNullImpl = class extends Promise {
|
|
480
|
-
constructor(ctx, entDefinitions, table, retrieve, paginationOpts) {
|
|
481
|
-
super(() => {
|
|
482
|
-
});
|
|
483
|
-
this.ctx = ctx;
|
|
484
|
-
this.entDefinitions = entDefinitions;
|
|
485
|
-
this.table = table;
|
|
486
|
-
this.retrieve = retrieve;
|
|
487
|
-
this.paginationOpts = paginationOpts;
|
|
488
|
-
}
|
|
489
|
-
async map(callbackFn) {
|
|
490
|
-
const result = await this;
|
|
491
|
-
if (result === null) {
|
|
492
|
-
return null;
|
|
493
|
-
}
|
|
494
|
-
return {
|
|
495
|
-
...result,
|
|
496
|
-
page: await Promise.all(result.page.map(callbackFn))
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
async docs() {
|
|
500
|
-
const query = await this.retrieve();
|
|
501
|
-
if (query === null) {
|
|
502
|
-
return null;
|
|
503
|
-
}
|
|
504
|
-
const result = await query.paginate(this.paginationOpts);
|
|
505
|
-
return {
|
|
506
|
-
...result,
|
|
507
|
-
page: await filterByReadRule(
|
|
508
|
-
this.ctx,
|
|
509
|
-
this.entDefinitions,
|
|
510
|
-
this.table,
|
|
511
|
-
result.page,
|
|
512
|
-
false
|
|
513
|
-
)
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
then(onfulfilled, onrejected) {
|
|
517
|
-
return this.docs().then(
|
|
518
|
-
(result) => result === null ? null : {
|
|
519
|
-
...result,
|
|
520
|
-
page: result.page.map(
|
|
521
|
-
(doc) => entWrapper(doc, this.ctx, this.entDefinitions, this.table)
|
|
522
|
-
)
|
|
523
|
-
}
|
|
524
|
-
).then(onfulfilled, onrejected);
|
|
525
|
-
}
|
|
526
|
-
};
|
|
527
|
-
var PromiseEntsOrNullImpl = class extends Promise {
|
|
528
|
-
constructor(ctx, entDefinitions, table, retrieve, throwIfNull) {
|
|
529
|
-
super(() => {
|
|
530
|
-
});
|
|
531
|
-
this.ctx = ctx;
|
|
532
|
-
this.entDefinitions = entDefinitions;
|
|
533
|
-
this.table = table;
|
|
534
|
-
this.retrieve = retrieve;
|
|
535
|
-
this.throwIfNull = throwIfNull;
|
|
536
|
-
}
|
|
537
|
-
map(callbackFn) {
|
|
538
|
-
return new PromiseArrayImpl(async () => {
|
|
539
|
-
const array = await this;
|
|
540
|
-
if (array === null) {
|
|
541
|
-
return null;
|
|
542
|
-
}
|
|
543
|
-
return await Promise.all(array.map(callbackFn));
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
first() {
|
|
547
|
-
return new PromiseEntOrNullImpl(
|
|
548
|
-
this.ctx,
|
|
549
|
-
this.entDefinitions,
|
|
550
|
-
this.table,
|
|
551
|
-
async () => {
|
|
552
|
-
const docs = await this.retrieve();
|
|
553
|
-
if (docs === null) {
|
|
554
|
-
return nullRetriever;
|
|
555
|
-
}
|
|
556
|
-
return loadedRetriever(docs[0] ?? null);
|
|
557
|
-
},
|
|
558
|
-
false
|
|
559
|
-
);
|
|
560
|
-
}
|
|
561
|
-
firstX() {
|
|
562
|
-
return new PromiseEntOrNullImpl(
|
|
563
|
-
this.ctx,
|
|
564
|
-
this.entDefinitions,
|
|
565
|
-
this.table,
|
|
566
|
-
async () => {
|
|
567
|
-
const docs = await this.retrieve();
|
|
568
|
-
if (docs === null) {
|
|
569
|
-
return nullRetriever;
|
|
570
|
-
}
|
|
571
|
-
const doc = docs[0] ?? void 0;
|
|
572
|
-
if (doc === void 0) {
|
|
573
|
-
throw new Error("Query returned no documents");
|
|
574
|
-
}
|
|
575
|
-
return loadedRetriever(doc);
|
|
576
|
-
},
|
|
577
|
-
true
|
|
578
|
-
);
|
|
579
|
-
}
|
|
580
|
-
unique() {
|
|
581
|
-
return new PromiseEntOrNullImpl(
|
|
582
|
-
this.ctx,
|
|
583
|
-
this.entDefinitions,
|
|
584
|
-
this.table,
|
|
585
|
-
async () => {
|
|
586
|
-
const docs = await this.retrieve();
|
|
587
|
-
if (docs === null) {
|
|
588
|
-
return nullRetriever;
|
|
589
|
-
}
|
|
590
|
-
if (docs.length > 1) {
|
|
591
|
-
throw new Error("unique() query returned more than one result");
|
|
592
|
-
}
|
|
593
|
-
return loadedRetriever(docs[0] ?? null);
|
|
594
|
-
},
|
|
595
|
-
false
|
|
596
|
-
);
|
|
597
|
-
}
|
|
598
|
-
uniqueX() {
|
|
599
|
-
return new PromiseEntOrNullImpl(
|
|
600
|
-
this.ctx,
|
|
601
|
-
this.entDefinitions,
|
|
602
|
-
this.table,
|
|
603
|
-
async () => {
|
|
604
|
-
const docs = await this.retrieve();
|
|
605
|
-
if (docs === null) {
|
|
606
|
-
return nullRetriever;
|
|
607
|
-
}
|
|
608
|
-
if (docs.length > 1) {
|
|
609
|
-
throw new Error("unique() query returned more than one result");
|
|
610
|
-
}
|
|
611
|
-
if (docs.length < 1) {
|
|
612
|
-
throw new Error("unique() query returned no documents");
|
|
613
|
-
}
|
|
614
|
-
return loadedRetriever(docs[0]);
|
|
615
|
-
},
|
|
616
|
-
true
|
|
617
|
-
);
|
|
618
|
-
}
|
|
619
|
-
async docs() {
|
|
620
|
-
const docs = await this.retrieve();
|
|
621
|
-
return filterByReadRule(
|
|
622
|
-
this.ctx,
|
|
623
|
-
this.entDefinitions,
|
|
624
|
-
this.table,
|
|
625
|
-
docs,
|
|
626
|
-
this.throwIfNull
|
|
627
|
-
);
|
|
628
|
-
}
|
|
629
|
-
then(onfulfilled, onrejected) {
|
|
630
|
-
return this.docs().then(
|
|
631
|
-
(docs) => docs === null ? null : docs.map(
|
|
632
|
-
(doc) => entWrapper(doc, this.ctx, this.entDefinitions, this.table)
|
|
633
|
-
)
|
|
634
|
-
).then(onfulfilled, onrejected);
|
|
635
|
-
}
|
|
636
|
-
};
|
|
637
|
-
var PromiseEdgeOrNullImpl = class extends PromiseEntsOrNullImpl {
|
|
638
|
-
constructor(ctx, entDefinitions, table, field, retrieveRange) {
|
|
639
|
-
super(ctx, entDefinitions, table, () => retrieveRange((q) => q), false);
|
|
640
|
-
this.field = field;
|
|
641
|
-
this.retrieveRange = retrieveRange;
|
|
642
|
-
}
|
|
643
|
-
async has(id) {
|
|
644
|
-
const docs = await this.retrieveRange((q) => q.eq(this.field, id));
|
|
645
|
-
return (docs?.length ?? 0) > 0;
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
var PromiseEntOrNullImpl = class _PromiseEntOrNullImpl extends Promise {
|
|
649
|
-
constructor(ctx, entDefinitions, table, retrieve, throwIfNull) {
|
|
650
|
-
super(() => {
|
|
651
|
-
});
|
|
652
|
-
this.ctx = ctx;
|
|
653
|
-
this.entDefinitions = entDefinitions;
|
|
654
|
-
this.table = table;
|
|
655
|
-
this.retrieve = retrieve;
|
|
656
|
-
this.throwIfNull = throwIfNull;
|
|
657
|
-
}
|
|
658
|
-
async doc() {
|
|
659
|
-
const { id, doc: getDoc } = await this.retrieve();
|
|
660
|
-
if (id === null) {
|
|
661
|
-
return null;
|
|
662
|
-
}
|
|
663
|
-
const doc = await getDoc();
|
|
664
|
-
if (doc === null) {
|
|
665
|
-
return null;
|
|
666
|
-
}
|
|
667
|
-
const readPolicy = getReadRule(this.entDefinitions, this.table);
|
|
668
|
-
if (readPolicy !== void 0) {
|
|
669
|
-
const decision = await readPolicy(
|
|
670
|
-
entWrapper(doc, this.ctx, this.entDefinitions, this.table)
|
|
671
|
-
);
|
|
672
|
-
if (this.throwIfNull && !decision) {
|
|
673
|
-
throw new Error(
|
|
674
|
-
`Document cannot be read with id \`${doc._id}\` in table "${this.table}"`
|
|
675
|
-
);
|
|
676
|
-
}
|
|
677
|
-
return decision ? doc : null;
|
|
678
|
-
}
|
|
679
|
-
return doc;
|
|
680
|
-
}
|
|
681
|
-
then(onfulfilled, onrejected) {
|
|
682
|
-
return this.doc().then(
|
|
683
|
-
(doc) => doc === null ? null : entWrapper(doc, this.ctx, this.entDefinitions, this.table)
|
|
684
|
-
).then(onfulfilled, onrejected);
|
|
685
|
-
}
|
|
686
|
-
edge(edge) {
|
|
687
|
-
return this.edgeImpl(edge);
|
|
688
|
-
}
|
|
689
|
-
edgeX(edge) {
|
|
690
|
-
return this.edgeImpl(edge, true);
|
|
691
|
-
}
|
|
692
|
-
edgeImpl(edge, throwIfNull = false) {
|
|
693
|
-
const edgeDefinition = getEdgeDefinitions(this.entDefinitions, this.table)[edge];
|
|
694
|
-
if (edgeDefinition.cardinality === "multiple") {
|
|
695
|
-
if (edgeDefinition.type === "ref") {
|
|
696
|
-
return new PromiseEdgeOrNullImpl(
|
|
697
|
-
this.ctx,
|
|
698
|
-
this.entDefinitions,
|
|
699
|
-
edgeDefinition.to,
|
|
700
|
-
edgeDefinition.ref,
|
|
701
|
-
async (indexRange) => {
|
|
702
|
-
const { id } = await this.retrieve();
|
|
703
|
-
if (id === null) {
|
|
704
|
-
return null;
|
|
705
|
-
}
|
|
706
|
-
const edgeDocs = await this.ctx.db.query(edgeDefinition.table).withIndex(
|
|
707
|
-
edgeDefinition.field,
|
|
708
|
-
(q) => indexRange(q.eq(edgeDefinition.field, id))
|
|
709
|
-
).collect();
|
|
710
|
-
return (await Promise.all(
|
|
711
|
-
edgeDocs.map(
|
|
712
|
-
(edgeDoc) => this.ctx.db.get(edgeDoc[edgeDefinition.ref])
|
|
713
|
-
)
|
|
714
|
-
)).filter((doc, i) => {
|
|
715
|
-
if (doc === null) {
|
|
716
|
-
throw new Error(
|
|
717
|
-
`Dangling reference for edge "${edgeDefinition.name}" in table "${this.table}" for document with ID "${id}": Could not find a document with ID "${edgeDocs[i][edgeDefinition.field]}" in table "${edgeDefinition.to}" (edge document ID is "${edgeDocs[i]._id}").`
|
|
718
|
-
);
|
|
719
|
-
}
|
|
720
|
-
return true;
|
|
721
|
-
});
|
|
722
|
-
}
|
|
723
|
-
);
|
|
724
|
-
}
|
|
725
|
-
return new PromiseQueryOrNullImpl(
|
|
726
|
-
this.ctx,
|
|
727
|
-
this.entDefinitions,
|
|
728
|
-
edgeDefinition.to,
|
|
729
|
-
async () => {
|
|
730
|
-
const { id } = await this.retrieve();
|
|
731
|
-
if (id === null) {
|
|
732
|
-
return null;
|
|
733
|
-
}
|
|
734
|
-
return this.ctx.db.query(edgeDefinition.to).withIndex(
|
|
735
|
-
edgeDefinition.ref,
|
|
736
|
-
(q) => q.eq(edgeDefinition.ref, id)
|
|
737
|
-
);
|
|
738
|
-
}
|
|
739
|
-
);
|
|
740
|
-
}
|
|
741
|
-
return new _PromiseEntOrNullImpl(
|
|
742
|
-
this.ctx,
|
|
743
|
-
this.entDefinitions,
|
|
744
|
-
edgeDefinition.to,
|
|
745
|
-
async () => {
|
|
746
|
-
const { id, doc: getDoc } = await this.retrieve();
|
|
747
|
-
if (id === null) {
|
|
748
|
-
return nullRetriever;
|
|
749
|
-
}
|
|
750
|
-
if (edgeDefinition.type === "ref") {
|
|
751
|
-
const otherDoc = await this.ctx.db.query(edgeDefinition.to).withIndex(
|
|
752
|
-
edgeDefinition.ref,
|
|
753
|
-
(q) => q.eq(edgeDefinition.ref, id)
|
|
754
|
-
).unique();
|
|
755
|
-
if (throwIfNull && otherDoc === null) {
|
|
756
|
-
throw new Error(
|
|
757
|
-
`Edge "${edgeDefinition.name}" does not exist for document with ID "${id}"`
|
|
758
|
-
);
|
|
759
|
-
}
|
|
760
|
-
return loadedRetriever(otherDoc);
|
|
761
|
-
}
|
|
762
|
-
const doc = await getDoc();
|
|
763
|
-
const otherId = doc[edgeDefinition.field];
|
|
764
|
-
return {
|
|
765
|
-
id: otherId,
|
|
766
|
-
doc: async () => {
|
|
767
|
-
const otherDoc = await this.ctx.db.get(otherId);
|
|
768
|
-
if (otherDoc === null) {
|
|
769
|
-
throw new Error(
|
|
770
|
-
`Dangling reference for edge "${edgeDefinition.name}" in table "${this.table}" for document with ID "${id}": Could not find a document with ID "${otherId}" in table "${edgeDefinition.to}".`
|
|
771
|
-
);
|
|
772
|
-
}
|
|
773
|
-
return otherDoc;
|
|
774
|
-
}
|
|
775
|
-
};
|
|
776
|
-
},
|
|
777
|
-
throwIfNull
|
|
778
|
-
);
|
|
779
|
-
}
|
|
780
|
-
};
|
|
781
|
-
var PromiseArrayImpl = class extends Promise {
|
|
782
|
-
constructor(retrieve) {
|
|
783
|
-
super(() => {
|
|
784
|
-
});
|
|
785
|
-
this.retrieve = retrieve;
|
|
786
|
-
}
|
|
787
|
-
async filter(predicate) {
|
|
788
|
-
const array = await this.retrieve();
|
|
789
|
-
if (array === null) {
|
|
790
|
-
return null;
|
|
791
|
-
}
|
|
792
|
-
return array.filter(predicate);
|
|
793
|
-
}
|
|
794
|
-
then(onfulfilled, onrejected) {
|
|
795
|
-
return this.retrieve().then(onfulfilled, onrejected);
|
|
796
|
-
}
|
|
797
|
-
};
|
|
798
|
-
function entWrapper(fields, ctx, entDefinitions, table) {
|
|
799
|
-
const doc = { ...fields };
|
|
800
|
-
const queryInterface = new PromiseEntWriterImpl(
|
|
801
|
-
ctx,
|
|
802
|
-
entDefinitions,
|
|
803
|
-
table,
|
|
804
|
-
async () => ({ id: doc._id, doc: async () => doc }),
|
|
805
|
-
// this `true` doesn't matter, the queryInterface cannot be awaited
|
|
806
|
-
true
|
|
807
|
-
);
|
|
808
|
-
Object.defineProperty(doc, "edge", {
|
|
809
|
-
value: (edge) => {
|
|
810
|
-
return queryInterface.edge(edge);
|
|
811
|
-
},
|
|
812
|
-
enumerable: false,
|
|
813
|
-
writable: false,
|
|
814
|
-
configurable: false
|
|
815
|
-
});
|
|
816
|
-
Object.defineProperty(doc, "edgeX", {
|
|
817
|
-
value: (edge) => {
|
|
818
|
-
return queryInterface.edgeX(edge);
|
|
819
|
-
},
|
|
820
|
-
enumerable: false,
|
|
821
|
-
writable: false,
|
|
822
|
-
configurable: false
|
|
823
|
-
});
|
|
824
|
-
Object.defineProperty(doc, "doc", {
|
|
825
|
-
value: () => {
|
|
826
|
-
return doc;
|
|
827
|
-
},
|
|
828
|
-
enumerable: false,
|
|
829
|
-
writable: false,
|
|
830
|
-
configurable: false
|
|
831
|
-
});
|
|
832
|
-
Object.defineProperty(doc, "patch", {
|
|
833
|
-
value: (value) => {
|
|
834
|
-
return queryInterface.patch(value);
|
|
835
|
-
},
|
|
836
|
-
enumerable: false,
|
|
837
|
-
writable: false,
|
|
838
|
-
configurable: false
|
|
839
|
-
});
|
|
840
|
-
Object.defineProperty(doc, "replace", {
|
|
841
|
-
value: (value) => {
|
|
842
|
-
return queryInterface.replace(value);
|
|
843
|
-
},
|
|
844
|
-
enumerable: false,
|
|
845
|
-
writable: false,
|
|
846
|
-
configurable: false
|
|
847
|
-
});
|
|
848
|
-
Object.defineProperty(doc, "delete", {
|
|
849
|
-
value: () => {
|
|
850
|
-
return queryInterface.delete();
|
|
851
|
-
},
|
|
852
|
-
enumerable: false,
|
|
853
|
-
writable: false,
|
|
854
|
-
configurable: false
|
|
855
|
-
});
|
|
856
|
-
Object.entries(entDefinitions[table].defaults).map(
|
|
857
|
-
([field, value]) => {
|
|
858
|
-
if (doc[field] === void 0) {
|
|
859
|
-
doc[field] = value;
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
);
|
|
863
|
-
return doc;
|
|
864
|
-
}
|
|
865
|
-
var PromiseEntWriterImpl = class extends PromiseEntOrNullImpl {
|
|
866
|
-
constructor(ctx, entDefinitions, table, retrieve, throwIfNull) {
|
|
867
|
-
super(ctx, entDefinitions, table, retrieve, throwIfNull);
|
|
868
|
-
this.ctx = ctx;
|
|
869
|
-
this.entDefinitions = entDefinitions;
|
|
870
|
-
this.table = table;
|
|
871
|
-
this.retrieve = retrieve;
|
|
872
|
-
this.throwIfNull = throwIfNull;
|
|
873
|
-
this.base = new WriterImplBase(ctx, entDefinitions, table);
|
|
874
|
-
}
|
|
875
|
-
base;
|
|
876
|
-
patch(value) {
|
|
877
|
-
return new PromiseEntIdImpl(
|
|
878
|
-
this.ctx,
|
|
879
|
-
this.entDefinitions,
|
|
880
|
-
this.table,
|
|
881
|
-
async () => {
|
|
882
|
-
const { id: docId } = await this.retrieve();
|
|
883
|
-
const id = docId;
|
|
884
|
-
await this.base.checkReadAndWriteRule("update", id, value);
|
|
885
|
-
await this.base.checkUniqueness(value, id);
|
|
886
|
-
const fields = this.base.fieldsOnly(value);
|
|
887
|
-
await this.ctx.db.patch(id, fields);
|
|
888
|
-
const edges = {};
|
|
889
|
-
await Promise.all(
|
|
890
|
-
Object.keys(value).map(async (key) => {
|
|
891
|
-
const edgeDefinition = getEdgeDefinitions(
|
|
892
|
-
this.entDefinitions,
|
|
893
|
-
this.table
|
|
894
|
-
)[key];
|
|
895
|
-
if (edgeDefinition === void 0 || edgeDefinition.cardinality === "single" && edgeDefinition.type === "field") {
|
|
896
|
-
return;
|
|
897
|
-
}
|
|
898
|
-
if (edgeDefinition.cardinality === "single") {
|
|
899
|
-
throw new Error(
|
|
900
|
-
`Cannot set 1:1 edge "${edgeDefinition.name}" on ent in table "${this.table}", update the ent in "${edgeDefinition.to}" table instead.`
|
|
901
|
-
);
|
|
902
|
-
} else {
|
|
903
|
-
if (edgeDefinition.type === "field") {
|
|
904
|
-
throw new Error(
|
|
905
|
-
`Cannot set 1:many edges "${edgeDefinition.name}" on ent in table "${this.table}", update the ents in "${edgeDefinition.to}" table instead.`
|
|
906
|
-
);
|
|
907
|
-
} else {
|
|
908
|
-
const { add, remove } = value[key];
|
|
909
|
-
const removeEdges = (await Promise.all(
|
|
910
|
-
(remove ?? []).map(
|
|
911
|
-
async (edgeId) => (await this.ctx.db.query(edgeDefinition.table).withIndex(
|
|
912
|
-
edgeDefinition.field,
|
|
913
|
-
(q) => q.eq(edgeDefinition.field, id).eq(
|
|
914
|
-
edgeDefinition.ref,
|
|
915
|
-
edgeId
|
|
916
|
-
)
|
|
917
|
-
).collect()).concat(
|
|
918
|
-
edgeDefinition.symmetric ? await this.ctx.db.query(edgeDefinition.table).withIndex(
|
|
919
|
-
edgeDefinition.ref,
|
|
920
|
-
(q) => q.eq(edgeDefinition.ref, id).eq(
|
|
921
|
-
edgeDefinition.field,
|
|
922
|
-
edgeId
|
|
923
|
-
)
|
|
924
|
-
).collect() : []
|
|
925
|
-
)
|
|
926
|
-
)
|
|
927
|
-
)).map((edgeDoc) => edgeDoc._id);
|
|
928
|
-
edges[key] = {
|
|
929
|
-
add,
|
|
930
|
-
removeEdges
|
|
931
|
-
};
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
})
|
|
935
|
-
);
|
|
936
|
-
await this.base.writeEdges(id, edges);
|
|
937
|
-
return id;
|
|
938
|
-
}
|
|
939
|
-
);
|
|
940
|
-
}
|
|
941
|
-
replace(value) {
|
|
942
|
-
return new PromiseEntIdImpl(
|
|
943
|
-
this.ctx,
|
|
944
|
-
this.entDefinitions,
|
|
945
|
-
this.table,
|
|
946
|
-
async () => {
|
|
947
|
-
const { id } = await this.retrieve();
|
|
948
|
-
const docId = id;
|
|
949
|
-
await this.base.checkReadAndWriteRule("update", docId, value);
|
|
950
|
-
await this.base.checkUniqueness(value, docId);
|
|
951
|
-
const fields = this.base.fieldsOnly(value);
|
|
952
|
-
await this.ctx.db.replace(docId, fields);
|
|
953
|
-
const edges = {};
|
|
954
|
-
await Promise.all(
|
|
955
|
-
Object.values(
|
|
956
|
-
getEdgeDefinitions(this.entDefinitions, this.table)
|
|
957
|
-
).map(async (edgeDefinition) => {
|
|
958
|
-
const key = edgeDefinition.name;
|
|
959
|
-
const idOrIds = value[key];
|
|
960
|
-
if (edgeDefinition.cardinality === "single") {
|
|
961
|
-
if (edgeDefinition.type === "ref") {
|
|
962
|
-
const oldDoc = await this.ctx.db.get(docId);
|
|
963
|
-
if (oldDoc[key] !== void 0 && oldDoc[key] !== idOrIds) {
|
|
964
|
-
throw new Error("Cannot set 1:1 edge from optional end.");
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
} else {
|
|
968
|
-
if (edgeDefinition.type === "field") {
|
|
969
|
-
if (idOrIds !== void 0) {
|
|
970
|
-
throw new Error("Cannot set 1:many edge from many end.");
|
|
971
|
-
}
|
|
972
|
-
} else {
|
|
973
|
-
const requested = new Set(idOrIds ?? []);
|
|
974
|
-
const removeEdges = (await this.ctx.db.query(edgeDefinition.table).withIndex(
|
|
975
|
-
edgeDefinition.field,
|
|
976
|
-
(q) => q.eq(edgeDefinition.field, docId)
|
|
977
|
-
).collect()).map((doc) => [doc._id, doc[edgeDefinition.ref]]).concat(
|
|
978
|
-
edgeDefinition.symmetric ? (await this.ctx.db.query(edgeDefinition.table).withIndex(
|
|
979
|
-
edgeDefinition.ref,
|
|
980
|
-
(q) => q.eq(edgeDefinition.ref, docId)
|
|
981
|
-
).collect()).map(
|
|
982
|
-
(doc) => [doc._id, doc[edgeDefinition.field]]
|
|
983
|
-
) : []
|
|
984
|
-
).filter(([_edgeId, otherId]) => {
|
|
985
|
-
if (requested.has(otherId)) {
|
|
986
|
-
requested.delete(otherId);
|
|
987
|
-
return false;
|
|
988
|
-
}
|
|
989
|
-
return true;
|
|
990
|
-
}).map(([edgeId]) => edgeId);
|
|
991
|
-
edges[key] = {
|
|
992
|
-
add: idOrIds ?? [],
|
|
993
|
-
removeEdges
|
|
994
|
-
};
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
})
|
|
998
|
-
);
|
|
999
|
-
await this.base.writeEdges(docId, edges);
|
|
1000
|
-
return docId;
|
|
1001
|
-
}
|
|
1002
|
-
);
|
|
1003
|
-
}
|
|
1004
|
-
async delete() {
|
|
1005
|
-
const { id: docId } = await this.retrieve();
|
|
1006
|
-
const id = docId;
|
|
1007
|
-
return this.base.deleteId(id, "default");
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
var PromiseEntIdImpl = class extends Promise {
|
|
1011
|
-
constructor(ctx, entDefinitions, table, retrieve) {
|
|
1012
|
-
super(() => {
|
|
1013
|
-
});
|
|
1014
|
-
this.ctx = ctx;
|
|
1015
|
-
this.entDefinitions = entDefinitions;
|
|
1016
|
-
this.table = table;
|
|
1017
|
-
this.retrieve = retrieve;
|
|
1018
|
-
}
|
|
1019
|
-
get() {
|
|
1020
|
-
return new PromiseEntOrNullImpl(
|
|
1021
|
-
this.ctx,
|
|
1022
|
-
this.entDefinitions,
|
|
1023
|
-
this.table,
|
|
1024
|
-
async () => {
|
|
1025
|
-
const id = await this.retrieve();
|
|
1026
|
-
return { id, doc: async () => this.ctx.db.get(id) };
|
|
1027
|
-
},
|
|
1028
|
-
true
|
|
1029
|
-
);
|
|
1030
|
-
}
|
|
1031
|
-
then(onfulfilled, onrejected) {
|
|
1032
|
-
return this.retrieve().then(onfulfilled, onrejected);
|
|
1033
|
-
}
|
|
1034
|
-
};
|
|
1035
|
-
var nullRetriever = {
|
|
1036
|
-
id: null,
|
|
1037
|
-
doc: async () => null
|
|
1038
|
-
};
|
|
1039
|
-
function loadedRetriever(doc) {
|
|
1040
|
-
return {
|
|
1041
|
-
id: doc?._id ?? null,
|
|
1042
|
-
doc: async () => doc
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
async function filterByReadRule(ctx, entDefinitions, table, docs, throwIfNull) {
|
|
1046
|
-
if (docs === null) {
|
|
1047
|
-
return null;
|
|
1048
|
-
}
|
|
1049
|
-
const readPolicy = getReadRule(entDefinitions, table);
|
|
1050
|
-
if (readPolicy === void 0) {
|
|
1051
|
-
return docs;
|
|
1052
|
-
}
|
|
1053
|
-
const decisions = await Promise.all(
|
|
1054
|
-
docs.map(async (doc) => {
|
|
1055
|
-
const decision = await readPolicy(
|
|
1056
|
-
entWrapper(doc, ctx, entDefinitions, table)
|
|
1057
|
-
);
|
|
1058
|
-
if (throwIfNull && !decision) {
|
|
1059
|
-
throw new Error(
|
|
1060
|
-
`Document cannot be read with id \`${doc._id}\` in table "${table}"`
|
|
1061
|
-
);
|
|
1062
|
-
}
|
|
1063
|
-
return decision;
|
|
1064
|
-
})
|
|
1065
|
-
);
|
|
1066
|
-
return docs.filter((_, i) => decisions[i]);
|
|
1067
|
-
}
|
|
1068
|
-
function getReadRule(entDefinitions, table) {
|
|
1069
|
-
return entDefinitions.rules?.[table]?.read;
|
|
1070
|
-
}
|
|
1071
|
-
function getWriteRule(entDefinitions, table) {
|
|
1072
|
-
return entDefinitions.rules?.[table]?.write;
|
|
1073
|
-
}
|
|
1074
33
|
function getEdgeDefinitions(entDefinitions, table) {
|
|
1075
34
|
return entDefinitions[table].edges;
|
|
1076
35
|
}
|
|
1077
|
-
function getDeletionConfig(entDefinitions, table) {
|
|
1078
|
-
return entDefinitions[table].deletionConfig;
|
|
1079
|
-
}
|
|
1080
36
|
|
|
1081
37
|
// src/deletion.ts
|
|
1082
|
-
var vApproach = import_values.v.union(
|
|
1083
|
-
import_values.v.literal("schedule"),
|
|
1084
|
-
import_values.v.literal("deleteOne"),
|
|
1085
|
-
import_values.v.literal("paginate")
|
|
1086
|
-
);
|
|
38
|
+
var vApproach = import_values.v.union(import_values.v.literal("cascade"), import_values.v.literal("paginate"));
|
|
1087
39
|
function scheduledDeleteFactory(entDefinitions, options) {
|
|
1088
40
|
const selfRef = options?.scheduledDelete ?? (0, import_server2.makeFunctionReference)(
|
|
1089
41
|
"functions:scheduledDelete"
|
|
@@ -1138,10 +90,8 @@ function scheduledDeleteFactory(entDefinitions, options) {
|
|
|
1138
90
|
return;
|
|
1139
91
|
}
|
|
1140
92
|
await progressScheduledDeletion(
|
|
1141
|
-
ctx,
|
|
1142
|
-
|
|
1143
|
-
selfRef,
|
|
1144
|
-
origin,
|
|
93
|
+
{ ctx, entDefinitions, selfRef, origin },
|
|
94
|
+
newCounter(),
|
|
1145
95
|
inProgress ? stack : [
|
|
1146
96
|
{
|
|
1147
97
|
id: originId,
|
|
@@ -1158,12 +108,11 @@ function getEdgeArgs(entDefinitions, table) {
|
|
|
1158
108
|
return Object.values(edges).flatMap((edgeDefinition) => {
|
|
1159
109
|
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "ref" || edgeDefinition.cardinality === "multiple" && edgeDefinition.type === "field") {
|
|
1160
110
|
const table2 = edgeDefinition.to;
|
|
1161
|
-
const targetDeletionConfig = getDeletionConfig(entDefinitions, table2);
|
|
1162
111
|
const targetEdges = getEdgeDefinitions(entDefinitions, table2);
|
|
1163
112
|
const hasCascadingEdges = Object.values(targetEdges).some(
|
|
1164
113
|
(edgeDefinition2) => edgeDefinition2.cardinality === "single" && edgeDefinition2.type === "ref" || edgeDefinition2.cardinality === "multiple"
|
|
1165
114
|
);
|
|
1166
|
-
const approach =
|
|
115
|
+
const approach = hasCascadingEdges ? "cascade" : "paginate";
|
|
1167
116
|
const indexName = edgeDefinition.ref;
|
|
1168
117
|
return [{ table: table2, indexName, approach }];
|
|
1169
118
|
} else if (edgeDefinition.cardinality === "multiple") {
|
|
@@ -1187,39 +136,49 @@ function getEdgeArgs(entDefinitions, table) {
|
|
|
1187
136
|
}
|
|
1188
137
|
});
|
|
1189
138
|
}
|
|
1190
|
-
async function progressScheduledDeletion(
|
|
139
|
+
async function progressScheduledDeletion(cascade, counter, stack) {
|
|
140
|
+
const { ctx } = cascade;
|
|
1191
141
|
const last = stack[stack.length - 1];
|
|
1192
142
|
if ("id" in last) {
|
|
1193
143
|
const edgeArgs = last.edges[0];
|
|
1194
144
|
if (edgeArgs === void 0) {
|
|
1195
145
|
await ctx.db.delete(last.id);
|
|
1196
146
|
if (stack.length > 1) {
|
|
1197
|
-
await
|
|
1198
|
-
origin,
|
|
1199
|
-
stack: stack.slice(0, -1),
|
|
1200
|
-
inProgress: true
|
|
1201
|
-
});
|
|
147
|
+
await continueOrSchedule(cascade, counter, stack.slice(0, -1));
|
|
1202
148
|
}
|
|
1203
149
|
} else {
|
|
1204
150
|
const updated = { ...last, edges: last.edges.slice(1) };
|
|
1205
|
-
await
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
selfRef,
|
|
1209
|
-
origin,
|
|
151
|
+
await paginateOrCascade(
|
|
152
|
+
cascade,
|
|
153
|
+
counter,
|
|
1210
154
|
stack.slice(0, -1).concat(updated),
|
|
1211
|
-
{
|
|
155
|
+
{
|
|
156
|
+
cursor: null,
|
|
157
|
+
fieldValue: last.id,
|
|
158
|
+
...edgeArgs
|
|
159
|
+
}
|
|
1212
160
|
);
|
|
1213
161
|
}
|
|
1214
162
|
} else {
|
|
1215
|
-
await
|
|
163
|
+
await paginateOrCascade(cascade, counter, stack, last);
|
|
1216
164
|
}
|
|
1217
165
|
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
}
|
|
166
|
+
var MAXIMUM_DOCUMENTS_READ = 8192 / 4;
|
|
167
|
+
var MAXIMUM_BYTES_READ = 2 ** 18;
|
|
168
|
+
async function paginateOrCascade(cascade, counter, stack, { table, approach, indexName, fieldValue, cursor }) {
|
|
169
|
+
const { ctx, entDefinitions } = cascade;
|
|
170
|
+
const { page, continueCursor, isDone, bytesRead } = await paginate(
|
|
171
|
+
ctx,
|
|
172
|
+
{ table, indexName, fieldValue },
|
|
173
|
+
{
|
|
174
|
+
cursor,
|
|
175
|
+
...limitsBasedOnCounter(
|
|
176
|
+
counter,
|
|
177
|
+
approach === "paginate" ? { numItems: MAXIMUM_DOCUMENTS_READ } : { numItems: 1 }
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
const updatedCounter = incrementCounter(counter, page.length, bytesRead);
|
|
1223
182
|
const updated = {
|
|
1224
183
|
approach,
|
|
1225
184
|
table,
|
|
@@ -1228,34 +187,92 @@ async function paginate(ctx, entDefinitions, selfRef, origin, stack, { table, ap
|
|
|
1228
187
|
fieldValue
|
|
1229
188
|
};
|
|
1230
189
|
const relevantStack = cursor === null ? stack : stack.slice(0, -1);
|
|
1231
|
-
|
|
190
|
+
const updatedStack = isDone && (approach === "paginate" || page.length === 0) ? relevantStack : relevantStack.concat(
|
|
191
|
+
approach === "cascade" ? [
|
|
192
|
+
updated,
|
|
193
|
+
{
|
|
194
|
+
id: page[0]._id,
|
|
195
|
+
table,
|
|
196
|
+
edges: getEdgeArgs(entDefinitions, table)
|
|
197
|
+
}
|
|
198
|
+
] : [updated]
|
|
199
|
+
);
|
|
200
|
+
if (approach === "paginate") {
|
|
201
|
+
await Promise.all(page.map((doc) => ctx.db.delete(doc._id)));
|
|
202
|
+
}
|
|
203
|
+
await continueOrSchedule(cascade, updatedCounter, updatedStack);
|
|
204
|
+
}
|
|
205
|
+
async function continueOrSchedule(cascade, counter, stack) {
|
|
206
|
+
if (shouldSchedule(counter)) {
|
|
207
|
+
const { ctx, selfRef, origin } = cascade;
|
|
1232
208
|
await ctx.scheduler.runAfter(0, selfRef, {
|
|
1233
209
|
origin,
|
|
1234
|
-
stack
|
|
1235
|
-
updated,
|
|
1236
|
-
{
|
|
1237
|
-
id: page[0]._id,
|
|
1238
|
-
table,
|
|
1239
|
-
edges: getEdgeArgs(entDefinitions, table)
|
|
1240
|
-
}
|
|
1241
|
-
]),
|
|
210
|
+
stack,
|
|
1242
211
|
inProgress: true
|
|
1243
212
|
});
|
|
1244
213
|
} else {
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
214
|
+
await progressScheduledDeletion(cascade, counter, stack);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function newCounter() {
|
|
218
|
+
return {
|
|
219
|
+
numDocuments: 0,
|
|
220
|
+
numBytesRead: 0
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function incrementCounter(counter, numDocuments, numBytesRead) {
|
|
224
|
+
return {
|
|
225
|
+
numDocuments: counter.numDocuments + numDocuments,
|
|
226
|
+
numBytesRead: counter.numBytesRead + numBytesRead
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function limitsBasedOnCounter(counter, { numItems }) {
|
|
230
|
+
return {
|
|
231
|
+
numItems: Math.max(1, numItems - counter.numDocuments),
|
|
232
|
+
maximumBytesRead: Math.max(1, MAXIMUM_BYTES_READ - counter.numBytesRead)
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function shouldSchedule(counter) {
|
|
236
|
+
return counter.numDocuments >= MAXIMUM_DOCUMENTS_READ || counter.numBytesRead >= MAXIMUM_BYTES_READ;
|
|
237
|
+
}
|
|
238
|
+
async function paginate(ctx, {
|
|
239
|
+
table,
|
|
240
|
+
indexName,
|
|
241
|
+
fieldValue
|
|
242
|
+
}, {
|
|
243
|
+
cursor,
|
|
244
|
+
numItems,
|
|
245
|
+
maximumBytesRead
|
|
246
|
+
}) {
|
|
247
|
+
const query = ctx.db.query(table).withIndex(
|
|
248
|
+
indexName,
|
|
249
|
+
(q) => q.eq(indexName, fieldValue).gt(
|
|
250
|
+
"_creationTime",
|
|
251
|
+
cursor
|
|
252
|
+
)
|
|
253
|
+
);
|
|
254
|
+
let bytesRead = 0;
|
|
255
|
+
const results = [];
|
|
256
|
+
let isDone = true;
|
|
257
|
+
for await (const doc of query) {
|
|
258
|
+
if (results.length >= numItems) {
|
|
259
|
+
isDone = false;
|
|
260
|
+
break;
|
|
1252
261
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
}
|
|
262
|
+
const size = JSON.stringify((0, import_values.convexToJson)(doc)).length * 8;
|
|
263
|
+
if (bytesRead + size > maximumBytesRead) {
|
|
264
|
+
isDone = false;
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
bytesRead += size;
|
|
268
|
+
results.push(doc);
|
|
1258
269
|
}
|
|
270
|
+
return {
|
|
271
|
+
page: results,
|
|
272
|
+
continueCursor: results.length === 0 ? cursor : results[results.length - 1]._creationTime,
|
|
273
|
+
isDone,
|
|
274
|
+
bytesRead
|
|
275
|
+
};
|
|
1259
276
|
}
|
|
1260
277
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1261
278
|
0 && (module.exports = {
|