convex-ents 0.1.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.
package/dist/index.js CHANGED
@@ -24,7 +24,8 @@ __export(src_exports, {
24
24
  defineEnt: () => defineEnt,
25
25
  defineEntSchema: () => defineEntSchema,
26
26
  entsTableFactory: () => entsTableFactory,
27
- getEntDefinitions: () => getEntDefinitions
27
+ getEntDefinitions: () => getEntDefinitions,
28
+ scheduledDeleteFactory: () => scheduledDeleteFactory
28
29
  });
29
30
  module.exports = __toCommonJS(src_exports);
30
31
 
@@ -35,7 +36,7 @@ function defineEntSchema(schema, options) {
35
36
  const tableNames = Object.keys(schema);
36
37
  for (const tableName of tableNames) {
37
38
  const table = schema[tableName];
38
- for (const edge of edgeConfigsFromEntDefinition(table)) {
39
+ for (const edge of edgeConfigsBeforeDefineSchema(table)) {
39
40
  if (
40
41
  // Skip inverse edges, we process their forward edges
41
42
  edge.cardinality === "multiple" && edge.type === "ref" && edge.inverse !== void 0 || // symmetric is only set by defineEntSchema,
@@ -52,7 +53,7 @@ function defineEntSchema(schema, options) {
52
53
  );
53
54
  }
54
55
  const isSelfDirected = edge.to === tableName;
55
- const inverseEdgeCandidates = edgeConfigsFromEntDefinition(
56
+ const inverseEdgeCandidates = edgeConfigsBeforeDefineSchema(
56
57
  otherTable
57
58
  ).filter(canBeInverseEdge(tableName, edge, isSelfDirected));
58
59
  if (inverseEdgeCandidates.length > 1) {
@@ -61,6 +62,11 @@ function defineEntSchema(schema, options) {
61
62
  );
62
63
  }
63
64
  const inverseEdge = inverseEdgeCandidates[0];
65
+ if (edge.cardinality === "single" && edge.type === "field" && inverseEdge === void 0) {
66
+ throw new Error(
67
+ `Missing inverse edge in table "${otherTableName}" for edge "${edge.name}" in table "${tableName}"`
68
+ );
69
+ }
64
70
  if (edge.cardinality === "single" && edge.type === "ref") {
65
71
  if (inverseEdge === void 0) {
66
72
  throw new Error(
@@ -82,6 +88,13 @@ function defineEntSchema(schema, options) {
82
88
  }
83
89
  inverseEdge.unique = true;
84
90
  }
91
+ if (edge.cardinality === "single" && edge.type === "ref" || edge.cardinality === "multiple" && edge.type === "field") {
92
+ if (edge.deletion !== void 0 && deletionConfigFromEntDefinition(otherTable) === void 0) {
93
+ throw new Error(
94
+ `Cannot specify soft deletion behavior for edge "${edge.name}" in table "${tableName}" because the target table "${otherTableName}" does not have a "soft" or "scheduled" deletion behavior configured.`
95
+ );
96
+ }
97
+ }
85
98
  if (edge.cardinality === "multiple") {
86
99
  if (!isSelfDirected && inverseEdge === void 0) {
87
100
  throw new Error(
@@ -165,11 +178,14 @@ function canBeInverseEdge(tableName, edge, isSelfDirected) {
165
178
  return true;
166
179
  };
167
180
  }
168
- function edgeConfigsFromEntDefinition(table) {
181
+ function edgeConfigsBeforeDefineSchema(table) {
169
182
  return Object.values(
170
183
  table.edgeConfigs
171
184
  );
172
185
  }
186
+ function deletionConfigFromEntDefinition(table) {
187
+ return table.deletionConfig;
188
+ }
173
189
  function defineEnt(documentSchema) {
174
190
  return new EntDefinitionImpl(documentSchema);
175
191
  }
@@ -187,6 +203,7 @@ var EntDefinitionImpl = class {
187
203
  edgeConfigs = {};
188
204
  fieldConfigs = {};
189
205
  defaults = {};
206
+ deletionConfig;
190
207
  constructor(documentSchema) {
191
208
  this.documentSchema = documentSchema;
192
209
  }
@@ -292,7 +309,8 @@ var EntDefinitionImpl = class {
292
309
  );
293
310
  }
294
311
  const inverseName = options?.inverse;
295
- this.edgeConfigs[name] = ref !== void 0 ? { name, to, cardinality, type: "field", ref } : { name, to, cardinality, type: "ref", table, field, inverseField };
312
+ const deletion = options?.deletion;
313
+ this.edgeConfigs[name] = ref !== void 0 ? { name, to, cardinality, type: "field", ref, deletion } : { name, to, cardinality, type: "ref", table, field, inverseField };
296
314
  if (inverseName !== void 0) {
297
315
  this.edgeConfigs[inverseName] = {
298
316
  name: inverseName,
@@ -305,6 +323,22 @@ var EntDefinitionImpl = class {
305
323
  }
306
324
  return this;
307
325
  }
326
+ deletion(type, options) {
327
+ if (this.documentSchema.deletionTime !== void 0) {
328
+ throw new Error(
329
+ `Cannot enable "${type}" deletion because "deletionTime" field was already defined.`
330
+ );
331
+ }
332
+ if (this.deletionConfig !== void 0) {
333
+ throw new Error(`Deletion behavior can only be specified once.`);
334
+ }
335
+ this.documentSchema = {
336
+ ...this.documentSchema,
337
+ deletionTime: import_values.v.optional(import_values.v.number())
338
+ };
339
+ this.deletionConfig = { type, ...options };
340
+ return this;
341
+ }
308
342
  };
309
343
  function getEntDefinitions(schema) {
310
344
  const tables = schema.tables;
@@ -314,7 +348,8 @@ function getEntDefinitions(schema) {
314
348
  [tableName]: {
315
349
  defaults: tables[tableName].defaults,
316
350
  edges: tables[tableName].edgeConfigs,
317
- fields: tables[tableName].fieldConfigs
351
+ fields: tables[tableName].fieldConfigs,
352
+ deletionConfig: tables[tableName].deletionConfig
318
353
  }
319
354
  }),
320
355
  {}
@@ -322,79 +357,119 @@ function getEntDefinitions(schema) {
322
357
  }
323
358
 
324
359
  // src/writer.ts
325
- var WriterImplBase = class {
326
- constructor(db, entDefinitions, table) {
327
- this.db = db;
360
+ var import_server2 = require("convex/server");
361
+ var WriterImplBase = class _WriterImplBase {
362
+ constructor(ctx, entDefinitions, table) {
363
+ this.ctx = ctx;
328
364
  this.entDefinitions = entDefinitions;
329
365
  this.table = table;
330
366
  }
331
- async writeEdges(docId, changes) {
367
+ async deleteId(id, behavior) {
368
+ await this.checkReadAndWriteRule("delete", id, void 0);
369
+ const deletionConfig = getDeletionConfig(this.entDefinitions, this.table);
370
+ const isDeletingSoftly = behavior !== "hard" && deletionConfig !== void 0 && (deletionConfig.type === "soft" || deletionConfig.type === "scheduled");
371
+ if (behavior === "soft" && !isDeletingSoftly) {
372
+ throw new Error(
373
+ `Cannot soft delete document with ID "${id}" in table "${this.table}" because it does not have an "allowSoft", "soft" or "scheduled" deletion behavior configured.`
374
+ );
375
+ }
376
+ const edges = {};
332
377
  await Promise.all(
333
- Object.values(
334
- this.entDefinitions[this.table].edges
335
- ).map(async (edgeDefinition) => {
336
- const idOrIds = changes[edgeDefinition.name];
337
- if (idOrIds === void 0) {
338
- return;
339
- }
340
- if (edgeDefinition.cardinality === "single") {
341
- if (edgeDefinition.type === "ref") {
342
- if (idOrIds.remove !== void 0) {
343
- await this.db.delete(idOrIds.remove);
378
+ Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map(
379
+ async (edgeDefinition) => {
380
+ const key = edgeDefinition.name;
381
+ if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "ref" || edgeDefinition.cardinality === "multiple" && edgeDefinition.type === "field") {
382
+ if (!isDeletingSoftly || edgeDefinition.deletion === "soft") {
383
+ const remove = (await this.ctx.db.query(edgeDefinition.to).withIndex(
384
+ edgeDefinition.ref,
385
+ (q) => q.eq(edgeDefinition.ref, id)
386
+ ).collect()).map((doc) => doc._id);
387
+ edges[key] = { remove };
344
388
  }
345
- if (idOrIds.add !== void 0) {
346
- await this.db.patch(
347
- idOrIds.add,
348
- { [edgeDefinition.ref]: docId }
349
- );
389
+ } else if (edgeDefinition.cardinality === "multiple") {
390
+ if (!isDeletingSoftly) {
391
+ const removeEdges = (await this.ctx.db.query(edgeDefinition.table).withIndex(
392
+ edgeDefinition.field,
393
+ (q) => q.eq(edgeDefinition.field, id)
394
+ ).collect()).concat(
395
+ edgeDefinition.symmetric ? await this.ctx.db.query(edgeDefinition.table).withIndex(
396
+ edgeDefinition.ref,
397
+ (q) => q.eq(edgeDefinition.ref, id)
398
+ ).collect() : []
399
+ ).map((doc) => doc._id);
400
+ edges[key] = { removeEdges };
350
401
  }
351
402
  }
352
- } else {
353
- if (edgeDefinition.type === "field") {
354
- if (idOrIds.remove !== void 0) {
403
+ }
404
+ )
405
+ );
406
+ const deletionTime = +/* @__PURE__ */ new Date();
407
+ if (isDeletingSoftly) {
408
+ await this.ctx.db.patch(id, { deletionTime });
409
+ } else {
410
+ try {
411
+ await this.ctx.db.delete(id);
412
+ } catch (e) {
413
+ }
414
+ }
415
+ await this.writeEdges(id, edges, isDeletingSoftly);
416
+ if (deletionConfig !== void 0 && deletionConfig.type === "scheduled") {
417
+ const fnRef = this.ctx.scheduledDelete ?? (0, import_server2.makeFunctionReference)(
418
+ "functions:scheduledDelete"
419
+ );
420
+ await this.ctx.scheduler.runAfter(deletionConfig.delayMs ?? 0, fnRef, {
421
+ origin: {
422
+ id,
423
+ table: this.table,
424
+ deletionTime
425
+ },
426
+ inProgress: false,
427
+ stack: []
428
+ });
429
+ }
430
+ return id;
431
+ }
432
+ async deletedIdIn(id, table, cascadingSoft) {
433
+ await new _WriterImplBase(this.ctx, this.entDefinitions, table).deleteId(
434
+ id,
435
+ cascadingSoft ? "soft" : "hard"
436
+ );
437
+ }
438
+ async writeEdges(docId, changes, deleteSoftly) {
439
+ await Promise.all(
440
+ Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map(
441
+ async (edgeDefinition) => {
442
+ const idOrIds = changes[edgeDefinition.name];
443
+ if (idOrIds === void 0) {
444
+ return;
445
+ }
446
+ if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "ref" || edgeDefinition.cardinality === "multiple" && edgeDefinition.type === "field") {
447
+ if (idOrIds.remove !== void 0 && idOrIds.remove.length > 0) {
355
448
  await Promise.all(
356
449
  idOrIds.remove.map(
357
- (id) => this.db.delete(id)
450
+ (id) => this.deletedIdIn(
451
+ id,
452
+ edgeDefinition.to,
453
+ (deleteSoftly ?? false) && edgeDefinition.deletion === "soft"
454
+ )
358
455
  )
359
456
  );
360
457
  }
361
- if (idOrIds.add !== void 0) {
458
+ if (idOrIds.add !== void 0 && idOrIds.add.length > 0) {
362
459
  await Promise.all(
363
460
  idOrIds.add.map(
364
- async (id) => this.db.patch(id, {
461
+ async (id) => this.ctx.db.patch(id, {
365
462
  [edgeDefinition.ref]: docId
366
463
  })
367
464
  )
368
465
  );
369
466
  }
370
- } else {
371
- let removeEdges = [];
372
- if (idOrIds.remove !== void 0) {
373
- removeEdges = (await Promise.all(
374
- idOrIds.remove.map(
375
- async (id) => (await this.db.query(edgeDefinition.table).withIndex(
376
- edgeDefinition.field,
377
- (q) => q.eq(edgeDefinition.field, docId).eq(
378
- edgeDefinition.ref,
379
- id
380
- )
381
- ).collect()).concat(
382
- edgeDefinition.symmetric ? await this.db.query(edgeDefinition.table).withIndex(
383
- edgeDefinition.ref,
384
- (q) => q.eq(edgeDefinition.ref, docId).eq(edgeDefinition.field, id)
385
- ).collect() : []
386
- )
387
- )
388
- )).map((doc) => doc._id);
389
- }
390
- if (idOrIds.removeEdges !== void 0) {
391
- removeEdges = idOrIds.removeEdges;
392
- }
393
- if (removeEdges.length > 0) {
467
+ } else if (edgeDefinition.cardinality === "multiple") {
468
+ if ((idOrIds.removeEdges ?? []).length > 0) {
394
469
  await Promise.all(
395
- removeEdges.map(async (id) => {
470
+ idOrIds.removeEdges.map(async (id) => {
396
471
  try {
397
- await this.db.delete(id);
472
+ await this.ctx.db.delete(id);
398
473
  } catch (e) {
399
474
  }
400
475
  })
@@ -403,12 +478,12 @@ var WriterImplBase = class {
403
478
  if (idOrIds.add !== void 0) {
404
479
  await Promise.all(
405
480
  idOrIds.add.map(async (id) => {
406
- await this.db.insert(edgeDefinition.table, {
481
+ await this.ctx.db.insert(edgeDefinition.table, {
407
482
  [edgeDefinition.field]: docId,
408
483
  [edgeDefinition.ref]: id
409
484
  });
410
485
  if (edgeDefinition.symmetric) {
411
- await this.db.insert(edgeDefinition.table, {
486
+ await this.ctx.db.insert(edgeDefinition.table, {
412
487
  [edgeDefinition.field]: id,
413
488
  [edgeDefinition.ref]: docId
414
489
  });
@@ -418,7 +493,7 @@ var WriterImplBase = class {
418
493
  }
419
494
  }
420
495
  }
421
- })
496
+ )
422
497
  );
423
498
  }
424
499
  async checkUniqueness(value, id) {
@@ -429,7 +504,7 @@ var WriterImplBase = class {
429
504
  if (fieldDefinition.unique) {
430
505
  const key = fieldDefinition.name;
431
506
  const fieldValue = value[key];
432
- const existing = await this.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique();
507
+ const existing = await this.ctx.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique();
433
508
  if (existing !== null && (id === void 0 || existing._id !== id)) {
434
509
  throw new Error(
435
510
  `In table "${this.table}" cannot create a duplicate document with field "${key}" of value \`${fieldValue}\`, existing document with ID "${existing._id}" already has it.`
@@ -439,28 +514,31 @@ var WriterImplBase = class {
439
514
  })
440
515
  );
441
516
  await Promise.all(
442
- Object.values(
443
- this.entDefinitions[this.table].edges
444
- ).map(async (edgeDefinition) => {
445
- if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "field" && edgeDefinition.unique) {
446
- const key = edgeDefinition.field;
447
- if (value[key] === void 0) {
448
- return;
449
- }
450
- const existing = await this.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique();
451
- if (existing !== null && (id === void 0 || existing._id !== id)) {
452
- throw new Error(
453
- `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.`
454
- );
517
+ Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map(
518
+ async (edgeDefinition) => {
519
+ if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "field" && edgeDefinition.unique) {
520
+ const key = edgeDefinition.field;
521
+ if (value[key] === void 0) {
522
+ return;
523
+ }
524
+ const existing = await this.ctx.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique();
525
+ if (existing !== null && (id === void 0 || existing._id !== id)) {
526
+ throw new Error(
527
+ `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.`
528
+ );
529
+ }
455
530
  }
456
531
  }
457
- })
532
+ )
458
533
  );
459
534
  }
460
535
  fieldsOnly(value) {
461
536
  const fields = {};
462
537
  Object.keys(value).forEach((key) => {
463
- const edgeDefinition = this.entDefinitions[this.table].edges[key];
538
+ const edgeDefinition = getEdgeDefinitions(
539
+ this.entDefinitions,
540
+ this.table
541
+ )[key];
464
542
  if (edgeDefinition === void 0) {
465
543
  fields[key] = value[key];
466
544
  }
@@ -471,7 +549,7 @@ var WriterImplBase = class {
471
549
  if (id !== void 0) {
472
550
  const readPolicy = getReadRule(this.entDefinitions, this.table);
473
551
  if (readPolicy !== void 0) {
474
- const doc = await this.db.get(id);
552
+ const doc = await this.ctx.db.get(id);
475
553
  if (doc === null) {
476
554
  throw new Error(
477
555
  `Cannot update document with ID "${id}" in table "${this.table} because it does not exist"`
@@ -490,8 +568,8 @@ var WriterImplBase = class {
490
568
  return;
491
569
  }
492
570
  const ent = id === void 0 ? void 0 : entWrapper(
493
- await this.db.get(id),
494
- this.db,
571
+ await this.ctx.db.get(id),
572
+ this.ctx,
495
573
  this.entDefinitions,
496
574
  this.table
497
575
  );
@@ -523,17 +601,17 @@ var WriterImplBase = class {
523
601
 
524
602
  // src/functions.ts
525
603
  var PromiseQueryOrNullImpl = class _PromiseQueryOrNullImpl extends Promise {
526
- constructor(db, entDefinitions, table, retrieve) {
604
+ constructor(ctx, entDefinitions, table, retrieve) {
527
605
  super(() => {
528
606
  });
529
- this.db = db;
607
+ this.ctx = ctx;
530
608
  this.entDefinitions = entDefinitions;
531
609
  this.table = table;
532
610
  this.retrieve = retrieve;
533
611
  }
534
612
  filter(predicate) {
535
613
  return new _PromiseQueryOrNullImpl(
536
- this.db,
614
+ this.ctx,
537
615
  this.entDefinitions,
538
616
  this.table,
539
617
  async () => {
@@ -545,16 +623,18 @@ var PromiseQueryOrNullImpl = class _PromiseQueryOrNullImpl extends Promise {
545
623
  }
546
624
  );
547
625
  }
548
- async map(callbackFn) {
549
- const array = await this;
550
- if (array === null) {
551
- return [];
552
- }
553
- return await Promise.all(array.map(callbackFn));
626
+ map(callbackFn) {
627
+ return new PromiseArrayImpl(async () => {
628
+ const array = await this;
629
+ if (array === null) {
630
+ return null;
631
+ }
632
+ return await Promise.all(array.map(callbackFn));
633
+ });
554
634
  }
555
635
  order(order, indexName) {
556
636
  return new _PromiseQueryOrNullImpl(
557
- this.db,
637
+ this.ctx,
558
638
  this.entDefinitions,
559
639
  this.table,
560
640
  async () => {
@@ -569,17 +649,18 @@ var PromiseQueryOrNullImpl = class _PromiseQueryOrNullImpl extends Promise {
569
649
  }
570
650
  );
571
651
  }
572
- // TODO: RLS for pagination
573
- async paginate(paginationOpts) {
574
- const query = await this.retrieve();
575
- if (query === null) {
576
- return null;
577
- }
578
- return await query.paginate(paginationOpts);
652
+ paginate(paginationOpts) {
653
+ return new PromisePaginationResultOrNullImpl(
654
+ this.ctx,
655
+ this.entDefinitions,
656
+ this.table,
657
+ this.retrieve,
658
+ paginationOpts
659
+ );
579
660
  }
580
661
  take(n) {
581
662
  return new PromiseEntsOrNullImpl(
582
- this.db,
663
+ this.ctx,
583
664
  this.entDefinitions,
584
665
  this.table,
585
666
  async () => {
@@ -590,7 +671,7 @@ var PromiseQueryOrNullImpl = class _PromiseQueryOrNullImpl extends Promise {
590
671
  }
591
672
  first() {
592
673
  return new PromiseEntOrNullImpl(
593
- this.db,
674
+ this.ctx,
594
675
  this.entDefinitions,
595
676
  this.table,
596
677
  async () => {
@@ -606,7 +687,7 @@ var PromiseQueryOrNullImpl = class _PromiseQueryOrNullImpl extends Promise {
606
687
  }
607
688
  firstX() {
608
689
  return new PromiseEntWriterImpl(
609
- this.db,
690
+ this.ctx,
610
691
  this.entDefinitions,
611
692
  this.table,
612
693
  async () => {
@@ -625,7 +706,7 @@ var PromiseQueryOrNullImpl = class _PromiseQueryOrNullImpl extends Promise {
625
706
  }
626
707
  unique() {
627
708
  return new PromiseEntOrNullImpl(
628
- this.db,
709
+ this.ctx,
629
710
  this.entDefinitions,
630
711
  this.table,
631
712
  async () => {
@@ -647,7 +728,7 @@ var PromiseQueryOrNullImpl = class _PromiseQueryOrNullImpl extends Promise {
647
728
  }
648
729
  uniqueX() {
649
730
  return new PromiseEntWriterImpl(
650
- this.db,
731
+ this.ctx,
651
732
  this.entDefinitions,
652
733
  this.table,
653
734
  async () => {
@@ -674,7 +755,7 @@ var PromiseQueryOrNullImpl = class _PromiseQueryOrNullImpl extends Promise {
674
755
  }
675
756
  const docs = await query.collect();
676
757
  return filterByReadRule(
677
- this.db,
758
+ this.ctx,
678
759
  this.entDefinitions,
679
760
  this.table,
680
761
  docs,
@@ -684,7 +765,7 @@ var PromiseQueryOrNullImpl = class _PromiseQueryOrNullImpl extends Promise {
684
765
  then(onfulfilled, onrejected) {
685
766
  return this.docs().then(
686
767
  (documents) => documents === null ? null : documents.map(
687
- (doc) => entWrapper(doc, this.db, this.entDefinitions, this.table)
768
+ (doc) => entWrapper(doc, this.ctx, this.entDefinitions, this.table)
688
769
  )
689
770
  ).then(onfulfilled, onrejected);
690
771
  }
@@ -713,7 +794,7 @@ var PromiseQueryOrNullImpl = class _PromiseQueryOrNullImpl extends Promise {
713
794
  }
714
795
  docs.push(
715
796
  ...(await filterByReadRule(
716
- this.db,
797
+ this.ctx,
717
798
  this.entDefinitions,
718
799
  this.table,
719
800
  page,
@@ -725,9 +806,57 @@ var PromiseQueryOrNullImpl = class _PromiseQueryOrNullImpl extends Promise {
725
806
  return docs;
726
807
  }
727
808
  };
809
+ var PromisePaginationResultOrNullImpl = class extends Promise {
810
+ constructor(ctx, entDefinitions, table, retrieve, paginationOpts) {
811
+ super(() => {
812
+ });
813
+ this.ctx = ctx;
814
+ this.entDefinitions = entDefinitions;
815
+ this.table = table;
816
+ this.retrieve = retrieve;
817
+ this.paginationOpts = paginationOpts;
818
+ }
819
+ async map(callbackFn) {
820
+ const result = await this;
821
+ if (result === null) {
822
+ return null;
823
+ }
824
+ return {
825
+ ...result,
826
+ page: await Promise.all(result.page.map(callbackFn))
827
+ };
828
+ }
829
+ async docs() {
830
+ const query = await this.retrieve();
831
+ if (query === null) {
832
+ return null;
833
+ }
834
+ const result = await query.paginate(this.paginationOpts);
835
+ return {
836
+ ...result,
837
+ page: await filterByReadRule(
838
+ this.ctx,
839
+ this.entDefinitions,
840
+ this.table,
841
+ result.page,
842
+ false
843
+ )
844
+ };
845
+ }
846
+ then(onfulfilled, onrejected) {
847
+ return this.docs().then(
848
+ (result) => result === null ? null : {
849
+ ...result,
850
+ page: result.page.map(
851
+ (doc) => entWrapper(doc, this.ctx, this.entDefinitions, this.table)
852
+ )
853
+ }
854
+ ).then(onfulfilled, onrejected);
855
+ }
856
+ };
728
857
  var PromiseTableImpl = class extends PromiseQueryOrNullImpl {
729
- constructor(db, entDefinitions, table) {
730
- super(db, entDefinitions, table, async () => db.query(table));
858
+ constructor(ctx, entDefinitions, table) {
859
+ super(ctx, entDefinitions, table, async () => ctx.db.query(table));
731
860
  }
732
861
  get(...args) {
733
862
  return this.getImpl(args);
@@ -743,18 +872,18 @@ var PromiseTableImpl = class extends PromiseQueryOrNullImpl {
743
872
  }
744
873
  getImpl(args, throwIfNull = false) {
745
874
  return new PromiseEntWriterImpl(
746
- this.db,
875
+ this.ctx,
747
876
  this.entDefinitions,
748
877
  this.table,
749
878
  args.length === 1 ? async () => {
750
879
  const id = args[0];
751
- if (this.db.normalizeId(this.table, id) === null) {
880
+ if (this.ctx.db.normalizeId(this.table, id) === null) {
752
881
  throw new Error(`Invalid id \`${id}\` for table "${this.table}"`);
753
882
  }
754
883
  return {
755
884
  id,
756
885
  doc: async () => {
757
- const doc = await this.db.get(id);
886
+ const doc = await this.ctx.db.get(id);
758
887
  if (throwIfNull && doc === null) {
759
888
  throw new Error(
760
889
  `Document not found with id \`${id}\` in table "${this.table}"`
@@ -765,7 +894,7 @@ var PromiseTableImpl = class extends PromiseQueryOrNullImpl {
765
894
  };
766
895
  } : async () => {
767
896
  const [indexName, value] = args;
768
- const doc = await this.db.query(this.table).withIndex(indexName, (q) => q.eq(indexName, value)).unique();
897
+ const doc = await this.ctx.db.query(this.table).withIndex(indexName, (q) => q.eq(indexName, value)).unique();
769
898
  if (throwIfNull && doc === null) {
770
899
  throw new Error(
771
900
  `Table "${this.table}" does not contain document with field "${indexName}" = \`${value}\``
@@ -778,13 +907,13 @@ var PromiseTableImpl = class extends PromiseQueryOrNullImpl {
778
907
  }
779
908
  getManyImpl(args, throwIfNull = false) {
780
909
  return new PromiseEntsOrNullImpl(
781
- this.db,
910
+ this.ctx,
782
911
  this.entDefinitions,
783
912
  this.table,
784
913
  args.length === 1 ? async () => {
785
914
  const ids = args[0];
786
915
  ids.forEach((id) => {
787
- if (this.db.normalizeId(this.table, id) === null) {
916
+ if (this.ctx.db.normalizeId(this.table, id) === null) {
788
917
  throw new Error(
789
918
  `Invalid id \`${id}\` for table "${this.table}"`
790
919
  );
@@ -792,7 +921,7 @@ var PromiseTableImpl = class extends PromiseQueryOrNullImpl {
792
921
  });
793
922
  return await Promise.all(
794
923
  ids.map(async (id) => {
795
- const doc = await this.db.get(id);
924
+ const doc = await this.ctx.db.get(id);
796
925
  if (doc === null) {
797
926
  throw new Error(
798
927
  `Document not found with id \`${id}\` in table "${this.table}"`
@@ -805,7 +934,7 @@ var PromiseTableImpl = class extends PromiseQueryOrNullImpl {
805
934
  const [indexName, values] = args;
806
935
  return await Promise.all(
807
936
  values.map(async (value) => {
808
- const doc = await this.db.query(this.table).withIndex(indexName, (q) => q.eq(indexName, value)).unique();
937
+ const doc = await this.ctx.db.query(this.table).withIndex(indexName, (q) => q.eq(indexName, value)).unique();
809
938
  if (throwIfNull && doc === null) {
810
939
  throw new Error(
811
940
  `Table "${this.table}" does not contain document with field "${indexName}" = \`${value}\``
@@ -819,7 +948,7 @@ var PromiseTableImpl = class extends PromiseQueryOrNullImpl {
819
948
  );
820
949
  }
821
950
  normalizeId(id) {
822
- return this.db.normalizeId(this.table, id);
951
+ return this.ctx.db.normalizeId(this.table, id);
823
952
  }
824
953
  // normalizeId or throw
825
954
  normalizeIdX(id) {
@@ -831,7 +960,7 @@ var PromiseTableImpl = class extends PromiseQueryOrNullImpl {
831
960
  }
832
961
  withIndex(indexName, indexRange) {
833
962
  return new PromiseQueryOrNullImpl(
834
- this.db,
963
+ this.ctx,
835
964
  this.entDefinitions,
836
965
  this.table,
837
966
  async () => {
@@ -842,7 +971,7 @@ var PromiseTableImpl = class extends PromiseQueryOrNullImpl {
842
971
  }
843
972
  search(indexName, searchFilter) {
844
973
  return new PromiseQueryOrNullImpl(
845
- this.db,
974
+ this.ctx,
846
975
  this.entDefinitions,
847
976
  this.table,
848
977
  async () => {
@@ -853,25 +982,27 @@ var PromiseTableImpl = class extends PromiseQueryOrNullImpl {
853
982
  }
854
983
  };
855
984
  var PromiseEntsOrNullImpl = class extends Promise {
856
- constructor(db, entDefinitions, table, retrieve, throwIfNull) {
985
+ constructor(ctx, entDefinitions, table, retrieve, throwIfNull) {
857
986
  super(() => {
858
987
  });
859
- this.db = db;
988
+ this.ctx = ctx;
860
989
  this.entDefinitions = entDefinitions;
861
990
  this.table = table;
862
991
  this.retrieve = retrieve;
863
992
  this.throwIfNull = throwIfNull;
864
993
  }
865
- async map(callbackFn) {
866
- const array = await this;
867
- if (array === null) {
868
- return [];
869
- }
870
- return await Promise.all(array.map(callbackFn));
994
+ map(callbackFn) {
995
+ return new PromiseArrayImpl(async () => {
996
+ const array = await this;
997
+ if (array === null) {
998
+ return null;
999
+ }
1000
+ return await Promise.all(array.map(callbackFn));
1001
+ });
871
1002
  }
872
1003
  first() {
873
1004
  return new PromiseEntOrNullImpl(
874
- this.db,
1005
+ this.ctx,
875
1006
  this.entDefinitions,
876
1007
  this.table,
877
1008
  async () => {
@@ -886,7 +1017,7 @@ var PromiseEntsOrNullImpl = class extends Promise {
886
1017
  }
887
1018
  firstX() {
888
1019
  return new PromiseEntOrNullImpl(
889
- this.db,
1020
+ this.ctx,
890
1021
  this.entDefinitions,
891
1022
  this.table,
892
1023
  async () => {
@@ -905,7 +1036,7 @@ var PromiseEntsOrNullImpl = class extends Promise {
905
1036
  }
906
1037
  unique() {
907
1038
  return new PromiseEntOrNullImpl(
908
- this.db,
1039
+ this.ctx,
909
1040
  this.entDefinitions,
910
1041
  this.table,
911
1042
  async () => {
@@ -923,7 +1054,7 @@ var PromiseEntsOrNullImpl = class extends Promise {
923
1054
  }
924
1055
  uniqueX() {
925
1056
  return new PromiseEntOrNullImpl(
926
- this.db,
1057
+ this.ctx,
927
1058
  this.entDefinitions,
928
1059
  this.table,
929
1060
  async () => {
@@ -945,7 +1076,7 @@ var PromiseEntsOrNullImpl = class extends Promise {
945
1076
  async docs() {
946
1077
  const docs = await this.retrieve();
947
1078
  return filterByReadRule(
948
- this.db,
1079
+ this.ctx,
949
1080
  this.entDefinitions,
950
1081
  this.table,
951
1082
  docs,
@@ -955,14 +1086,14 @@ var PromiseEntsOrNullImpl = class extends Promise {
955
1086
  then(onfulfilled, onrejected) {
956
1087
  return this.docs().then(
957
1088
  (docs) => docs === null ? null : docs.map(
958
- (doc) => entWrapper(doc, this.db, this.entDefinitions, this.table)
1089
+ (doc) => entWrapper(doc, this.ctx, this.entDefinitions, this.table)
959
1090
  )
960
1091
  ).then(onfulfilled, onrejected);
961
1092
  }
962
1093
  };
963
1094
  var PromiseEdgeOrNullImpl = class extends PromiseEntsOrNullImpl {
964
- constructor(db, entDefinitions, table, field, retrieveRange) {
965
- super(db, entDefinitions, table, () => retrieveRange((q) => q), false);
1095
+ constructor(ctx, entDefinitions, table, field, retrieveRange) {
1096
+ super(ctx, entDefinitions, table, () => retrieveRange((q) => q), false);
966
1097
  this.field = field;
967
1098
  this.retrieveRange = retrieveRange;
968
1099
  }
@@ -972,10 +1103,10 @@ var PromiseEdgeOrNullImpl = class extends PromiseEntsOrNullImpl {
972
1103
  }
973
1104
  };
974
1105
  var PromiseEntOrNullImpl = class _PromiseEntOrNullImpl extends Promise {
975
- constructor(db, entDefinitions, table, retrieve, throwIfNull) {
1106
+ constructor(ctx, entDefinitions, table, retrieve, throwIfNull) {
976
1107
  super(() => {
977
1108
  });
978
- this.db = db;
1109
+ this.ctx = ctx;
979
1110
  this.entDefinitions = entDefinitions;
980
1111
  this.table = table;
981
1112
  this.retrieve = retrieve;
@@ -993,7 +1124,7 @@ var PromiseEntOrNullImpl = class _PromiseEntOrNullImpl extends Promise {
993
1124
  const readPolicy = getReadRule(this.entDefinitions, this.table);
994
1125
  if (readPolicy !== void 0) {
995
1126
  const decision = await readPolicy(
996
- entWrapper(doc, this.db, this.entDefinitions, this.table)
1127
+ entWrapper(doc, this.ctx, this.entDefinitions, this.table)
997
1128
  );
998
1129
  if (this.throwIfNull && !decision) {
999
1130
  throw new Error(
@@ -1006,7 +1137,7 @@ var PromiseEntOrNullImpl = class _PromiseEntOrNullImpl extends Promise {
1006
1137
  }
1007
1138
  then(onfulfilled, onrejected) {
1008
1139
  return this.doc().then(
1009
- (doc) => doc === null ? null : entWrapper(doc, this.db, this.entDefinitions, this.table)
1140
+ (doc) => doc === null ? null : entWrapper(doc, this.ctx, this.entDefinitions, this.table)
1010
1141
  ).then(onfulfilled, onrejected);
1011
1142
  }
1012
1143
  edge(edge) {
@@ -1016,11 +1147,11 @@ var PromiseEntOrNullImpl = class _PromiseEntOrNullImpl extends Promise {
1016
1147
  return this.edgeImpl(edge, true);
1017
1148
  }
1018
1149
  edgeImpl(edge, throwIfNull = false) {
1019
- const edgeDefinition = this.entDefinitions[this.table].edges[edge];
1150
+ const edgeDefinition = getEdgeDefinitions(this.entDefinitions, this.table)[edge];
1020
1151
  if (edgeDefinition.cardinality === "multiple") {
1021
1152
  if (edgeDefinition.type === "ref") {
1022
1153
  return new PromiseEdgeOrNullImpl(
1023
- this.db,
1154
+ this.ctx,
1024
1155
  this.entDefinitions,
1025
1156
  edgeDefinition.to,
1026
1157
  edgeDefinition.ref,
@@ -1029,13 +1160,13 @@ var PromiseEntOrNullImpl = class _PromiseEntOrNullImpl extends Promise {
1029
1160
  if (id === null) {
1030
1161
  return null;
1031
1162
  }
1032
- const edgeDocs = await this.db.query(edgeDefinition.table).withIndex(
1163
+ const edgeDocs = await this.ctx.db.query(edgeDefinition.table).withIndex(
1033
1164
  edgeDefinition.field,
1034
1165
  (q) => indexRange(q.eq(edgeDefinition.field, id))
1035
1166
  ).collect();
1036
1167
  return (await Promise.all(
1037
1168
  edgeDocs.map(
1038
- (edgeDoc) => this.db.get(edgeDoc[edgeDefinition.ref])
1169
+ (edgeDoc) => this.ctx.db.get(edgeDoc[edgeDefinition.ref])
1039
1170
  )
1040
1171
  )).filter((doc, i) => {
1041
1172
  if (doc === null) {
@@ -1049,7 +1180,7 @@ var PromiseEntOrNullImpl = class _PromiseEntOrNullImpl extends Promise {
1049
1180
  );
1050
1181
  }
1051
1182
  return new PromiseQueryOrNullImpl(
1052
- this.db,
1183
+ this.ctx,
1053
1184
  this.entDefinitions,
1054
1185
  edgeDefinition.to,
1055
1186
  async () => {
@@ -1057,7 +1188,7 @@ var PromiseEntOrNullImpl = class _PromiseEntOrNullImpl extends Promise {
1057
1188
  if (id === null) {
1058
1189
  return null;
1059
1190
  }
1060
- return this.db.query(edgeDefinition.to).withIndex(
1191
+ return this.ctx.db.query(edgeDefinition.to).withIndex(
1061
1192
  edgeDefinition.ref,
1062
1193
  (q) => q.eq(edgeDefinition.ref, id)
1063
1194
  );
@@ -1065,7 +1196,7 @@ var PromiseEntOrNullImpl = class _PromiseEntOrNullImpl extends Promise {
1065
1196
  );
1066
1197
  }
1067
1198
  return new _PromiseEntOrNullImpl(
1068
- this.db,
1199
+ this.ctx,
1069
1200
  this.entDefinitions,
1070
1201
  edgeDefinition.to,
1071
1202
  async () => {
@@ -1074,7 +1205,7 @@ var PromiseEntOrNullImpl = class _PromiseEntOrNullImpl extends Promise {
1074
1205
  return nullRetriever;
1075
1206
  }
1076
1207
  if (edgeDefinition.type === "ref") {
1077
- const otherDoc = await this.db.query(edgeDefinition.to).withIndex(
1208
+ const otherDoc = await this.ctx.db.query(edgeDefinition.to).withIndex(
1078
1209
  edgeDefinition.ref,
1079
1210
  (q) => q.eq(edgeDefinition.ref, id)
1080
1211
  ).unique();
@@ -1090,7 +1221,7 @@ var PromiseEntOrNullImpl = class _PromiseEntOrNullImpl extends Promise {
1090
1221
  return {
1091
1222
  id: otherId,
1092
1223
  doc: async () => {
1093
- const otherDoc = await this.db.get(otherId);
1224
+ const otherDoc = await this.ctx.db.get(otherId);
1094
1225
  if (otherDoc === null) {
1095
1226
  throw new Error(
1096
1227
  `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}".`
@@ -1104,10 +1235,27 @@ var PromiseEntOrNullImpl = class _PromiseEntOrNullImpl extends Promise {
1104
1235
  );
1105
1236
  }
1106
1237
  };
1107
- function entWrapper(fields, db, entDefinitions, table) {
1238
+ var PromiseArrayImpl = class extends Promise {
1239
+ constructor(retrieve) {
1240
+ super(() => {
1241
+ });
1242
+ this.retrieve = retrieve;
1243
+ }
1244
+ async filter(predicate) {
1245
+ const array = await this.retrieve();
1246
+ if (array === null) {
1247
+ return null;
1248
+ }
1249
+ return array.filter(predicate);
1250
+ }
1251
+ then(onfulfilled, onrejected) {
1252
+ return this.retrieve().then(onfulfilled, onrejected);
1253
+ }
1254
+ };
1255
+ function entWrapper(fields, ctx, entDefinitions, table) {
1108
1256
  const doc = { ...fields };
1109
1257
  const queryInterface = new PromiseEntWriterImpl(
1110
- db,
1258
+ ctx,
1111
1259
  entDefinitions,
1112
1260
  table,
1113
1261
  async () => ({ id: doc._id, doc: async () => doc }),
@@ -1130,6 +1278,14 @@ function entWrapper(fields, db, entDefinitions, table) {
1130
1278
  writable: false,
1131
1279
  configurable: false
1132
1280
  });
1281
+ Object.defineProperty(doc, "doc", {
1282
+ value: () => {
1283
+ return doc;
1284
+ },
1285
+ enumerable: false,
1286
+ writable: false,
1287
+ configurable: false
1288
+ });
1133
1289
  Object.defineProperty(doc, "patch", {
1134
1290
  value: (value) => {
1135
1291
  return queryInterface.patch(value);
@@ -1163,54 +1319,58 @@ function entWrapper(fields, db, entDefinitions, table) {
1163
1319
  );
1164
1320
  return doc;
1165
1321
  }
1166
- function entsTableFactory(db, entDefinitions) {
1322
+ function entsTableFactory(ctx, entDefinitions, options) {
1323
+ const enrichedCtx = options !== void 0 ? { ...ctx, ...options } : ctx;
1167
1324
  return (table, indexName, indexRange) => {
1168
1325
  if (typeof table !== "string") {
1169
1326
  throw new Error(`Expected table name, got \`${table}\``);
1170
1327
  }
1171
1328
  if (indexName !== void 0) {
1172
- return new PromiseTableImpl(db, entDefinitions, table).withIndex(
1173
- indexName,
1174
- indexRange
1175
- );
1329
+ return new PromiseTableImpl(
1330
+ enrichedCtx,
1331
+ entDefinitions,
1332
+ table
1333
+ ).withIndex(indexName, indexRange);
1176
1334
  }
1177
- if (db.insert !== void 0) {
1335
+ if (ctx.db.insert !== void 0) {
1178
1336
  return new PromiseTableWriterImpl(
1179
- db,
1337
+ enrichedCtx,
1180
1338
  entDefinitions,
1181
1339
  table
1182
1340
  );
1183
1341
  }
1184
- return new PromiseTableImpl(db, entDefinitions, table);
1342
+ return new PromiseTableImpl(enrichedCtx, entDefinitions, table);
1185
1343
  };
1186
1344
  }
1187
1345
  var PromiseTableWriterImpl = class extends PromiseTableImpl {
1188
- constructor(db, entDefinitions, table) {
1189
- super(db, entDefinitions, table);
1190
- this.db = db;
1191
- this.base = new WriterImplBase(db, entDefinitions, table);
1346
+ constructor(ctx, entDefinitions, table) {
1347
+ super(ctx, entDefinitions, table);
1348
+ this.ctx = ctx;
1349
+ this.base = new WriterImplBase(ctx, entDefinitions, table);
1192
1350
  }
1193
1351
  base;
1194
1352
  insert(value) {
1195
1353
  return new PromiseEntIdImpl(
1196
- this.db,
1354
+ this.ctx,
1197
1355
  this.entDefinitions,
1198
1356
  this.table,
1199
1357
  async () => {
1200
1358
  await this.base.checkReadAndWriteRule("create", void 0, value);
1201
1359
  await this.base.checkUniqueness(value);
1202
1360
  const fields = this.base.fieldsOnly(value);
1203
- const docId = await this.db.insert(this.table, fields);
1361
+ const docId = await this.ctx.db.insert(this.table, fields);
1204
1362
  const edges = {};
1205
1363
  Object.keys(value).forEach((key) => {
1206
- const edgeDefinition = this.entDefinitions[this.table].edges[key];
1364
+ const edgeDefinition = getEdgeDefinitions(
1365
+ this.entDefinitions,
1366
+ this.table
1367
+ )[key];
1207
1368
  if (edgeDefinition === void 0 || edgeDefinition.cardinality === "single" && edgeDefinition.type === "field") {
1208
1369
  return;
1209
1370
  }
1210
- if (edgeDefinition.cardinality === "single") {
1211
- throw new Error("Cannot set 1:1 edge from optional end.");
1212
- }
1213
- edges[key] = { add: value[key] };
1371
+ edges[key] = {
1372
+ add: edgeDefinition.cardinality === "single" ? [value[key]] : value[key]
1373
+ };
1214
1374
  });
1215
1375
  await this.base.writeEdges(docId, edges);
1216
1376
  return docId;
@@ -1223,19 +1383,19 @@ var PromiseTableWriterImpl = class extends PromiseTableImpl {
1223
1383
  }
1224
1384
  };
1225
1385
  var PromiseEntWriterImpl = class extends PromiseEntOrNullImpl {
1226
- constructor(db, entDefinitions, table, retrieve, throwIfNull) {
1227
- super(db, entDefinitions, table, retrieve, throwIfNull);
1228
- this.db = db;
1386
+ constructor(ctx, entDefinitions, table, retrieve, throwIfNull) {
1387
+ super(ctx, entDefinitions, table, retrieve, throwIfNull);
1388
+ this.ctx = ctx;
1229
1389
  this.entDefinitions = entDefinitions;
1230
1390
  this.table = table;
1231
1391
  this.retrieve = retrieve;
1232
1392
  this.throwIfNull = throwIfNull;
1233
- this.base = new WriterImplBase(db, entDefinitions, table);
1393
+ this.base = new WriterImplBase(ctx, entDefinitions, table);
1234
1394
  }
1235
1395
  base;
1236
1396
  patch(value) {
1237
1397
  return new PromiseEntIdImpl(
1238
- this.db,
1398
+ this.ctx,
1239
1399
  this.entDefinitions,
1240
1400
  this.table,
1241
1401
  async () => {
@@ -1244,18 +1404,52 @@ var PromiseEntWriterImpl = class extends PromiseEntOrNullImpl {
1244
1404
  await this.base.checkReadAndWriteRule("update", id, value);
1245
1405
  await this.base.checkUniqueness(value, id);
1246
1406
  const fields = this.base.fieldsOnly(value);
1247
- await this.db.patch(id, fields);
1407
+ await this.ctx.db.patch(id, fields);
1248
1408
  const edges = {};
1249
1409
  await Promise.all(
1250
1410
  Object.keys(value).map(async (key) => {
1251
- const edgeDefinition = this.entDefinitions[this.table].edges[key];
1411
+ const edgeDefinition = getEdgeDefinitions(
1412
+ this.entDefinitions,
1413
+ this.table
1414
+ )[key];
1252
1415
  if (edgeDefinition === void 0 || edgeDefinition.cardinality === "single" && edgeDefinition.type === "field") {
1253
1416
  return;
1254
1417
  }
1255
1418
  if (edgeDefinition.cardinality === "single") {
1256
- throw new Error("Cannot set 1:1 edge from optional end.");
1419
+ throw new Error(
1420
+ `Cannot set 1:1 edge "${edgeDefinition.name}" on ent in table "${this.table}", update the ent in "${edgeDefinition.to}" table instead.`
1421
+ );
1257
1422
  } else {
1258
- edges[key] = value[key];
1423
+ if (edgeDefinition.type === "field") {
1424
+ throw new Error(
1425
+ `Cannot set 1:many edges "${edgeDefinition.name}" on ent in table "${this.table}", update the ents in "${edgeDefinition.to}" table instead.`
1426
+ );
1427
+ } else {
1428
+ const { add, remove } = value[key];
1429
+ const removeEdges = (await Promise.all(
1430
+ (remove ?? []).map(
1431
+ async (edgeId) => (await this.ctx.db.query(edgeDefinition.table).withIndex(
1432
+ edgeDefinition.field,
1433
+ (q) => q.eq(edgeDefinition.field, id).eq(
1434
+ edgeDefinition.ref,
1435
+ edgeId
1436
+ )
1437
+ ).collect()).concat(
1438
+ edgeDefinition.symmetric ? await this.ctx.db.query(edgeDefinition.table).withIndex(
1439
+ edgeDefinition.ref,
1440
+ (q) => q.eq(edgeDefinition.ref, id).eq(
1441
+ edgeDefinition.field,
1442
+ edgeId
1443
+ )
1444
+ ).collect() : []
1445
+ )
1446
+ )
1447
+ )).map((edgeDoc) => edgeDoc._id);
1448
+ edges[key] = {
1449
+ add,
1450
+ removeEdges
1451
+ };
1452
+ }
1259
1453
  }
1260
1454
  })
1261
1455
  );
@@ -1266,7 +1460,7 @@ var PromiseEntWriterImpl = class extends PromiseEntOrNullImpl {
1266
1460
  }
1267
1461
  replace(value) {
1268
1462
  return new PromiseEntIdImpl(
1269
- this.db,
1463
+ this.ctx,
1270
1464
  this.entDefinitions,
1271
1465
  this.table,
1272
1466
  async () => {
@@ -1275,38 +1469,33 @@ var PromiseEntWriterImpl = class extends PromiseEntOrNullImpl {
1275
1469
  await this.base.checkReadAndWriteRule("update", docId, value);
1276
1470
  await this.base.checkUniqueness(value, docId);
1277
1471
  const fields = this.base.fieldsOnly(value);
1278
- await this.db.replace(docId, fields);
1472
+ await this.ctx.db.replace(docId, fields);
1279
1473
  const edges = {};
1280
1474
  await Promise.all(
1281
1475
  Object.values(
1282
- this.entDefinitions[this.table].edges
1476
+ getEdgeDefinitions(this.entDefinitions, this.table)
1283
1477
  ).map(async (edgeDefinition) => {
1284
1478
  const key = edgeDefinition.name;
1285
1479
  const idOrIds = value[key];
1286
1480
  if (edgeDefinition.cardinality === "single") {
1287
1481
  if (edgeDefinition.type === "ref") {
1288
- const oldDoc = await this.db.get(docId);
1482
+ const oldDoc = await this.ctx.db.get(docId);
1289
1483
  if (oldDoc[key] !== void 0 && oldDoc[key] !== idOrIds) {
1290
1484
  throw new Error("Cannot set 1:1 edge from optional end.");
1291
1485
  }
1292
1486
  }
1293
1487
  } else {
1294
1488
  if (edgeDefinition.type === "field") {
1295
- const existing = (await this.db.query(edgeDefinition.to).withIndex(
1296
- edgeDefinition.ref,
1297
- (q) => q.eq(edgeDefinition.ref, docId)
1298
- ).collect()).map((doc) => doc._id);
1299
- edges[key] = {
1300
- add: idOrIds,
1301
- remove: existing
1302
- };
1489
+ if (idOrIds !== void 0) {
1490
+ throw new Error("Cannot set 1:many edge from many end.");
1491
+ }
1303
1492
  } else {
1304
1493
  const requested = new Set(idOrIds ?? []);
1305
- const remove = (await this.db.query(edgeDefinition.table).withIndex(
1494
+ const removeEdges = (await this.ctx.db.query(edgeDefinition.table).withIndex(
1306
1495
  edgeDefinition.field,
1307
1496
  (q) => q.eq(edgeDefinition.field, docId)
1308
1497
  ).collect()).map((doc) => [doc._id, doc[edgeDefinition.ref]]).concat(
1309
- edgeDefinition.symmetric ? (await this.db.query(edgeDefinition.table).withIndex(
1498
+ edgeDefinition.symmetric ? (await this.ctx.db.query(edgeDefinition.table).withIndex(
1310
1499
  edgeDefinition.ref,
1311
1500
  (q) => q.eq(edgeDefinition.ref, docId)
1312
1501
  ).collect()).map(
@@ -1320,8 +1509,8 @@ var PromiseEntWriterImpl = class extends PromiseEntOrNullImpl {
1320
1509
  return true;
1321
1510
  }).map(([edgeId]) => edgeId);
1322
1511
  edges[key] = {
1323
- add: Array.from(requested),
1324
- removeEdges: remove
1512
+ add: idOrIds ?? [],
1513
+ removeEdges
1325
1514
  };
1326
1515
  }
1327
1516
  }
@@ -1335,70 +1524,26 @@ var PromiseEntWriterImpl = class extends PromiseEntOrNullImpl {
1335
1524
  async delete() {
1336
1525
  const { id: docId } = await this.retrieve();
1337
1526
  const id = docId;
1338
- await this.base.checkReadAndWriteRule("delete", id, void 0);
1339
- let memoized = void 0;
1340
- const oldDoc = async () => {
1341
- if (memoized !== void 0) {
1342
- return memoized;
1343
- }
1344
- return memoized = await this.db.get(id);
1345
- };
1346
- const edges = {};
1347
- await Promise.all(
1348
- Object.values(
1349
- this.entDefinitions[this.table].edges
1350
- ).map(async (edgeDefinition) => {
1351
- const key = edgeDefinition.name;
1352
- if (edgeDefinition.cardinality === "single") {
1353
- if (edgeDefinition.type === "ref") {
1354
- edges[key] = {
1355
- remove: (await oldDoc())[key]
1356
- };
1357
- }
1358
- } else {
1359
- if (edgeDefinition.type === "field") {
1360
- const existing = (await this.db.query(edgeDefinition.to).withIndex(
1361
- edgeDefinition.ref,
1362
- (q) => q.eq(edgeDefinition.ref, id)
1363
- ).collect()).map((doc) => doc._id);
1364
- edges[key] = { remove: existing };
1365
- } else {
1366
- const existing = (await this.db.query(edgeDefinition.table).withIndex(
1367
- edgeDefinition.field,
1368
- (q) => q.eq(edgeDefinition.field, id)
1369
- ).collect()).concat(
1370
- edgeDefinition.symmetric ? await this.db.query(edgeDefinition.table).withIndex(
1371
- edgeDefinition.ref,
1372
- (q) => q.eq(edgeDefinition.ref, id)
1373
- ).collect() : []
1374
- ).map((doc) => doc._id);
1375
- edges[key] = { removeEdges: existing };
1376
- }
1377
- }
1378
- })
1379
- );
1380
- await this.db.delete(id);
1381
- await this.base.writeEdges(id, edges);
1382
- return id;
1527
+ return this.base.deleteId(id, "default");
1383
1528
  }
1384
1529
  };
1385
1530
  var PromiseEntIdImpl = class extends Promise {
1386
- constructor(db, entDefinitions, table, retrieve) {
1531
+ constructor(ctx, entDefinitions, table, retrieve) {
1387
1532
  super(() => {
1388
1533
  });
1389
- this.db = db;
1534
+ this.ctx = ctx;
1390
1535
  this.entDefinitions = entDefinitions;
1391
1536
  this.table = table;
1392
1537
  this.retrieve = retrieve;
1393
1538
  }
1394
1539
  get() {
1395
1540
  return new PromiseEntOrNullImpl(
1396
- this.db,
1541
+ this.ctx,
1397
1542
  this.entDefinitions,
1398
1543
  this.table,
1399
1544
  async () => {
1400
1545
  const id = await this.retrieve();
1401
- return { id, doc: async () => this.db.get(id) };
1546
+ return { id, doc: async () => this.ctx.db.get(id) };
1402
1547
  },
1403
1548
  true
1404
1549
  );
@@ -1420,28 +1565,28 @@ function loadedRetriever(doc) {
1420
1565
  function addEntRules(entDefinitions, rules) {
1421
1566
  return { ...entDefinitions, rules };
1422
1567
  }
1423
- async function filterByReadRule(db, entDefinitions, table, docs, throwIfNull) {
1568
+ async function filterByReadRule(ctx, entDefinitions, table, docs, throwIfNull) {
1424
1569
  if (docs === null) {
1425
1570
  return null;
1426
1571
  }
1427
1572
  const readPolicy = getReadRule(entDefinitions, table);
1428
- if (readPolicy !== void 0) {
1429
- const decisions = await Promise.all(
1430
- docs.map(async (doc) => {
1431
- const decision = await readPolicy(
1432
- entWrapper(doc, db, entDefinitions, table)
1433
- );
1434
- if (throwIfNull && !decision) {
1435
- throw new Error(
1436
- `Document cannot be read with id \`${doc._id}\` in table "${table}"`
1437
- );
1438
- }
1439
- return decision;
1440
- })
1441
- );
1442
- return docs.filter((_, i) => decisions[i]);
1573
+ if (readPolicy === void 0) {
1574
+ return docs;
1443
1575
  }
1444
- return docs;
1576
+ const decisions = await Promise.all(
1577
+ docs.map(async (doc) => {
1578
+ const decision = await readPolicy(
1579
+ entWrapper(doc, ctx, entDefinitions, table)
1580
+ );
1581
+ if (throwIfNull && !decision) {
1582
+ throw new Error(
1583
+ `Document cannot be read with id \`${doc._id}\` in table "${table}"`
1584
+ );
1585
+ }
1586
+ return decision;
1587
+ })
1588
+ );
1589
+ return docs.filter((_, i) => decisions[i]);
1445
1590
  }
1446
1591
  function getReadRule(entDefinitions, table) {
1447
1592
  return entDefinitions.rules?.[table]?.read;
@@ -1449,12 +1594,201 @@ function getReadRule(entDefinitions, table) {
1449
1594
  function getWriteRule(entDefinitions, table) {
1450
1595
  return entDefinitions.rules?.[table]?.write;
1451
1596
  }
1597
+ function getEdgeDefinitions(entDefinitions, table) {
1598
+ return entDefinitions[table].edges;
1599
+ }
1600
+ function getDeletionConfig(entDefinitions, table) {
1601
+ return entDefinitions[table].deletionConfig;
1602
+ }
1603
+
1604
+ // src/deletion.ts
1605
+ var import_server3 = require("convex/server");
1606
+ var import_values2 = require("convex/values");
1607
+ var vApproach = import_values2.v.union(
1608
+ import_values2.v.literal("schedule"),
1609
+ import_values2.v.literal("deleteOne"),
1610
+ import_values2.v.literal("paginate")
1611
+ );
1612
+ function scheduledDeleteFactory(entDefinitions, options) {
1613
+ const selfRef = options?.scheduledDelete ?? (0, import_server3.makeFunctionReference)(
1614
+ "functions:scheduledDelete"
1615
+ );
1616
+ return (0, import_server3.internalMutationGeneric)({
1617
+ args: {
1618
+ origin: import_values2.v.object({
1619
+ id: import_values2.v.string(),
1620
+ table: import_values2.v.string(),
1621
+ deletionTime: import_values2.v.number()
1622
+ }),
1623
+ stack: import_values2.v.array(
1624
+ import_values2.v.union(
1625
+ import_values2.v.object({
1626
+ id: import_values2.v.string(),
1627
+ table: import_values2.v.string(),
1628
+ edges: import_values2.v.array(
1629
+ import_values2.v.object({
1630
+ approach: vApproach,
1631
+ table: import_values2.v.string(),
1632
+ indexName: import_values2.v.string()
1633
+ })
1634
+ )
1635
+ }),
1636
+ import_values2.v.object({
1637
+ approach: vApproach,
1638
+ cursor: import_values2.v.union(import_values2.v.string(), import_values2.v.null()),
1639
+ table: import_values2.v.string(),
1640
+ indexName: import_values2.v.string(),
1641
+ fieldValue: import_values2.v.any()
1642
+ })
1643
+ )
1644
+ ),
1645
+ inProgress: import_values2.v.boolean()
1646
+ },
1647
+ handler: async (ctx, { origin, stack, inProgress }) => {
1648
+ const originId = ctx.db.normalizeId(origin.table, origin.id);
1649
+ if (originId === null) {
1650
+ throw new Error(`Invalid ID "${origin.id}" for table ${origin.table}`);
1651
+ }
1652
+ const doc = await ctx.db.get(originId);
1653
+ if (doc.deletionTime !== origin.deletionTime) {
1654
+ if (inProgress) {
1655
+ console.error(
1656
+ `[Ents] Already in-progress scheduled deletion for "${origin.id}" was cancelled!`
1657
+ );
1658
+ } else {
1659
+ console.log(
1660
+ `[Ents] Scheduled deletion for "${origin.id}" was cancelled`
1661
+ );
1662
+ }
1663
+ return;
1664
+ }
1665
+ await progressScheduledDeletion(
1666
+ ctx,
1667
+ entDefinitions,
1668
+ selfRef,
1669
+ origin,
1670
+ inProgress ? stack : [
1671
+ {
1672
+ id: originId,
1673
+ table: origin.table,
1674
+ edges: getEdgeArgs(entDefinitions, origin.table)
1675
+ }
1676
+ ]
1677
+ );
1678
+ }
1679
+ });
1680
+ }
1681
+ function getEdgeArgs(entDefinitions, table) {
1682
+ const edges = getEdgeDefinitions(entDefinitions, table);
1683
+ return Object.values(edges).flatMap((edgeDefinition) => {
1684
+ if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "ref" || edgeDefinition.cardinality === "multiple" && edgeDefinition.type === "field") {
1685
+ const table2 = edgeDefinition.to;
1686
+ const targetDeletionConfig = getDeletionConfig(entDefinitions, table2);
1687
+ const targetEdges = getEdgeDefinitions(entDefinitions, table2);
1688
+ const hasCascadingEdges = Object.values(targetEdges).some(
1689
+ (edgeDefinition2) => edgeDefinition2.cardinality === "single" && edgeDefinition2.type === "ref" || edgeDefinition2.cardinality === "multiple"
1690
+ );
1691
+ const approach = targetDeletionConfig !== void 0 && hasCascadingEdges ? "schedule" : hasCascadingEdges ? "deleteOne" : "paginate";
1692
+ const indexName = edgeDefinition.ref;
1693
+ return [{ table: table2, indexName, approach }];
1694
+ } else if (edgeDefinition.cardinality === "multiple") {
1695
+ const table2 = edgeDefinition.table;
1696
+ return [
1697
+ {
1698
+ table: table2,
1699
+ indexName: edgeDefinition.field,
1700
+ approach: "paginate"
1701
+ },
1702
+ ...edgeDefinition.symmetric ? [
1703
+ {
1704
+ table: table2,
1705
+ indexName: edgeDefinition.ref,
1706
+ approach: "paginate"
1707
+ }
1708
+ ] : []
1709
+ ];
1710
+ } else {
1711
+ return [];
1712
+ }
1713
+ });
1714
+ }
1715
+ async function progressScheduledDeletion(ctx, entDefinitions, selfRef, origin, stack) {
1716
+ const last = stack[stack.length - 1];
1717
+ if ("id" in last) {
1718
+ const edgeArgs = last.edges[0];
1719
+ if (edgeArgs === void 0) {
1720
+ await ctx.db.delete(last.id);
1721
+ if (stack.length > 1) {
1722
+ await ctx.scheduler.runAfter(0, selfRef, {
1723
+ origin,
1724
+ stack: stack.slice(0, -1),
1725
+ inProgress: true
1726
+ });
1727
+ }
1728
+ } else {
1729
+ const updated = { ...last, edges: last.edges.slice(1) };
1730
+ await paginate(
1731
+ ctx,
1732
+ entDefinitions,
1733
+ selfRef,
1734
+ origin,
1735
+ stack.slice(0, -1).concat(updated),
1736
+ { cursor: null, fieldValue: last.id, ...edgeArgs }
1737
+ );
1738
+ }
1739
+ } else {
1740
+ await paginate(ctx, entDefinitions, selfRef, origin, stack, last);
1741
+ }
1742
+ }
1743
+ async function paginate(ctx, entDefinitions, selfRef, origin, stack, { table, approach, indexName, fieldValue, cursor }) {
1744
+ const { page, continueCursor, isDone } = await ctx.db.query(table).withIndex(indexName, (q) => q.eq(indexName, fieldValue)).paginate({
1745
+ cursor,
1746
+ ...approach === "paginate" ? { numItems: 8192 / 4, maximumBytesRead: 2 ** 18 } : { numItems: 1 }
1747
+ });
1748
+ const updated = {
1749
+ approach,
1750
+ table,
1751
+ cursor: continueCursor,
1752
+ indexName,
1753
+ fieldValue
1754
+ };
1755
+ const relevantStack = cursor === null ? stack : stack.slice(0, -1);
1756
+ if (approach === "schedule") {
1757
+ await ctx.scheduler.runAfter(0, selfRef, {
1758
+ origin,
1759
+ stack: isDone ? relevantStack : relevantStack.concat([
1760
+ updated,
1761
+ {
1762
+ id: page[0]._id,
1763
+ table,
1764
+ edges: getEdgeArgs(entDefinitions, table)
1765
+ }
1766
+ ]),
1767
+ inProgress: true
1768
+ });
1769
+ } else {
1770
+ if (approach === "deleteOne") {
1771
+ await new WriterImplBase(ctx, entDefinitions, origin.table).deleteId(
1772
+ page[0].id,
1773
+ "hard"
1774
+ );
1775
+ } else {
1776
+ await Promise.all(page.map((doc) => ctx.db.delete(doc._id)));
1777
+ }
1778
+ await ctx.scheduler.runAfter(0, selfRef, {
1779
+ origin,
1780
+ stack: isDone ? relevantStack : relevantStack.concat([updated]),
1781
+ inProgress: true
1782
+ });
1783
+ }
1784
+ }
1452
1785
  // Annotate the CommonJS export names for ESM import in node:
1453
1786
  0 && (module.exports = {
1454
1787
  addEntRules,
1455
1788
  defineEnt,
1456
1789
  defineEntSchema,
1457
1790
  entsTableFactory,
1458
- getEntDefinitions
1791
+ getEntDefinitions,
1792
+ scheduledDeleteFactory
1459
1793
  });
1460
1794
  //# sourceMappingURL=index.js.map