@verdant-web/store 3.6.2 → 3.6.4

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.
@@ -42,6 +42,7 @@ export type DeepPartial<T> = T extends object ? {
42
42
  } : T;
43
43
  export interface ObjectEntity<Init, Value extends BaseEntityValue, Snapshot = DataFromInit<Init>> extends BaseEntity<Init, Value, Snapshot> {
44
44
  keys(): string[];
45
+ readonly size: number;
45
46
  entries(): [string, Exclude<Value[keyof Value], undefined>][];
46
47
  values(): Exclude<Value[keyof Value], undefined>[];
47
48
  set<Key extends keyof Init>(key: Key, value: Init[Key]): void;
@@ -69,6 +70,16 @@ export interface ObjectEntity<Init, Value extends BaseEntityValue, Snapshot = Da
69
70
  */
70
71
  merge?: boolean;
71
72
  }): void;
73
+ /**
74
+ * Deletes the entity from either its parent (if it's a nested value)
75
+ * or the database itself. WARNING: this method is tricky. It will
76
+ * throw an error on nested fields which are not deletable in the
77
+ * schema. Deleting any entity and then attempting to access its
78
+ * data will also result in an error.
79
+ *
80
+ * Prefer using client.<collection>.delete(id) instead.
81
+ */
82
+ deleteSelf(): void;
72
83
  readonly isList: false;
73
84
  }
74
85
  export interface ListEntity<Init, Value extends BaseEntityValue, Snapshot = DataFromInit<Init>> extends Iterable<ListItemValue<Value>>, BaseEntity<Init, Value, Snapshot> {
@@ -23,6 +23,7 @@ export declare class EntityFile extends EventSubscriber<EntityFileEvents> {
23
23
  downloadRemote?: boolean;
24
24
  });
25
25
  get downloadRemote(): boolean;
26
+ get isFile(): boolean;
26
27
  [UPDATE]: (fileData: FileData) => void;
27
28
  [MARK_FAILED]: () => void;
28
29
  get url(): string | null;
@@ -44,6 +44,9 @@ export class EntityFile extends EventSubscriber {
44
44
  get downloadRemote() {
45
45
  return this._downloadRemote;
46
46
  }
47
+ get isFile() {
48
+ return true;
49
+ }
47
50
  get url() {
48
51
  var _c, _d;
49
52
  if (this.loading)
@@ -1 +1 @@
1
- {"version":3,"file":"EntityFile.js","sourceRoot":"","sources":["../../../src/files/EntityFile.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,eAAe,EAAY,MAAM,qBAAqB,CAAC;AAMhE,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC;AACnD,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,yBAAyB,CAAC,CAAC;AAO7D;;;GAGG;AACH,MAAM,OAAO,UAAW,SAAQ,eAAiC;IAQhE,YACiB,EAAU,EAC1B,EACC,cAAc,GAAG,KAAK,MAGnB,EAAE;QAEN,KAAK,EAAE,CAAC;QAPQ,OAAE,GAAF,EAAE,CAAQ;QAR3B,yDAAyD;QACjD,eAAU,GAAkB,IAAI,CAAC;QACjC,cAAS,GAAoB,IAAI,CAAC;QAClC,aAAQ,GAAG,IAAI,CAAC;QAChB,YAAO,GAAG,KAAK,CAAC;QAChB,oBAAe,GAAG,KAAK,CAAC;QAkBhC,QAAQ,GAAG,CAAC,QAAkB,EAAE,EAAE;YACjC,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;YACrB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;YAC1B,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACnB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;oBACrB,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBACtC,CAAC;gBACD,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YACtD,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrB,CAAC,CAAC;QAEF,QAAa,GAAG,GAAG,EAAE;YACpB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrB,CAAC,CAAC;QAwBF,YAAO,GAAG,GAAG,EAAE;YACd,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;gBACrB,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACtC,CAAC;YACD,IAAI,CAAC,OAAO,EAAE,CAAC;QAChB,CAAC,CAAC;QArDD,IAAI,CAAC,eAAe,GAAG,cAAc,CAAC;IACvC,CAAC;IAED,IAAI,cAAc;QACjB,OAAO,IAAI,CAAC,eAAe,CAAC;IAC7B,CAAC;IAqBD,IAAI,GAAG;;QACN,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC9B,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC,UAAU,CAAC;QAC5C,OAAO,MAAA,MAAA,IAAI,CAAC,SAAS,0CAAE,GAAG,mCAAI,IAAI,CAAC;IACpC,CAAC;IAED,IAAI,IAAI;;QACP,OAAO,MAAA,MAAA,IAAI,CAAC,SAAS,0CAAE,IAAI,mCAAI,IAAI,CAAC;IACrC,CAAC;IAED,IAAI,IAAI;;QACP,OAAO,MAAA,MAAA,IAAI,CAAC,SAAS,0CAAE,IAAI,mCAAI,IAAI,CAAC;IACrC,CAAC;IAED,IAAI,OAAO;QACV,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAED,IAAI,MAAM;QACT,OAAO,IAAI,CAAC,OAAO,CAAC;IACrB,CAAC;IASD,WAAW;QACV,OAAO;YACN,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,GAAG,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG;SACvD,CAAC;IACH,CAAC;CACD;KAtDC,MAAM,OAaN,WAAW"}
1
+ {"version":3,"file":"EntityFile.js","sourceRoot":"","sources":["../../../src/files/EntityFile.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verdant-web/store",
3
- "version": "3.6.2",
3
+ "version": "3.6.4",
4
4
  "access": "public",
5
5
  "type": "module",
6
6
  "exports": {
@@ -463,6 +463,20 @@ describe('storage documents', () => {
463
463
  expect(item1.get('tags').deepUpdatedAt).not.toEqual(time.getTime());
464
464
  });
465
465
 
466
+ it('should expose namespace', async () => {
467
+ const storage = await createTestStorage();
468
+
469
+ const item1 = await storage.todos.put({
470
+ content: 'item 1',
471
+ done: false,
472
+ tags: [],
473
+ category: 'general',
474
+ attachments: [],
475
+ });
476
+
477
+ expect(item1.namespace).toBe('test');
478
+ });
479
+
466
480
  it('should allow creating a new document from another document snapshot', async () => {
467
481
  const storage = await createTestStorage();
468
482
  const item1 = await storage.todos.put({
@@ -592,4 +606,18 @@ describe('storage documents', () => {
592
606
  expect(fooArray.get(0)).toEqual('bar');
593
607
  expect(arrayMap.get('foo').get(0)).toEqual('bar');
594
608
  });
609
+
610
+ it('should not fire change event on delete', async () => {
611
+ const storage = await createTestStorage();
612
+ const created = await storage.weirds.put({});
613
+ const weird = await storage.weirds.get(created.get('id')).resolved!;
614
+
615
+ weird.subscribe('change', () => {
616
+ // never get a change with deleted data
617
+ expect(weird.deleted).toBe(false);
618
+ });
619
+
620
+ await storage.weirds.delete(weird.get('id'));
621
+ expect(true).toBe(true);
622
+ });
595
623
  });
@@ -54,4 +54,57 @@ describe('mutations', () => {
54
54
  expect(itemAExists).toBeNull();
55
55
  expect(itemBExists === itemB).toBe(true);
56
56
  });
57
+
58
+ describe('on entities', () => {
59
+ it('should allow them to delete themselves (root level)', async () => {
60
+ const client = await createTestStorage();
61
+
62
+ const itemA = await client.todos.put({
63
+ id: '1',
64
+ content: 'itemA',
65
+ category: 'test',
66
+ });
67
+
68
+ await itemA.deleteSelf();
69
+
70
+ const itemAExists = await client.todos.get('1').resolved;
71
+
72
+ expect(itemAExists).toBeNull();
73
+ });
74
+
75
+ it('should allow them to delete themselves (nested array)', async () => {
76
+ const client = await createTestStorage();
77
+
78
+ const itemA = await client.todos.put({
79
+ id: '1',
80
+ content: 'itemA',
81
+ category: 'test',
82
+ attachments: [
83
+ {
84
+ name: 'foo',
85
+ },
86
+ ],
87
+ });
88
+
89
+ itemA.get('attachments').get(0).deleteSelf();
90
+
91
+ expect(itemA.get('attachments').length).toBe(0);
92
+ });
93
+
94
+ it('should allow them to delete themselves (nested map)', async () => {
95
+ const client = await createTestStorage();
96
+
97
+ const weird = await client.weirds.put({
98
+ objectMap: {
99
+ foo: {
100
+ content: 'bar',
101
+ },
102
+ },
103
+ });
104
+
105
+ weird.get('objectMap').get('foo').deleteSelf();
106
+
107
+ expect(weird.get('objectMap').size).toBe(0);
108
+ });
109
+ });
57
110
  });
@@ -79,6 +79,7 @@ describe('Entity', () => {
79
79
  } as any as FileManager,
80
80
  patchCreator,
81
81
  readonlyKeys: ['id'],
82
+ deleteSelf: vi.fn(),
82
83
  });
83
84
 
84
85
  function initialize(data: any) {
@@ -58,6 +58,7 @@ export interface EntityInit {
58
58
  fieldPath?: (string | number)[];
59
59
  patchCreator: PatchCreator;
60
60
  events: EntityStoreEvents;
61
+ deleteSelf: () => void;
61
62
  }
62
63
 
63
64
  export class Entity<
@@ -94,6 +95,9 @@ export class Entity<
94
95
  // only used for root entities to track delete/restore state.
95
96
  private wasDeletedLastChange = false;
96
97
  private cachedView: any | undefined = undefined;
98
+ // provided from external creators, this is a method to delete
99
+ // this entity.
100
+ private _deleteSelf: () => void;
97
101
 
98
102
  constructor({
99
103
  oid,
@@ -106,6 +110,7 @@ export class Entity<
106
110
  files,
107
111
  patchCreator,
108
112
  events,
113
+ deleteSelf,
109
114
  }: EntityInit) {
110
115
  super();
111
116
 
@@ -125,6 +130,7 @@ export class Entity<
125
130
  this.metadataFamily = metadataFamily;
126
131
  this.events = events;
127
132
  this.parent = parent;
133
+ this._deleteSelf = deleteSelf;
128
134
 
129
135
  // TODO: should any but the root entity be listening to these?
130
136
  if (!this.parent) {
@@ -366,6 +372,15 @@ export class Entity<
366
372
  return this.viewData.fromOlderVersion;
367
373
  }
368
374
 
375
+ /**
376
+ * Returns the storage namespace this entity came from. For example, if you
377
+ * have multiple stores initialized from the same schema, you can use this
378
+ * to figure out where an isolated entity was created / stored.
379
+ */
380
+ get namespace() {
381
+ return this.ctx.namespace;
382
+ }
383
+
369
384
  /**
370
385
  * Pruning - when entities have invalid children, we 'prune' that
371
386
  * data up to the nearest prunable point - a nullable field,
@@ -465,25 +480,10 @@ export class Entity<
465
480
  // reset cached view
466
481
  this._viewData = undefined;
467
482
  this.cachedView = undefined;
468
- // chain deepChanges to parents
469
- this.deepChange(this, ev);
470
- // emit the change, it's for us
471
- this.ctx.log('Emitting change event', this.oid);
472
- this.emit('change', { isLocal: ev.isLocal });
473
- // for root entities, we need to go ahead and decide if we're
474
- // deleted or not - so queries can exclude us if we are.
475
483
  if (!this.parent) {
476
- // newly deleted - emit event
477
- if (this.deleted && !this.wasDeletedLastChange) {
478
- this.ctx.log('debug', 'Entity deleted', this.oid);
479
- this.emit('delete', { isLocal: ev.isLocal });
480
- this.wasDeletedLastChange = true;
481
- } else if (!this.deleted && this.wasDeletedLastChange) {
482
- this.ctx.log('debug', 'Entity restored', this.oid);
483
- // newly restored - emit event
484
- this.emit('restore', { isLocal: ev.isLocal });
485
- this.wasDeletedLastChange = false;
486
- }
484
+ this.changeRoot(ev);
485
+ } else {
486
+ this.changeNested(ev);
487
487
  }
488
488
  } else {
489
489
  // forward it to the correct family member. if none exists
@@ -494,6 +494,36 @@ export class Entity<
494
494
  }
495
495
  }
496
496
  };
497
+ private changeRoot = (ev: EntityChange) => {
498
+ // for root entities, we need to determine if we're deleted or not
499
+ // before firing any events
500
+ if (this.deleted) {
501
+ if (!this.wasDeletedLastChange) {
502
+ this.ctx.log('debug', 'Entity deleted', this.oid);
503
+ this.emit('delete', { isLocal: ev.isLocal });
504
+ this.wasDeletedLastChange = true;
505
+ }
506
+ // already deleted, do nothing.
507
+ } else {
508
+ if (this.wasDeletedLastChange) {
509
+ this.ctx.log('debug', 'Entity restored', this.oid);
510
+ this.emit('restore', { isLocal: ev.isLocal });
511
+ this.wasDeletedLastChange = false;
512
+ }
513
+ // emit deepchange, too
514
+ this.deepChange(this, ev);
515
+ // emit the change, it's for us
516
+ this.ctx.log('Emitting change event', this.oid);
517
+ this.emit('change', { isLocal: ev.isLocal });
518
+ }
519
+ };
520
+ private changeNested = (ev: EntityChange) => {
521
+ // chain deepChanges to parents
522
+ this.deepChange(this, ev);
523
+ // emit the change, it's for us
524
+ this.ctx.log('Emitting change event', this.oid);
525
+ this.emit('change', { isLocal: ev.isLocal });
526
+ };
497
527
  protected deepChange = (target: Entity, ev: EntityChange) => {
498
528
  // reset cached deep updated at timestamp; either this
499
529
  // entity or children have changed
@@ -530,6 +560,7 @@ export class Entity<
530
560
  fieldPath: [...this.fieldPath, key],
531
561
  patchCreator: this.patchCreator,
532
562
  events: this.events,
563
+ deleteSelf: this.delete.bind(this, key),
533
564
  });
534
565
  };
535
566
 
@@ -781,6 +812,13 @@ export class Entity<
781
812
  return Object.values(this.view);
782
813
  };
783
814
 
815
+ get size() {
816
+ if (this.isList) {
817
+ return this.length;
818
+ }
819
+ return this.keys().length;
820
+ }
821
+
784
822
  update = (
785
823
  data: any,
786
824
  {
@@ -976,6 +1014,21 @@ export class Entity<
976
1014
 
977
1015
  includes = this.has;
978
1016
 
1017
+ /**
1018
+ * Deletes this entity. WARNING: this can be tricky to
1019
+ * use correctly. You must not reference this entity
1020
+ * instance in any way after the deletion happens, or
1021
+ * you will get an error!
1022
+ *
1023
+ * It's a little easier to delete using client.delete
1024
+ * if you can manage it with your app's code. For example,
1025
+ * in React, use hooks.useClient() to get the client and
1026
+ * call delete from there.
1027
+ */
1028
+ deleteSelf = () => {
1029
+ return this._deleteSelf();
1030
+ };
1031
+
979
1032
  // TODO: make these escape hatches unnecessary
980
1033
  __getViewData__ = (oid: ObjectIdentifier, type: 'confirmed' | 'pending') => {
981
1034
  return this.metadataFamily.get(oid).computeView(type === 'confirmed');
@@ -451,6 +451,7 @@ export class EntityStore extends Disposable {
451
451
  metadataFamily: metadataFamily,
452
452
  patchCreator: this.meta.patchCreator,
453
453
  events: this.events,
454
+ deleteSelf: this.delete.bind(this, oid),
454
455
  });
455
456
  };
456
457
 
@@ -84,6 +84,7 @@ export interface ObjectEntity<
84
84
  Snapshot = DataFromInit<Init>,
85
85
  > extends BaseEntity<Init, Value, Snapshot> {
86
86
  keys(): string[];
87
+ readonly size: number;
87
88
  entries(): [string, Exclude<Value[keyof Value], undefined>][];
88
89
  values(): Exclude<Value[keyof Value], undefined>[];
89
90
  set<Key extends keyof Init>(key: Key, value: Init[Key]): void;
@@ -114,6 +115,16 @@ export interface ObjectEntity<
114
115
  merge?: boolean;
115
116
  },
116
117
  ): void;
118
+ /**
119
+ * Deletes the entity from either its parent (if it's a nested value)
120
+ * or the database itself. WARNING: this method is tricky. It will
121
+ * throw an error on nested fields which are not deletable in the
122
+ * schema. Deleting any entity and then attempting to access its
123
+ * data will also result in an error.
124
+ *
125
+ * Prefer using client.<collection>.delete(id) instead.
126
+ */
127
+ deleteSelf(): void;
117
128
  readonly isList: false;
118
129
  }
119
130
 
@@ -39,6 +39,9 @@ export class EntityFile extends EventSubscriber<EntityFileEvents> {
39
39
  get downloadRemote() {
40
40
  return this._downloadRemote;
41
41
  }
42
+ get isFile() {
43
+ return true;
44
+ }
42
45
 
43
46
  [UPDATE] = (fileData: FileData) => {
44
47
  this._loading = false;