convex-ents 0.2.0 → 0.3.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.
@@ -0,0 +1,1264 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/deletion.ts
21
+ var deletion_exports = {};
22
+ __export(deletion_exports, {
23
+ scheduledDeleteFactory: () => scheduledDeleteFactory
24
+ });
25
+ module.exports = __toCommonJS(deletion_exports);
26
+ var import_server2 = require("convex/server");
27
+ var import_values = require("convex/values");
28
+
29
+ // src/writer.ts
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
+
272
+ // 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
+ function getEdgeDefinitions(entDefinitions, table) {
1075
+ return entDefinitions[table].edges;
1076
+ }
1077
+ function getDeletionConfig(entDefinitions, table) {
1078
+ return entDefinitions[table].deletionConfig;
1079
+ }
1080
+
1081
+ // 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
+ );
1087
+ function scheduledDeleteFactory(entDefinitions, options) {
1088
+ const selfRef = options?.scheduledDelete ?? (0, import_server2.makeFunctionReference)(
1089
+ "functions:scheduledDelete"
1090
+ );
1091
+ return (0, import_server2.internalMutationGeneric)({
1092
+ args: {
1093
+ origin: import_values.v.object({
1094
+ id: import_values.v.string(),
1095
+ table: import_values.v.string(),
1096
+ deletionTime: import_values.v.number()
1097
+ }),
1098
+ stack: import_values.v.array(
1099
+ import_values.v.union(
1100
+ import_values.v.object({
1101
+ id: import_values.v.string(),
1102
+ table: import_values.v.string(),
1103
+ edges: import_values.v.array(
1104
+ import_values.v.object({
1105
+ approach: vApproach,
1106
+ table: import_values.v.string(),
1107
+ indexName: import_values.v.string()
1108
+ })
1109
+ )
1110
+ }),
1111
+ import_values.v.object({
1112
+ approach: vApproach,
1113
+ cursor: import_values.v.union(import_values.v.string(), import_values.v.null()),
1114
+ table: import_values.v.string(),
1115
+ indexName: import_values.v.string(),
1116
+ fieldValue: import_values.v.any()
1117
+ })
1118
+ )
1119
+ ),
1120
+ inProgress: import_values.v.boolean()
1121
+ },
1122
+ handler: async (ctx, { origin, stack, inProgress }) => {
1123
+ const originId = ctx.db.normalizeId(origin.table, origin.id);
1124
+ if (originId === null) {
1125
+ throw new Error(`Invalid ID "${origin.id}" for table ${origin.table}`);
1126
+ }
1127
+ const doc = await ctx.db.get(originId);
1128
+ if (doc.deletionTime !== origin.deletionTime) {
1129
+ if (inProgress) {
1130
+ console.error(
1131
+ `[Ents] Already in-progress scheduled deletion for "${origin.id}" was cancelled!`
1132
+ );
1133
+ } else {
1134
+ console.log(
1135
+ `[Ents] Scheduled deletion for "${origin.id}" was cancelled`
1136
+ );
1137
+ }
1138
+ return;
1139
+ }
1140
+ await progressScheduledDeletion(
1141
+ ctx,
1142
+ entDefinitions,
1143
+ selfRef,
1144
+ origin,
1145
+ inProgress ? stack : [
1146
+ {
1147
+ id: originId,
1148
+ table: origin.table,
1149
+ edges: getEdgeArgs(entDefinitions, origin.table)
1150
+ }
1151
+ ]
1152
+ );
1153
+ }
1154
+ });
1155
+ }
1156
+ function getEdgeArgs(entDefinitions, table) {
1157
+ const edges = getEdgeDefinitions(entDefinitions, table);
1158
+ return Object.values(edges).flatMap((edgeDefinition) => {
1159
+ if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "ref" || edgeDefinition.cardinality === "multiple" && edgeDefinition.type === "field") {
1160
+ const table2 = edgeDefinition.to;
1161
+ const targetDeletionConfig = getDeletionConfig(entDefinitions, table2);
1162
+ const targetEdges = getEdgeDefinitions(entDefinitions, table2);
1163
+ const hasCascadingEdges = Object.values(targetEdges).some(
1164
+ (edgeDefinition2) => edgeDefinition2.cardinality === "single" && edgeDefinition2.type === "ref" || edgeDefinition2.cardinality === "multiple"
1165
+ );
1166
+ const approach = targetDeletionConfig !== void 0 && hasCascadingEdges ? "schedule" : hasCascadingEdges ? "deleteOne" : "paginate";
1167
+ const indexName = edgeDefinition.ref;
1168
+ return [{ table: table2, indexName, approach }];
1169
+ } else if (edgeDefinition.cardinality === "multiple") {
1170
+ const table2 = edgeDefinition.table;
1171
+ return [
1172
+ {
1173
+ table: table2,
1174
+ indexName: edgeDefinition.field,
1175
+ approach: "paginate"
1176
+ },
1177
+ ...edgeDefinition.symmetric ? [
1178
+ {
1179
+ table: table2,
1180
+ indexName: edgeDefinition.ref,
1181
+ approach: "paginate"
1182
+ }
1183
+ ] : []
1184
+ ];
1185
+ } else {
1186
+ return [];
1187
+ }
1188
+ });
1189
+ }
1190
+ async function progressScheduledDeletion(ctx, entDefinitions, selfRef, origin, stack) {
1191
+ const last = stack[stack.length - 1];
1192
+ if ("id" in last) {
1193
+ const edgeArgs = last.edges[0];
1194
+ if (edgeArgs === void 0) {
1195
+ await ctx.db.delete(last.id);
1196
+ if (stack.length > 1) {
1197
+ await ctx.scheduler.runAfter(0, selfRef, {
1198
+ origin,
1199
+ stack: stack.slice(0, -1),
1200
+ inProgress: true
1201
+ });
1202
+ }
1203
+ } else {
1204
+ const updated = { ...last, edges: last.edges.slice(1) };
1205
+ await paginate(
1206
+ ctx,
1207
+ entDefinitions,
1208
+ selfRef,
1209
+ origin,
1210
+ stack.slice(0, -1).concat(updated),
1211
+ { cursor: null, fieldValue: last.id, ...edgeArgs }
1212
+ );
1213
+ }
1214
+ } else {
1215
+ await paginate(ctx, entDefinitions, selfRef, origin, stack, last);
1216
+ }
1217
+ }
1218
+ async function paginate(ctx, entDefinitions, selfRef, origin, stack, { table, approach, indexName, fieldValue, cursor }) {
1219
+ const { page, continueCursor, isDone } = await ctx.db.query(table).withIndex(indexName, (q) => q.eq(indexName, fieldValue)).paginate({
1220
+ cursor,
1221
+ ...approach === "paginate" ? { numItems: 8192 / 4, maximumBytesRead: 2 ** 18 } : { numItems: 1 }
1222
+ });
1223
+ const updated = {
1224
+ approach,
1225
+ table,
1226
+ cursor: continueCursor,
1227
+ indexName,
1228
+ fieldValue
1229
+ };
1230
+ const relevantStack = cursor === null ? stack : stack.slice(0, -1);
1231
+ if (approach === "schedule") {
1232
+ await ctx.scheduler.runAfter(0, selfRef, {
1233
+ origin,
1234
+ stack: isDone ? relevantStack : relevantStack.concat([
1235
+ updated,
1236
+ {
1237
+ id: page[0]._id,
1238
+ table,
1239
+ edges: getEdgeArgs(entDefinitions, table)
1240
+ }
1241
+ ]),
1242
+ inProgress: true
1243
+ });
1244
+ } else {
1245
+ if (approach === "deleteOne") {
1246
+ await new WriterImplBase(ctx, entDefinitions, origin.table).deleteId(
1247
+ page[0].id,
1248
+ "hard"
1249
+ );
1250
+ } else {
1251
+ await Promise.all(page.map((doc) => ctx.db.delete(doc._id)));
1252
+ }
1253
+ await ctx.scheduler.runAfter(0, selfRef, {
1254
+ origin,
1255
+ stack: isDone ? relevantStack : relevantStack.concat([updated]),
1256
+ inProgress: true
1257
+ });
1258
+ }
1259
+ }
1260
+ // Annotate the CommonJS export names for ESM import in node:
1261
+ 0 && (module.exports = {
1262
+ scheduledDeleteFactory
1263
+ });
1264
+ //# sourceMappingURL=deletion.js.map