@xyo-network/archivist-indexeddb 2.91.0 → 2.91.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,8 @@
1
+ import { Hash } from '@xylabs/hex';
1
2
  import { AbstractArchivist } from '@xyo-network/archivist-abstract';
2
3
  import { ArchivistModuleEventData } from '@xyo-network/archivist-model';
3
4
  import { Payload, PayloadWithMeta } from '@xyo-network/payload-model';
5
+ import { IDBPDatabase } from 'idb';
4
6
  import { IndexedDbArchivistParams } from './Params';
5
7
  export interface PayloadStore {
6
8
  [s: string]: Payload;
@@ -40,7 +42,16 @@ export declare class IndexedDbArchivist<TParams extends IndexedDbArchivistParams
40
42
  private get indexes();
41
43
  protected allHandler(): Promise<PayloadWithMeta[]>;
42
44
  protected clearHandler(): Promise<void>;
43
- protected deleteHandler(hashes: string[]): Promise<string[]>;
45
+ protected deleteHandler(hashes: Hash[]): Promise<Hash[]>;
46
+ /**
47
+ * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)
48
+ * @param db The db instance to use
49
+ * @param storeName The name of the store to use
50
+ * @param indexName The index to use
51
+ * @param key The key to get from the index
52
+ * @returns The primary key and the payload, or undefined if not found
53
+ */
54
+ protected getFromIndexWithPrimaryKey(db: IDBPDatabase<PayloadStore>, storeName: string, indexName: string, key: IDBValidKey): Promise<[number, PayloadWithMeta] | undefined>;
44
55
  protected getHandler(hashes: string[]): Promise<PayloadWithMeta[]>;
45
56
  protected insertHandler(payloads: Payload[]): Promise<PayloadWithMeta[]>;
46
57
  protected startHandler(): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"Archivist.d.ts","sourceRoot":"","sources":["../../src/Archivist.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAKL,wBAAwB,EAGzB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAIrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AAEnD,MAAM,WAAW,YAAY;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAED,qBACa,kBAAkB,CAC7B,OAAO,SAAS,wBAAwB,GAAG,wBAAwB,EACnE,UAAU,SAAS,wBAAwB,GAAG,wBAAwB,CACtE,SAAQ,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC;IAC9C,OAAgB,aAAa,6CAAmC;IAChE,MAAM,CAAC,QAAQ,CAAC,aAAa,eAAc;IAC3C,MAAM,CAAC,QAAQ,CAAC,gBAAgB,KAAI;IACpC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,cAAa;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAC5G,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAA4E;IACjH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAA6E;IAEhH,MAAM,CAAC,QAAQ,CAAC,aAAa,SAAuD;IAEpF,MAAM,CAAC,QAAQ,CAAC,iBAAiB,SAA2D;IAE5F,MAAM,CAAC,QAAQ,CAAC,eAAe,SAAyD;IAExF;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED,IAAa,OAAO,aAEnB;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED;;OAEG;IACH,OAAO,KAAK,OAAO,GAElB;cAEwB,UAAU,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;cAOxC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;cAI7B,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;cA0BlD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cA4BxD,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAsC9D,YAAY;IAQrC;;;OAGG;YACW,gBAAgB;IAiD9B;;;;OAIG;YACW,KAAK;CAWpB"}
1
+ {"version":3,"file":"Archivist.d.ts","sourceRoot":"","sources":["../../src/Archivist.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAKL,wBAAwB,EAGzB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,YAAY,EAAU,MAAM,KAAK,CAAA;AAG1C,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AAEnD,MAAM,WAAW,YAAY;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAED,qBACa,kBAAkB,CAC7B,OAAO,SAAS,wBAAwB,GAAG,wBAAwB,EACnE,UAAU,SAAS,wBAAwB,GAAG,wBAAwB,CACtE,SAAQ,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC;IAC9C,OAAgB,aAAa,6CAAmC;IAChE,MAAM,CAAC,QAAQ,CAAC,aAAa,eAAc;IAC3C,MAAM,CAAC,QAAQ,CAAC,gBAAgB,KAAI;IACpC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,cAAa;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAC5G,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAA4E;IACjH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAA6E;IAEhH,MAAM,CAAC,QAAQ,CAAC,aAAa,SAAuD;IAEpF,MAAM,CAAC,QAAQ,CAAC,iBAAiB,SAA2D;IAE5F,MAAM,CAAC,QAAQ,CAAC,eAAe,SAAyD;IAExF;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED,IAAa,OAAO,aAEnB;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED;;OAEG;IACH,OAAO,KAAK,OAAO,GAElB;cAEwB,UAAU,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;cAOxC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;cAI7B,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IA0BvE;;;;;;;OAOG;cACa,0BAA0B,CACxC,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,CAAC;cAaxB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAmCxD,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAsC9D,YAAY;IAQrC;;;OAGG;YACW,gBAAgB;IAiD9B;;;;OAIG;YACW,KAAK;CAWpB"}
@@ -1,6 +1,8 @@
1
+ import { Hash } from '@xylabs/hex';
1
2
  import { AbstractArchivist } from '@xyo-network/archivist-abstract';
2
3
  import { ArchivistModuleEventData } from '@xyo-network/archivist-model';
3
4
  import { Payload, PayloadWithMeta } from '@xyo-network/payload-model';
5
+ import { IDBPDatabase } from 'idb';
4
6
  import { IndexedDbArchivistParams } from './Params';
5
7
  export interface PayloadStore {
6
8
  [s: string]: Payload;
@@ -40,7 +42,16 @@ export declare class IndexedDbArchivist<TParams extends IndexedDbArchivistParams
40
42
  private get indexes();
41
43
  protected allHandler(): Promise<PayloadWithMeta[]>;
42
44
  protected clearHandler(): Promise<void>;
43
- protected deleteHandler(hashes: string[]): Promise<string[]>;
45
+ protected deleteHandler(hashes: Hash[]): Promise<Hash[]>;
46
+ /**
47
+ * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)
48
+ * @param db The db instance to use
49
+ * @param storeName The name of the store to use
50
+ * @param indexName The index to use
51
+ * @param key The key to get from the index
52
+ * @returns The primary key and the payload, or undefined if not found
53
+ */
54
+ protected getFromIndexWithPrimaryKey(db: IDBPDatabase<PayloadStore>, storeName: string, indexName: string, key: IDBValidKey): Promise<[number, PayloadWithMeta] | undefined>;
44
55
  protected getHandler(hashes: string[]): Promise<PayloadWithMeta[]>;
45
56
  protected insertHandler(payloads: Payload[]): Promise<PayloadWithMeta[]>;
46
57
  protected startHandler(): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"Archivist.d.ts","sourceRoot":"","sources":["../../src/Archivist.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAKL,wBAAwB,EAGzB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAIrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AAEnD,MAAM,WAAW,YAAY;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAED,qBACa,kBAAkB,CAC7B,OAAO,SAAS,wBAAwB,GAAG,wBAAwB,EACnE,UAAU,SAAS,wBAAwB,GAAG,wBAAwB,CACtE,SAAQ,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC;IAC9C,OAAgB,aAAa,6CAAmC;IAChE,MAAM,CAAC,QAAQ,CAAC,aAAa,eAAc;IAC3C,MAAM,CAAC,QAAQ,CAAC,gBAAgB,KAAI;IACpC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,cAAa;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAC5G,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAA4E;IACjH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAA6E;IAEhH,MAAM,CAAC,QAAQ,CAAC,aAAa,SAAuD;IAEpF,MAAM,CAAC,QAAQ,CAAC,iBAAiB,SAA2D;IAE5F,MAAM,CAAC,QAAQ,CAAC,eAAe,SAAyD;IAExF;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED,IAAa,OAAO,aAEnB;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED;;OAEG;IACH,OAAO,KAAK,OAAO,GAElB;cAEwB,UAAU,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;cAOxC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;cAI7B,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;cA0BlD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cA4BxD,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAsC9D,YAAY;IAQrC;;;OAGG;YACW,gBAAgB;IAiD9B;;;;OAIG;YACW,KAAK;CAWpB"}
1
+ {"version":3,"file":"Archivist.d.ts","sourceRoot":"","sources":["../../src/Archivist.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAKL,wBAAwB,EAGzB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,YAAY,EAAU,MAAM,KAAK,CAAA;AAG1C,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AAEnD,MAAM,WAAW,YAAY;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAED,qBACa,kBAAkB,CAC7B,OAAO,SAAS,wBAAwB,GAAG,wBAAwB,EACnE,UAAU,SAAS,wBAAwB,GAAG,wBAAwB,CACtE,SAAQ,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC;IAC9C,OAAgB,aAAa,6CAAmC;IAChE,MAAM,CAAC,QAAQ,CAAC,aAAa,eAAc;IAC3C,MAAM,CAAC,QAAQ,CAAC,gBAAgB,KAAI;IACpC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,cAAa;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAC5G,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAA4E;IACjH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAA6E;IAEhH,MAAM,CAAC,QAAQ,CAAC,aAAa,SAAuD;IAEpF,MAAM,CAAC,QAAQ,CAAC,iBAAiB,SAA2D;IAE5F,MAAM,CAAC,QAAQ,CAAC,eAAe,SAAyD;IAExF;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED,IAAa,OAAO,aAEnB;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED;;OAEG;IACH,OAAO,KAAK,OAAO,GAElB;cAEwB,UAAU,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;cAOxC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;cAI7B,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IA0BvE;;;;;;;OAOG;cACa,0BAA0B,CACxC,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,CAAC;cAaxB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAmCxD,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAsC9D,YAAY;IAQrC;;;OAGG;YACW,gBAAgB;IAiD9B;;;;OAIG;YACW,KAAK;CAWpB"}
@@ -1,6 +1,8 @@
1
+ import { Hash } from '@xylabs/hex';
1
2
  import { AbstractArchivist } from '@xyo-network/archivist-abstract';
2
3
  import { ArchivistModuleEventData } from '@xyo-network/archivist-model';
3
4
  import { Payload, PayloadWithMeta } from '@xyo-network/payload-model';
5
+ import { IDBPDatabase } from 'idb';
4
6
  import { IndexedDbArchivistParams } from './Params';
5
7
  export interface PayloadStore {
6
8
  [s: string]: Payload;
@@ -40,7 +42,16 @@ export declare class IndexedDbArchivist<TParams extends IndexedDbArchivistParams
40
42
  private get indexes();
41
43
  protected allHandler(): Promise<PayloadWithMeta[]>;
42
44
  protected clearHandler(): Promise<void>;
43
- protected deleteHandler(hashes: string[]): Promise<string[]>;
45
+ protected deleteHandler(hashes: Hash[]): Promise<Hash[]>;
46
+ /**
47
+ * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)
48
+ * @param db The db instance to use
49
+ * @param storeName The name of the store to use
50
+ * @param indexName The index to use
51
+ * @param key The key to get from the index
52
+ * @returns The primary key and the payload, or undefined if not found
53
+ */
54
+ protected getFromIndexWithPrimaryKey(db: IDBPDatabase<PayloadStore>, storeName: string, indexName: string, key: IDBValidKey): Promise<[number, PayloadWithMeta] | undefined>;
44
55
  protected getHandler(hashes: string[]): Promise<PayloadWithMeta[]>;
45
56
  protected insertHandler(payloads: Payload[]): Promise<PayloadWithMeta[]>;
46
57
  protected startHandler(): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"Archivist.d.ts","sourceRoot":"","sources":["../../src/Archivist.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAKL,wBAAwB,EAGzB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAIrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AAEnD,MAAM,WAAW,YAAY;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAED,qBACa,kBAAkB,CAC7B,OAAO,SAAS,wBAAwB,GAAG,wBAAwB,EACnE,UAAU,SAAS,wBAAwB,GAAG,wBAAwB,CACtE,SAAQ,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC;IAC9C,OAAgB,aAAa,6CAAmC;IAChE,MAAM,CAAC,QAAQ,CAAC,aAAa,eAAc;IAC3C,MAAM,CAAC,QAAQ,CAAC,gBAAgB,KAAI;IACpC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,cAAa;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAC5G,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAA4E;IACjH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAA6E;IAEhH,MAAM,CAAC,QAAQ,CAAC,aAAa,SAAuD;IAEpF,MAAM,CAAC,QAAQ,CAAC,iBAAiB,SAA2D;IAE5F,MAAM,CAAC,QAAQ,CAAC,eAAe,SAAyD;IAExF;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED,IAAa,OAAO,aAEnB;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED;;OAEG;IACH,OAAO,KAAK,OAAO,GAElB;cAEwB,UAAU,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;cAOxC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;cAI7B,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;cA0BlD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cA4BxD,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAsC9D,YAAY;IAQrC;;;OAGG;YACW,gBAAgB;IAiD9B;;;;OAIG;YACW,KAAK;CAWpB"}
1
+ {"version":3,"file":"Archivist.d.ts","sourceRoot":"","sources":["../../src/Archivist.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAKL,wBAAwB,EAGzB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,YAAY,EAAU,MAAM,KAAK,CAAA;AAG1C,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AAEnD,MAAM,WAAW,YAAY;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAED,qBACa,kBAAkB,CAC7B,OAAO,SAAS,wBAAwB,GAAG,wBAAwB,EACnE,UAAU,SAAS,wBAAwB,GAAG,wBAAwB,CACtE,SAAQ,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC;IAC9C,OAAgB,aAAa,6CAAmC;IAChE,MAAM,CAAC,QAAQ,CAAC,aAAa,eAAc;IAC3C,MAAM,CAAC,QAAQ,CAAC,gBAAgB,KAAI;IACpC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,cAAa;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAC5G,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAA4E;IACjH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAA6E;IAEhH,MAAM,CAAC,QAAQ,CAAC,aAAa,SAAuD;IAEpF,MAAM,CAAC,QAAQ,CAAC,iBAAiB,SAA2D;IAE5F,MAAM,CAAC,QAAQ,CAAC,eAAe,SAAyD;IAExF;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED,IAAa,OAAO,aAEnB;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED;;OAEG;IACH,OAAO,KAAK,OAAO,GAElB;cAEwB,UAAU,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;cAOxC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;cAI7B,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IA0BvE;;;;;;;OAOG;cACa,0BAA0B,CACxC,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,CAAC;cAaxB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAmCxD,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAsC9D,YAAY;IAQrC;;;OAGG;YACW,gBAAgB;IAiD9B;;;;OAIG;YACW,KAAK;CAWpB"}
@@ -160,11 +160,33 @@ var IndexedDbArchivist = class _IndexedDbArchivist extends import_archivist_abst
160
160
  return found.filter(import_exists.exists).filter((hash) => hashes.includes(hash));
161
161
  });
162
162
  }
163
+ /**
164
+ * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)
165
+ * @param db The db instance to use
166
+ * @param storeName The name of the store to use
167
+ * @param indexName The index to use
168
+ * @param key The key to get from the index
169
+ * @returns The primary key and the payload, or undefined if not found
170
+ */
171
+ async getFromIndexWithPrimaryKey(db, storeName, indexName, key) {
172
+ const transaction = db.transaction(storeName, "readonly");
173
+ const store = transaction.objectStore(storeName);
174
+ const index = store.index(indexName);
175
+ const cursor = await index.openCursor(key);
176
+ if (cursor) {
177
+ const singleValue = cursor.value;
178
+ const primaryKey = cursor.primaryKey;
179
+ return [
180
+ primaryKey,
181
+ singleValue
182
+ ];
183
+ }
184
+ }
163
185
  async getHandler(hashes) {
164
- const payloads = await this.useDb((db) => Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, _IndexedDbArchivist.hashIndexName, hash))));
165
- const payloadsFromDataHashes = await this.useDb((db) => Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, _IndexedDbArchivist.dataHashIndexName, hash))));
186
+ const payloads = await this.useDb((db) => Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, _IndexedDbArchivist.hashIndexName, hash))));
187
+ const payloadsFromDataHashes = await this.useDb((db) => Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, _IndexedDbArchivist.dataHashIndexName, hash))));
166
188
  const found = /* @__PURE__ */ new Set();
167
- const payloadsFromHash = payloads.filter(import_exists.exists).filter((payload) => {
189
+ const payloadsFromHash = payloads.filter(import_exists.exists).filter(([_key, payload]) => {
168
190
  if (found.has(payload.$hash)) {
169
191
  return false;
170
192
  } else {
@@ -172,7 +194,7 @@ var IndexedDbArchivist = class _IndexedDbArchivist extends import_archivist_abst
172
194
  return true;
173
195
  }
174
196
  });
175
- const payloadsFromDataHash = payloadsFromDataHashes.filter(import_exists.exists).filter((payload) => {
197
+ const payloadsFromDataHash = payloadsFromDataHashes.filter(import_exists.exists).filter(([_key, payload]) => {
176
198
  if (found.has(payload.$hash)) {
177
199
  return false;
178
200
  } else {
@@ -180,10 +202,13 @@ var IndexedDbArchivist = class _IndexedDbArchivist extends import_archivist_abst
180
202
  return true;
181
203
  }
182
204
  });
183
- return [
184
- ...payloadsFromHash,
185
- ...payloadsFromDataHash
186
- ];
205
+ return (
206
+ // Merge what we found from the hash and data hash indexes
207
+ [
208
+ ...payloadsFromHash,
209
+ ...payloadsFromDataHash
210
+ ].sort((a, b) => a[0] - b[0]).map(([_key, payload]) => payload)
211
+ );
187
212
  }
188
213
  async insertHandler(payloads) {
189
214
  const pairs = await import_payload_builder.PayloadBuilder.hashPairs(payloads);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.ts","../../src/Archivist.ts","../../src/Schema.ts","../../src/Config.ts"],"sourcesContent":["export * from './Archivist'\nexport * from './Config'\nexport * from './Params'\nexport * from './Schema'\n","import { exists } from '@xylabs/exists'\nimport { Hash } from '@xylabs/hex'\nimport { AbstractArchivist } from '@xyo-network/archivist-abstract'\nimport {\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ArchivistModuleEventData,\n buildStandardIndexName,\n IndexDescription,\n} from '@xyo-network/archivist-model'\nimport { creatableModule } from '@xyo-network/module-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, PayloadWithMeta } from '@xyo-network/payload-model'\nimport { IDBPDatabase, openDB } from 'idb'\n\nimport { IndexedDbArchivistConfigSchema } from './Config'\nimport { IndexedDbArchivistParams } from './Params'\n\nexport interface PayloadStore {\n [s: string]: Payload\n}\n\n@creatableModule()\nexport class IndexedDbArchivist<\n TParams extends IndexedDbArchivistParams = IndexedDbArchivistParams,\n TEventData extends ArchivistModuleEventData = ArchivistModuleEventData,\n> extends AbstractArchivist<TParams, TEventData> {\n static override configSchemas = [IndexedDbArchivistConfigSchema]\n static readonly defaultDbName = 'archivist'\n static readonly defaultDbVersion = 1\n static readonly defaultStoreName = 'payloads'\n private static readonly hashIndex: IndexDescription = { key: { _hash: 1 }, multiEntry: false, unique: true }\n private static readonly dataHashIndex: IndexDescription = { key: { $hash: 1 }, multiEntry: false, unique: false }\n private static readonly schemaIndex: IndexDescription = { key: { schema: 1 }, multiEntry: false, unique: false }\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly hashIndexName = buildStandardIndexName(IndexedDbArchivist.hashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly dataHashIndexName = buildStandardIndexName(IndexedDbArchivist.dataHashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly schemaIndexName = buildStandardIndexName(IndexedDbArchivist.schemaIndex)\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the module name (not guaranteed to be unique) and if module\n * name is not supplied, it defaults to `archivist`. This behavior\n * biases towards a single, isolated DB per archivist which seems to\n * make the most sense for 99% of use cases.\n */\n get dbName() {\n return this.config?.dbName ?? this.config?.name ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to 1.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\n }\n\n override get queries() {\n return [ArchivistAllQuerySchema, ArchivistClearQuerySchema, ArchivistDeleteQuerySchema, ArchivistInsertQuerySchema, ...super.queries]\n }\n\n /**\n * The name of the object store. If not supplied via config, it defaults\n * to `payloads`.\n */\n get storeName() {\n return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n /**\n * The indexes to create on the store\n */\n private get indexes() {\n return [IndexedDbArchivist.dataHashIndex, IndexedDbArchivist.hashIndex, IndexedDbArchivist.schemaIndex, ...(this.config?.storage?.indexes ?? [])]\n }\n\n protected override async allHandler(): Promise<PayloadWithMeta[]> {\n // Get all payloads from the store\n const payloads = await this.useDb((db) => db.getAll(this.storeName))\n // Remove any metadata before returning to the client\n return await Promise.all(payloads.map((payload) => PayloadBuilder.build(payload)))\n }\n\n protected override async clearHandler(): Promise<void> {\n await this.useDb((db) => db.clear(this.storeName))\n }\n\n protected override async deleteHandler(hashes: string[]): Promise<string[]> {\n const pairs = await PayloadBuilder.hashPairs(await this.getHandler(hashes))\n const hashesToDelete = pairs.flatMap<Hash>((pair) => [pair[0].$hash, pair[1]])\n // Remove any duplicates\n const distinctHashes = [...new Set(hashesToDelete)]\n return await this.useDb(async (db) => {\n // Only return hashes that were successfully deleted\n const found = await Promise.all(\n distinctHashes.map(async (hash) => {\n // Check if the hash exists\n const existing =\n (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash)) ??\n (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))\n // If it does exist\n if (existing) {\n // Delete it\n await db.delete(this.storeName, existing)\n // Return the hash so it gets added to the list of deleted hashes\n return hash\n }\n }),\n )\n return found.filter(exists).filter((hash) => hashes.includes(hash))\n })\n }\n\n protected override async getHandler(hashes: string[]): Promise<PayloadWithMeta[]> {\n const payloads = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash))),\n )\n const payloadsFromDataHashes = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))),\n )\n //filter out duplicates\n const found = new Set<string>()\n const payloadsFromHash = payloads.filter(exists).filter((payload) => {\n if (found.has(payload.$hash)) {\n return false\n } else {\n found.add(payload.$hash)\n return true\n }\n })\n const payloadsFromDataHash = payloadsFromDataHashes.filter(exists).filter((payload) => {\n if (found.has(payload.$hash)) {\n return false\n } else {\n found.add(payload.$hash)\n return true\n }\n })\n return [...payloadsFromHash, ...payloadsFromDataHash]\n }\n\n protected override async insertHandler(payloads: Payload[]): Promise<PayloadWithMeta[]> {\n const pairs = await PayloadBuilder.hashPairs(payloads)\n\n const db = await this.getInitializedDb()\n try {\n // Only return the payloads that were successfully inserted\n const inserted = await Promise.all(\n pairs.map(async ([payload, _hash]) => {\n // Perform each insert via a transaction to ensure it is atomic\n // with respect to checking for the pre-existence of the hash.\n // This is done to preserve iteration via insertion order.\n const tx = db.transaction(this.storeName, 'readwrite')\n try {\n // Get the object store\n const store = tx.objectStore(this.storeName)\n\n // Check if the hash already exists\n const existingTopHash = await store.index(IndexedDbArchivist.hashIndexName).get(_hash)\n // If it does not already exist\n if (!existingTopHash) {\n // Insert the payload\n await store.put({ ...payload, _hash })\n }\n\n // Return it so it gets added to the list of inserted payloads\n return payload\n } finally {\n // Close the transaction\n await tx.done\n }\n }),\n )\n return inserted.filter(exists)\n } finally {\n db.close()\n }\n }\n\n protected override async startHandler() {\n await super.startHandler()\n // NOTE: We could defer this creation to first access but we\n // want to fail fast here in case something is wrong\n await this.useDb(() => {})\n return true\n }\n\n /**\n * Returns that the desired DB/Store initialized to the correct version\n * @returns The initialized DB\n */\n private async getInitializedDb(): Promise<IDBPDatabase<PayloadStore>> {\n const { dbName, dbVersion, indexes, storeName } = this\n const db = await openDB<PayloadStore>(dbName, dbVersion, {\n blocked(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocked from upgrading from ${currentVersion} to ${blockedVersion}`, event)\n },\n blocking(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocking upgrade from ${currentVersion} to ${blockedVersion}`, event)\n },\n terminated() {\n console.log('IndexedDbArchivist: Terminated')\n },\n upgrade(database, oldVersion, newVersion, transaction) {\n // NOTE: This is called whenever the DB is created/updated. We could simply ensure the desired end\n // state but, out of an abundance of caution, we will just delete (so we know where we are starting\n // from a known good point) and recreate the desired state. This prioritizes resilience over data\n // retention but we can revisit that tradeoff when it becomes limiting. Because distributed browser\n // state is extremely hard to debug, this seems like fair tradeoff for now.\n if (oldVersion !== newVersion) {\n console.log(`IndexedDbArchivist: Upgrading from ${oldVersion} to ${newVersion}`)\n // Delete any existing databases that are not the current version\n const objectStores = transaction.objectStoreNames\n for (const name of objectStores) {\n try {\n database.deleteObjectStore(name)\n } catch {\n console.log(`IndexedDbArchivist: Failed to delete existing object store ${name}`)\n }\n }\n }\n // Create the store\n const store = database.createObjectStore(storeName, {\n // If it isn't explicitly set, create a value by auto incrementing.\n autoIncrement: true,\n })\n // Name the store\n store.name = storeName\n // Create an index on the hash\n for (const { key, multiEntry, unique } of indexes) {\n const indexKeys = Object.keys(key)\n const keys = indexKeys.length === 1 ? indexKeys[0] : indexKeys\n const indexName = buildStandardIndexName({ key, unique })\n store.createIndex(indexName, keys, { multiEntry, unique })\n }\n },\n })\n return db\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async useDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T> {\n // Get the initialized DB\n const db = await this.getInitializedDb()\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n}\n","export type IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\nexport const IndexedDbArchivistSchema: IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\n","import { ArchivistConfig, IndexDescription } from '@xyo-network/archivist-model'\n\nimport { IndexedDbArchivistSchema } from './Schema'\n\nexport type IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\nexport const IndexedDbArchivistConfigSchema: IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\n\nexport type IndexedDbArchivistConfig = ArchivistConfig<{\n /**\n * The database name\n */\n dbName?: string\n /**\n * The version of the DB, defaults to 1\n */\n dbVersion?: number\n schema: IndexedDbArchivistConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main archivist config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n"],"mappings":"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","names":["IndexedDbArchivistSchema","IndexedDbArchivistConfigSchema","IndexedDbArchivistSchema","IndexedDbArchivist","AbstractArchivist","configSchemas","IndexedDbArchivistConfigSchema","defaultDbName","defaultDbVersion","defaultStoreName","hashIndex","key","_hash","multiEntry","unique","dataHashIndex","$hash","schemaIndex","schema","hashIndexName","buildStandardIndexName","dataHashIndexName","schemaIndexName","dbName","config","name","dbVersion","queries","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","storeName","indexes","storage","allHandler","payloads","useDb","db","getAll","Promise","all","map","payload","PayloadBuilder","build","clearHandler","clear","deleteHandler","hashes","pairs","hashPairs","getHandler","hashesToDelete","flatMap","pair","distinctHashes","Set","found","hash","existing","getKeyFromIndex","delete","filter","exists","includes","getFromIndex","payloadsFromDataHashes","payloadsFromHash","has","add","payloadsFromDataHash","insertHandler","getInitializedDb","inserted","tx","transaction","store","objectStore","existingTopHash","index","get","put","done","close","startHandler","openDB","blocked","currentVersion","blockedVersion","event","console","warn","blocking","terminated","log","upgrade","database","oldVersion","newVersion","objectStores","objectStoreNames","deleteObjectStore","createObjectStore","autoIncrement","indexKeys","Object","keys","length","indexName","createIndex","callback","creatableModule"]}
1
+ {"version":3,"sources":["../../src/index.ts","../../src/Archivist.ts","../../src/Schema.ts","../../src/Config.ts"],"sourcesContent":["export * from './Archivist'\nexport * from './Config'\nexport * from './Params'\nexport * from './Schema'\n","import { exists } from '@xylabs/exists'\nimport { Hash } from '@xylabs/hex'\nimport { AbstractArchivist } from '@xyo-network/archivist-abstract'\nimport {\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ArchivistModuleEventData,\n buildStandardIndexName,\n IndexDescription,\n} from '@xyo-network/archivist-model'\nimport { creatableModule } from '@xyo-network/module-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, PayloadWithMeta } from '@xyo-network/payload-model'\nimport { IDBPDatabase, openDB } from 'idb'\n\nimport { IndexedDbArchivistConfigSchema } from './Config'\nimport { IndexedDbArchivistParams } from './Params'\n\nexport interface PayloadStore {\n [s: string]: Payload\n}\n\n@creatableModule()\nexport class IndexedDbArchivist<\n TParams extends IndexedDbArchivistParams = IndexedDbArchivistParams,\n TEventData extends ArchivistModuleEventData = ArchivistModuleEventData,\n> extends AbstractArchivist<TParams, TEventData> {\n static override configSchemas = [IndexedDbArchivistConfigSchema]\n static readonly defaultDbName = 'archivist'\n static readonly defaultDbVersion = 1\n static readonly defaultStoreName = 'payloads'\n private static readonly hashIndex: IndexDescription = { key: { _hash: 1 }, multiEntry: false, unique: true }\n private static readonly dataHashIndex: IndexDescription = { key: { $hash: 1 }, multiEntry: false, unique: false }\n private static readonly schemaIndex: IndexDescription = { key: { schema: 1 }, multiEntry: false, unique: false }\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly hashIndexName = buildStandardIndexName(IndexedDbArchivist.hashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly dataHashIndexName = buildStandardIndexName(IndexedDbArchivist.dataHashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly schemaIndexName = buildStandardIndexName(IndexedDbArchivist.schemaIndex)\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the module name (not guaranteed to be unique) and if module\n * name is not supplied, it defaults to `archivist`. This behavior\n * biases towards a single, isolated DB per archivist which seems to\n * make the most sense for 99% of use cases.\n */\n get dbName() {\n return this.config?.dbName ?? this.config?.name ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to 1.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\n }\n\n override get queries() {\n return [ArchivistAllQuerySchema, ArchivistClearQuerySchema, ArchivistDeleteQuerySchema, ArchivistInsertQuerySchema, ...super.queries]\n }\n\n /**\n * The name of the object store. If not supplied via config, it defaults\n * to `payloads`.\n */\n get storeName() {\n return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n /**\n * The indexes to create on the store\n */\n private get indexes() {\n return [IndexedDbArchivist.dataHashIndex, IndexedDbArchivist.hashIndex, IndexedDbArchivist.schemaIndex, ...(this.config?.storage?.indexes ?? [])]\n }\n\n protected override async allHandler(): Promise<PayloadWithMeta[]> {\n // Get all payloads from the store\n const payloads = await this.useDb((db) => db.getAll(this.storeName))\n // Remove any metadata before returning to the client\n return await Promise.all(payloads.map((payload) => PayloadBuilder.build(payload)))\n }\n\n protected override async clearHandler(): Promise<void> {\n await this.useDb((db) => db.clear(this.storeName))\n }\n\n protected override async deleteHandler(hashes: Hash[]): Promise<Hash[]> {\n const pairs = await PayloadBuilder.hashPairs(await this.getHandler(hashes))\n const hashesToDelete = pairs.flatMap<Hash>((pair) => [pair[0].$hash, pair[1]])\n // Remove any duplicates\n const distinctHashes = [...new Set(hashesToDelete)]\n return await this.useDb(async (db) => {\n // Only return hashes that were successfully deleted\n const found = await Promise.all(\n distinctHashes.map(async (hash) => {\n // Check if the hash exists\n const existing =\n (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash)) ??\n (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))\n // If it does exist\n if (existing) {\n // Delete it\n await db.delete(this.storeName, existing)\n // Return the hash so it gets added to the list of deleted hashes\n return hash\n }\n }),\n )\n return found.filter(exists).filter((hash) => hashes.includes(hash))\n })\n }\n\n /**\n * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)\n * @param db The db instance to use\n * @param storeName The name of the store to use\n * @param indexName The index to use\n * @param key The key to get from the index\n * @returns The primary key and the payload, or undefined if not found\n */\n protected async getFromIndexWithPrimaryKey(\n db: IDBPDatabase<PayloadStore>,\n storeName: string,\n indexName: string,\n key: IDBValidKey,\n ): Promise<[number, PayloadWithMeta] | undefined> {\n const transaction = db.transaction(storeName, 'readonly')\n const store = transaction.objectStore(storeName)\n const index = store.index(indexName)\n const cursor = await index.openCursor(key)\n if (cursor) {\n const singleValue = cursor.value\n // NOTE: It's known to be a number because we are using IndexedDB supplied auto-incrementing keys\n const primaryKey = cursor.primaryKey as number\n return [primaryKey, singleValue]\n }\n }\n\n protected override async getHandler(hashes: string[]): Promise<PayloadWithMeta[]> {\n const payloads = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.hashIndexName, hash))),\n )\n const payloadsFromDataHashes = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.dataHashIndexName, hash))),\n )\n //filter out duplicates\n const found = new Set<string>()\n const payloadsFromHash = payloads.filter(exists).filter(([_key, payload]) => {\n if (found.has(payload.$hash)) {\n return false\n } else {\n found.add(payload.$hash)\n return true\n }\n })\n const payloadsFromDataHash = payloadsFromDataHashes.filter(exists).filter(([_key, payload]) => {\n if (found.has(payload.$hash)) {\n return false\n } else {\n found.add(payload.$hash)\n return true\n }\n })\n return (\n // Merge what we found from the hash and data hash indexes\n [...payloadsFromHash, ...payloadsFromDataHash]\n // Sort in ascending order by primary key (for semi-predictable ordering in terms of insertion order)\n .sort((a, b) => a[0] - b[0])\n // Return just the payloads\n .map(([_key, payload]) => payload)\n )\n }\n\n protected override async insertHandler(payloads: Payload[]): Promise<PayloadWithMeta[]> {\n const pairs = await PayloadBuilder.hashPairs(payloads)\n\n const db = await this.getInitializedDb()\n try {\n // Only return the payloads that were successfully inserted\n const inserted = await Promise.all(\n pairs.map(async ([payload, _hash]) => {\n // Perform each insert via a transaction to ensure it is atomic\n // with respect to checking for the pre-existence of the hash.\n // This is done to preserve iteration via insertion order.\n const tx = db.transaction(this.storeName, 'readwrite')\n try {\n // Get the object store\n const store = tx.objectStore(this.storeName)\n\n // Check if the hash already exists\n const existingTopHash = await store.index(IndexedDbArchivist.hashIndexName).get(_hash)\n // If it does not already exist\n if (!existingTopHash) {\n // Insert the payload\n await store.put({ ...payload, _hash })\n }\n\n // Return it so it gets added to the list of inserted payloads\n return payload\n } finally {\n // Close the transaction\n await tx.done\n }\n }),\n )\n return inserted.filter(exists)\n } finally {\n db.close()\n }\n }\n\n protected override async startHandler() {\n await super.startHandler()\n // NOTE: We could defer this creation to first access but we\n // want to fail fast here in case something is wrong\n await this.useDb(() => {})\n return true\n }\n\n /**\n * Returns that the desired DB/Store initialized to the correct version\n * @returns The initialized DB\n */\n private async getInitializedDb(): Promise<IDBPDatabase<PayloadStore>> {\n const { dbName, dbVersion, indexes, storeName } = this\n const db = await openDB<PayloadStore>(dbName, dbVersion, {\n blocked(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocked from upgrading from ${currentVersion} to ${blockedVersion}`, event)\n },\n blocking(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocking upgrade from ${currentVersion} to ${blockedVersion}`, event)\n },\n terminated() {\n console.log('IndexedDbArchivist: Terminated')\n },\n upgrade(database, oldVersion, newVersion, transaction) {\n // NOTE: This is called whenever the DB is created/updated. We could simply ensure the desired end\n // state but, out of an abundance of caution, we will just delete (so we know where we are starting\n // from a known good point) and recreate the desired state. This prioritizes resilience over data\n // retention but we can revisit that tradeoff when it becomes limiting. Because distributed browser\n // state is extremely hard to debug, this seems like fair tradeoff for now.\n if (oldVersion !== newVersion) {\n console.log(`IndexedDbArchivist: Upgrading from ${oldVersion} to ${newVersion}`)\n // Delete any existing databases that are not the current version\n const objectStores = transaction.objectStoreNames\n for (const name of objectStores) {\n try {\n database.deleteObjectStore(name)\n } catch {\n console.log(`IndexedDbArchivist: Failed to delete existing object store ${name}`)\n }\n }\n }\n // Create the store\n const store = database.createObjectStore(storeName, {\n // If it isn't explicitly set, create a value by auto incrementing.\n autoIncrement: true,\n })\n // Name the store\n store.name = storeName\n // Create an index on the hash\n for (const { key, multiEntry, unique } of indexes) {\n const indexKeys = Object.keys(key)\n const keys = indexKeys.length === 1 ? indexKeys[0] : indexKeys\n const indexName = buildStandardIndexName({ key, unique })\n store.createIndex(indexName, keys, { multiEntry, unique })\n }\n },\n })\n return db\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async useDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T> {\n // Get the initialized DB\n const db = await this.getInitializedDb()\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n}\n","export type IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\nexport const IndexedDbArchivistSchema: IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\n","import { ArchivistConfig, IndexDescription } from '@xyo-network/archivist-model'\n\nimport { IndexedDbArchivistSchema } from './Schema'\n\nexport type IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\nexport const IndexedDbArchivistConfigSchema: IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\n\nexport type IndexedDbArchivistConfig = ArchivistConfig<{\n /**\n * The database name\n */\n dbName?: string\n /**\n * The version of the DB, defaults to 1\n */\n dbVersion?: number\n schema: IndexedDbArchivistConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main archivist config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n"],"mappings":"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","names":["IndexedDbArchivistSchema","IndexedDbArchivistConfigSchema","IndexedDbArchivistSchema","IndexedDbArchivist","AbstractArchivist","configSchemas","IndexedDbArchivistConfigSchema","defaultDbName","defaultDbVersion","defaultStoreName","hashIndex","key","_hash","multiEntry","unique","dataHashIndex","$hash","schemaIndex","schema","hashIndexName","buildStandardIndexName","dataHashIndexName","schemaIndexName","dbName","config","name","dbVersion","queries","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","storeName","indexes","storage","allHandler","payloads","useDb","db","getAll","Promise","all","map","payload","PayloadBuilder","build","clearHandler","clear","deleteHandler","hashes","pairs","hashPairs","getHandler","hashesToDelete","flatMap","pair","distinctHashes","Set","found","hash","existing","getKeyFromIndex","delete","filter","exists","includes","getFromIndexWithPrimaryKey","indexName","transaction","store","objectStore","index","cursor","openCursor","singleValue","value","primaryKey","payloadsFromDataHashes","payloadsFromHash","_key","has","add","payloadsFromDataHash","sort","a","b","insertHandler","getInitializedDb","inserted","tx","existingTopHash","get","put","done","close","startHandler","openDB","blocked","currentVersion","blockedVersion","event","console","warn","blocking","terminated","log","upgrade","database","oldVersion","newVersion","objectStores","objectStoreNames","deleteObjectStore","createObjectStore","autoIncrement","indexKeys","Object","keys","length","createIndex","callback","creatableModule"]}
@@ -134,11 +134,33 @@ var IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
134
134
  return found.filter(exists).filter((hash) => hashes.includes(hash));
135
135
  });
136
136
  }
137
+ /**
138
+ * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)
139
+ * @param db The db instance to use
140
+ * @param storeName The name of the store to use
141
+ * @param indexName The index to use
142
+ * @param key The key to get from the index
143
+ * @returns The primary key and the payload, or undefined if not found
144
+ */
145
+ async getFromIndexWithPrimaryKey(db, storeName, indexName, key) {
146
+ const transaction = db.transaction(storeName, "readonly");
147
+ const store = transaction.objectStore(storeName);
148
+ const index = store.index(indexName);
149
+ const cursor = await index.openCursor(key);
150
+ if (cursor) {
151
+ const singleValue = cursor.value;
152
+ const primaryKey = cursor.primaryKey;
153
+ return [
154
+ primaryKey,
155
+ singleValue
156
+ ];
157
+ }
158
+ }
137
159
  async getHandler(hashes) {
138
- const payloads = await this.useDb((db) => Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, _IndexedDbArchivist.hashIndexName, hash))));
139
- const payloadsFromDataHashes = await this.useDb((db) => Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, _IndexedDbArchivist.dataHashIndexName, hash))));
160
+ const payloads = await this.useDb((db) => Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, _IndexedDbArchivist.hashIndexName, hash))));
161
+ const payloadsFromDataHashes = await this.useDb((db) => Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, _IndexedDbArchivist.dataHashIndexName, hash))));
140
162
  const found = /* @__PURE__ */ new Set();
141
- const payloadsFromHash = payloads.filter(exists).filter((payload) => {
163
+ const payloadsFromHash = payloads.filter(exists).filter(([_key, payload]) => {
142
164
  if (found.has(payload.$hash)) {
143
165
  return false;
144
166
  } else {
@@ -146,7 +168,7 @@ var IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
146
168
  return true;
147
169
  }
148
170
  });
149
- const payloadsFromDataHash = payloadsFromDataHashes.filter(exists).filter((payload) => {
171
+ const payloadsFromDataHash = payloadsFromDataHashes.filter(exists).filter(([_key, payload]) => {
150
172
  if (found.has(payload.$hash)) {
151
173
  return false;
152
174
  } else {
@@ -154,10 +176,13 @@ var IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
154
176
  return true;
155
177
  }
156
178
  });
157
- return [
158
- ...payloadsFromHash,
159
- ...payloadsFromDataHash
160
- ];
179
+ return (
180
+ // Merge what we found from the hash and data hash indexes
181
+ [
182
+ ...payloadsFromHash,
183
+ ...payloadsFromDataHash
184
+ ].sort((a, b) => a[0] - b[0]).map(([_key, payload]) => payload)
185
+ );
161
186
  }
162
187
  async insertHandler(payloads) {
163
188
  const pairs = await PayloadBuilder.hashPairs(payloads);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/Archivist.ts","../../src/Schema.ts","../../src/Config.ts"],"sourcesContent":["import { exists } from '@xylabs/exists'\nimport { Hash } from '@xylabs/hex'\nimport { AbstractArchivist } from '@xyo-network/archivist-abstract'\nimport {\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ArchivistModuleEventData,\n buildStandardIndexName,\n IndexDescription,\n} from '@xyo-network/archivist-model'\nimport { creatableModule } from '@xyo-network/module-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, PayloadWithMeta } from '@xyo-network/payload-model'\nimport { IDBPDatabase, openDB } from 'idb'\n\nimport { IndexedDbArchivistConfigSchema } from './Config'\nimport { IndexedDbArchivistParams } from './Params'\n\nexport interface PayloadStore {\n [s: string]: Payload\n}\n\n@creatableModule()\nexport class IndexedDbArchivist<\n TParams extends IndexedDbArchivistParams = IndexedDbArchivistParams,\n TEventData extends ArchivistModuleEventData = ArchivistModuleEventData,\n> extends AbstractArchivist<TParams, TEventData> {\n static override configSchemas = [IndexedDbArchivistConfigSchema]\n static readonly defaultDbName = 'archivist'\n static readonly defaultDbVersion = 1\n static readonly defaultStoreName = 'payloads'\n private static readonly hashIndex: IndexDescription = { key: { _hash: 1 }, multiEntry: false, unique: true }\n private static readonly dataHashIndex: IndexDescription = { key: { $hash: 1 }, multiEntry: false, unique: false }\n private static readonly schemaIndex: IndexDescription = { key: { schema: 1 }, multiEntry: false, unique: false }\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly hashIndexName = buildStandardIndexName(IndexedDbArchivist.hashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly dataHashIndexName = buildStandardIndexName(IndexedDbArchivist.dataHashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly schemaIndexName = buildStandardIndexName(IndexedDbArchivist.schemaIndex)\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the module name (not guaranteed to be unique) and if module\n * name is not supplied, it defaults to `archivist`. This behavior\n * biases towards a single, isolated DB per archivist which seems to\n * make the most sense for 99% of use cases.\n */\n get dbName() {\n return this.config?.dbName ?? this.config?.name ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to 1.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\n }\n\n override get queries() {\n return [ArchivistAllQuerySchema, ArchivistClearQuerySchema, ArchivistDeleteQuerySchema, ArchivistInsertQuerySchema, ...super.queries]\n }\n\n /**\n * The name of the object store. If not supplied via config, it defaults\n * to `payloads`.\n */\n get storeName() {\n return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n /**\n * The indexes to create on the store\n */\n private get indexes() {\n return [IndexedDbArchivist.dataHashIndex, IndexedDbArchivist.hashIndex, IndexedDbArchivist.schemaIndex, ...(this.config?.storage?.indexes ?? [])]\n }\n\n protected override async allHandler(): Promise<PayloadWithMeta[]> {\n // Get all payloads from the store\n const payloads = await this.useDb((db) => db.getAll(this.storeName))\n // Remove any metadata before returning to the client\n return await Promise.all(payloads.map((payload) => PayloadBuilder.build(payload)))\n }\n\n protected override async clearHandler(): Promise<void> {\n await this.useDb((db) => db.clear(this.storeName))\n }\n\n protected override async deleteHandler(hashes: string[]): Promise<string[]> {\n const pairs = await PayloadBuilder.hashPairs(await this.getHandler(hashes))\n const hashesToDelete = pairs.flatMap<Hash>((pair) => [pair[0].$hash, pair[1]])\n // Remove any duplicates\n const distinctHashes = [...new Set(hashesToDelete)]\n return await this.useDb(async (db) => {\n // Only return hashes that were successfully deleted\n const found = await Promise.all(\n distinctHashes.map(async (hash) => {\n // Check if the hash exists\n const existing =\n (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash)) ??\n (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))\n // If it does exist\n if (existing) {\n // Delete it\n await db.delete(this.storeName, existing)\n // Return the hash so it gets added to the list of deleted hashes\n return hash\n }\n }),\n )\n return found.filter(exists).filter((hash) => hashes.includes(hash))\n })\n }\n\n protected override async getHandler(hashes: string[]): Promise<PayloadWithMeta[]> {\n const payloads = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash))),\n )\n const payloadsFromDataHashes = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))),\n )\n //filter out duplicates\n const found = new Set<string>()\n const payloadsFromHash = payloads.filter(exists).filter((payload) => {\n if (found.has(payload.$hash)) {\n return false\n } else {\n found.add(payload.$hash)\n return true\n }\n })\n const payloadsFromDataHash = payloadsFromDataHashes.filter(exists).filter((payload) => {\n if (found.has(payload.$hash)) {\n return false\n } else {\n found.add(payload.$hash)\n return true\n }\n })\n return [...payloadsFromHash, ...payloadsFromDataHash]\n }\n\n protected override async insertHandler(payloads: Payload[]): Promise<PayloadWithMeta[]> {\n const pairs = await PayloadBuilder.hashPairs(payloads)\n\n const db = await this.getInitializedDb()\n try {\n // Only return the payloads that were successfully inserted\n const inserted = await Promise.all(\n pairs.map(async ([payload, _hash]) => {\n // Perform each insert via a transaction to ensure it is atomic\n // with respect to checking for the pre-existence of the hash.\n // This is done to preserve iteration via insertion order.\n const tx = db.transaction(this.storeName, 'readwrite')\n try {\n // Get the object store\n const store = tx.objectStore(this.storeName)\n\n // Check if the hash already exists\n const existingTopHash = await store.index(IndexedDbArchivist.hashIndexName).get(_hash)\n // If it does not already exist\n if (!existingTopHash) {\n // Insert the payload\n await store.put({ ...payload, _hash })\n }\n\n // Return it so it gets added to the list of inserted payloads\n return payload\n } finally {\n // Close the transaction\n await tx.done\n }\n }),\n )\n return inserted.filter(exists)\n } finally {\n db.close()\n }\n }\n\n protected override async startHandler() {\n await super.startHandler()\n // NOTE: We could defer this creation to first access but we\n // want to fail fast here in case something is wrong\n await this.useDb(() => {})\n return true\n }\n\n /**\n * Returns that the desired DB/Store initialized to the correct version\n * @returns The initialized DB\n */\n private async getInitializedDb(): Promise<IDBPDatabase<PayloadStore>> {\n const { dbName, dbVersion, indexes, storeName } = this\n const db = await openDB<PayloadStore>(dbName, dbVersion, {\n blocked(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocked from upgrading from ${currentVersion} to ${blockedVersion}`, event)\n },\n blocking(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocking upgrade from ${currentVersion} to ${blockedVersion}`, event)\n },\n terminated() {\n console.log('IndexedDbArchivist: Terminated')\n },\n upgrade(database, oldVersion, newVersion, transaction) {\n // NOTE: This is called whenever the DB is created/updated. We could simply ensure the desired end\n // state but, out of an abundance of caution, we will just delete (so we know where we are starting\n // from a known good point) and recreate the desired state. This prioritizes resilience over data\n // retention but we can revisit that tradeoff when it becomes limiting. Because distributed browser\n // state is extremely hard to debug, this seems like fair tradeoff for now.\n if (oldVersion !== newVersion) {\n console.log(`IndexedDbArchivist: Upgrading from ${oldVersion} to ${newVersion}`)\n // Delete any existing databases that are not the current version\n const objectStores = transaction.objectStoreNames\n for (const name of objectStores) {\n try {\n database.deleteObjectStore(name)\n } catch {\n console.log(`IndexedDbArchivist: Failed to delete existing object store ${name}`)\n }\n }\n }\n // Create the store\n const store = database.createObjectStore(storeName, {\n // If it isn't explicitly set, create a value by auto incrementing.\n autoIncrement: true,\n })\n // Name the store\n store.name = storeName\n // Create an index on the hash\n for (const { key, multiEntry, unique } of indexes) {\n const indexKeys = Object.keys(key)\n const keys = indexKeys.length === 1 ? indexKeys[0] : indexKeys\n const indexName = buildStandardIndexName({ key, unique })\n store.createIndex(indexName, keys, { multiEntry, unique })\n }\n },\n })\n return db\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async useDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T> {\n // Get the initialized DB\n const db = await this.getInitializedDb()\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n}\n","export type IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\nexport const IndexedDbArchivistSchema: IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\n","import { ArchivistConfig, IndexDescription } from '@xyo-network/archivist-model'\n\nimport { IndexedDbArchivistSchema } from './Schema'\n\nexport type IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\nexport const IndexedDbArchivistConfigSchema: IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\n\nexport type IndexedDbArchivistConfig = ArchivistConfig<{\n /**\n * The database name\n */\n dbName?: string\n /**\n * The version of the DB, defaults to 1\n */\n dbVersion?: number\n schema: IndexedDbArchivistConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main archivist config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n"],"mappings":"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","names":["exists","AbstractArchivist","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","buildStandardIndexName","creatableModule","PayloadBuilder","openDB","IndexedDbArchivistSchema","IndexedDbArchivistConfigSchema","IndexedDbArchivistSchema","IndexedDbArchivist","AbstractArchivist","configSchemas","IndexedDbArchivistConfigSchema","defaultDbName","defaultDbVersion","defaultStoreName","hashIndex","key","_hash","multiEntry","unique","dataHashIndex","$hash","schemaIndex","schema","hashIndexName","buildStandardIndexName","dataHashIndexName","schemaIndexName","dbName","config","name","dbVersion","queries","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","storeName","indexes","storage","allHandler","payloads","useDb","db","getAll","Promise","all","map","payload","PayloadBuilder","build","clearHandler","clear","deleteHandler","hashes","pairs","hashPairs","getHandler","hashesToDelete","flatMap","pair","distinctHashes","Set","found","hash","existing","getKeyFromIndex","delete","filter","exists","includes","getFromIndex","payloadsFromDataHashes","payloadsFromHash","has","add","payloadsFromDataHash","insertHandler","getInitializedDb","inserted","tx","transaction","store","objectStore","existingTopHash","index","get","put","done","close","startHandler","openDB","blocked","currentVersion","blockedVersion","event","console","warn","blocking","terminated","log","upgrade","database","oldVersion","newVersion","objectStores","objectStoreNames","deleteObjectStore","createObjectStore","autoIncrement","indexKeys","Object","keys","length","indexName","createIndex","callback","creatableModule"]}
1
+ {"version":3,"sources":["../../src/Archivist.ts","../../src/Schema.ts","../../src/Config.ts"],"sourcesContent":["import { exists } from '@xylabs/exists'\nimport { Hash } from '@xylabs/hex'\nimport { AbstractArchivist } from '@xyo-network/archivist-abstract'\nimport {\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ArchivistModuleEventData,\n buildStandardIndexName,\n IndexDescription,\n} from '@xyo-network/archivist-model'\nimport { creatableModule } from '@xyo-network/module-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, PayloadWithMeta } from '@xyo-network/payload-model'\nimport { IDBPDatabase, openDB } from 'idb'\n\nimport { IndexedDbArchivistConfigSchema } from './Config'\nimport { IndexedDbArchivistParams } from './Params'\n\nexport interface PayloadStore {\n [s: string]: Payload\n}\n\n@creatableModule()\nexport class IndexedDbArchivist<\n TParams extends IndexedDbArchivistParams = IndexedDbArchivistParams,\n TEventData extends ArchivistModuleEventData = ArchivistModuleEventData,\n> extends AbstractArchivist<TParams, TEventData> {\n static override configSchemas = [IndexedDbArchivistConfigSchema]\n static readonly defaultDbName = 'archivist'\n static readonly defaultDbVersion = 1\n static readonly defaultStoreName = 'payloads'\n private static readonly hashIndex: IndexDescription = { key: { _hash: 1 }, multiEntry: false, unique: true }\n private static readonly dataHashIndex: IndexDescription = { key: { $hash: 1 }, multiEntry: false, unique: false }\n private static readonly schemaIndex: IndexDescription = { key: { schema: 1 }, multiEntry: false, unique: false }\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly hashIndexName = buildStandardIndexName(IndexedDbArchivist.hashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly dataHashIndexName = buildStandardIndexName(IndexedDbArchivist.dataHashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly schemaIndexName = buildStandardIndexName(IndexedDbArchivist.schemaIndex)\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the module name (not guaranteed to be unique) and if module\n * name is not supplied, it defaults to `archivist`. This behavior\n * biases towards a single, isolated DB per archivist which seems to\n * make the most sense for 99% of use cases.\n */\n get dbName() {\n return this.config?.dbName ?? this.config?.name ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to 1.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\n }\n\n override get queries() {\n return [ArchivistAllQuerySchema, ArchivistClearQuerySchema, ArchivistDeleteQuerySchema, ArchivistInsertQuerySchema, ...super.queries]\n }\n\n /**\n * The name of the object store. If not supplied via config, it defaults\n * to `payloads`.\n */\n get storeName() {\n return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n /**\n * The indexes to create on the store\n */\n private get indexes() {\n return [IndexedDbArchivist.dataHashIndex, IndexedDbArchivist.hashIndex, IndexedDbArchivist.schemaIndex, ...(this.config?.storage?.indexes ?? [])]\n }\n\n protected override async allHandler(): Promise<PayloadWithMeta[]> {\n // Get all payloads from the store\n const payloads = await this.useDb((db) => db.getAll(this.storeName))\n // Remove any metadata before returning to the client\n return await Promise.all(payloads.map((payload) => PayloadBuilder.build(payload)))\n }\n\n protected override async clearHandler(): Promise<void> {\n await this.useDb((db) => db.clear(this.storeName))\n }\n\n protected override async deleteHandler(hashes: Hash[]): Promise<Hash[]> {\n const pairs = await PayloadBuilder.hashPairs(await this.getHandler(hashes))\n const hashesToDelete = pairs.flatMap<Hash>((pair) => [pair[0].$hash, pair[1]])\n // Remove any duplicates\n const distinctHashes = [...new Set(hashesToDelete)]\n return await this.useDb(async (db) => {\n // Only return hashes that were successfully deleted\n const found = await Promise.all(\n distinctHashes.map(async (hash) => {\n // Check if the hash exists\n const existing =\n (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash)) ??\n (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))\n // If it does exist\n if (existing) {\n // Delete it\n await db.delete(this.storeName, existing)\n // Return the hash so it gets added to the list of deleted hashes\n return hash\n }\n }),\n )\n return found.filter(exists).filter((hash) => hashes.includes(hash))\n })\n }\n\n /**\n * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)\n * @param db The db instance to use\n * @param storeName The name of the store to use\n * @param indexName The index to use\n * @param key The key to get from the index\n * @returns The primary key and the payload, or undefined if not found\n */\n protected async getFromIndexWithPrimaryKey(\n db: IDBPDatabase<PayloadStore>,\n storeName: string,\n indexName: string,\n key: IDBValidKey,\n ): Promise<[number, PayloadWithMeta] | undefined> {\n const transaction = db.transaction(storeName, 'readonly')\n const store = transaction.objectStore(storeName)\n const index = store.index(indexName)\n const cursor = await index.openCursor(key)\n if (cursor) {\n const singleValue = cursor.value\n // NOTE: It's known to be a number because we are using IndexedDB supplied auto-incrementing keys\n const primaryKey = cursor.primaryKey as number\n return [primaryKey, singleValue]\n }\n }\n\n protected override async getHandler(hashes: string[]): Promise<PayloadWithMeta[]> {\n const payloads = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.hashIndexName, hash))),\n )\n const payloadsFromDataHashes = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.dataHashIndexName, hash))),\n )\n //filter out duplicates\n const found = new Set<string>()\n const payloadsFromHash = payloads.filter(exists).filter(([_key, payload]) => {\n if (found.has(payload.$hash)) {\n return false\n } else {\n found.add(payload.$hash)\n return true\n }\n })\n const payloadsFromDataHash = payloadsFromDataHashes.filter(exists).filter(([_key, payload]) => {\n if (found.has(payload.$hash)) {\n return false\n } else {\n found.add(payload.$hash)\n return true\n }\n })\n return (\n // Merge what we found from the hash and data hash indexes\n [...payloadsFromHash, ...payloadsFromDataHash]\n // Sort in ascending order by primary key (for semi-predictable ordering in terms of insertion order)\n .sort((a, b) => a[0] - b[0])\n // Return just the payloads\n .map(([_key, payload]) => payload)\n )\n }\n\n protected override async insertHandler(payloads: Payload[]): Promise<PayloadWithMeta[]> {\n const pairs = await PayloadBuilder.hashPairs(payloads)\n\n const db = await this.getInitializedDb()\n try {\n // Only return the payloads that were successfully inserted\n const inserted = await Promise.all(\n pairs.map(async ([payload, _hash]) => {\n // Perform each insert via a transaction to ensure it is atomic\n // with respect to checking for the pre-existence of the hash.\n // This is done to preserve iteration via insertion order.\n const tx = db.transaction(this.storeName, 'readwrite')\n try {\n // Get the object store\n const store = tx.objectStore(this.storeName)\n\n // Check if the hash already exists\n const existingTopHash = await store.index(IndexedDbArchivist.hashIndexName).get(_hash)\n // If it does not already exist\n if (!existingTopHash) {\n // Insert the payload\n await store.put({ ...payload, _hash })\n }\n\n // Return it so it gets added to the list of inserted payloads\n return payload\n } finally {\n // Close the transaction\n await tx.done\n }\n }),\n )\n return inserted.filter(exists)\n } finally {\n db.close()\n }\n }\n\n protected override async startHandler() {\n await super.startHandler()\n // NOTE: We could defer this creation to first access but we\n // want to fail fast here in case something is wrong\n await this.useDb(() => {})\n return true\n }\n\n /**\n * Returns that the desired DB/Store initialized to the correct version\n * @returns The initialized DB\n */\n private async getInitializedDb(): Promise<IDBPDatabase<PayloadStore>> {\n const { dbName, dbVersion, indexes, storeName } = this\n const db = await openDB<PayloadStore>(dbName, dbVersion, {\n blocked(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocked from upgrading from ${currentVersion} to ${blockedVersion}`, event)\n },\n blocking(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocking upgrade from ${currentVersion} to ${blockedVersion}`, event)\n },\n terminated() {\n console.log('IndexedDbArchivist: Terminated')\n },\n upgrade(database, oldVersion, newVersion, transaction) {\n // NOTE: This is called whenever the DB is created/updated. We could simply ensure the desired end\n // state but, out of an abundance of caution, we will just delete (so we know where we are starting\n // from a known good point) and recreate the desired state. This prioritizes resilience over data\n // retention but we can revisit that tradeoff when it becomes limiting. Because distributed browser\n // state is extremely hard to debug, this seems like fair tradeoff for now.\n if (oldVersion !== newVersion) {\n console.log(`IndexedDbArchivist: Upgrading from ${oldVersion} to ${newVersion}`)\n // Delete any existing databases that are not the current version\n const objectStores = transaction.objectStoreNames\n for (const name of objectStores) {\n try {\n database.deleteObjectStore(name)\n } catch {\n console.log(`IndexedDbArchivist: Failed to delete existing object store ${name}`)\n }\n }\n }\n // Create the store\n const store = database.createObjectStore(storeName, {\n // If it isn't explicitly set, create a value by auto incrementing.\n autoIncrement: true,\n })\n // Name the store\n store.name = storeName\n // Create an index on the hash\n for (const { key, multiEntry, unique } of indexes) {\n const indexKeys = Object.keys(key)\n const keys = indexKeys.length === 1 ? indexKeys[0] : indexKeys\n const indexName = buildStandardIndexName({ key, unique })\n store.createIndex(indexName, keys, { multiEntry, unique })\n }\n },\n })\n return db\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async useDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T> {\n // Get the initialized DB\n const db = await this.getInitializedDb()\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n}\n","export type IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\nexport const IndexedDbArchivistSchema: IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\n","import { ArchivistConfig, IndexDescription } from '@xyo-network/archivist-model'\n\nimport { IndexedDbArchivistSchema } from './Schema'\n\nexport type IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\nexport const IndexedDbArchivistConfigSchema: IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\n\nexport type IndexedDbArchivistConfig = ArchivistConfig<{\n /**\n * The database name\n */\n dbName?: string\n /**\n * The version of the DB, defaults to 1\n */\n dbVersion?: number\n schema: IndexedDbArchivistConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main archivist config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n"],"mappings":"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","names":["exists","AbstractArchivist","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","buildStandardIndexName","creatableModule","PayloadBuilder","openDB","IndexedDbArchivistSchema","IndexedDbArchivistConfigSchema","IndexedDbArchivistSchema","IndexedDbArchivist","AbstractArchivist","configSchemas","IndexedDbArchivistConfigSchema","defaultDbName","defaultDbVersion","defaultStoreName","hashIndex","key","_hash","multiEntry","unique","dataHashIndex","$hash","schemaIndex","schema","hashIndexName","buildStandardIndexName","dataHashIndexName","schemaIndexName","dbName","config","name","dbVersion","queries","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","storeName","indexes","storage","allHandler","payloads","useDb","db","getAll","Promise","all","map","payload","PayloadBuilder","build","clearHandler","clear","deleteHandler","hashes","pairs","hashPairs","getHandler","hashesToDelete","flatMap","pair","distinctHashes","Set","found","hash","existing","getKeyFromIndex","delete","filter","exists","includes","getFromIndexWithPrimaryKey","indexName","transaction","store","objectStore","index","cursor","openCursor","singleValue","value","primaryKey","payloadsFromDataHashes","payloadsFromHash","_key","has","add","payloadsFromDataHash","sort","a","b","insertHandler","getInitializedDb","inserted","tx","existingTopHash","get","put","done","close","startHandler","openDB","blocked","currentVersion","blockedVersion","event","console","warn","blocking","terminated","log","upgrade","database","oldVersion","newVersion","objectStores","objectStoreNames","deleteObjectStore","createObjectStore","autoIncrement","indexKeys","Object","keys","length","createIndex","callback","creatableModule"]}
@@ -1,6 +1,8 @@
1
+ import { Hash } from '@xylabs/hex';
1
2
  import { AbstractArchivist } from '@xyo-network/archivist-abstract';
2
3
  import { ArchivistModuleEventData } from '@xyo-network/archivist-model';
3
4
  import { Payload, PayloadWithMeta } from '@xyo-network/payload-model';
5
+ import { IDBPDatabase } from 'idb';
4
6
  import { IndexedDbArchivistParams } from './Params';
5
7
  export interface PayloadStore {
6
8
  [s: string]: Payload;
@@ -40,7 +42,16 @@ export declare class IndexedDbArchivist<TParams extends IndexedDbArchivistParams
40
42
  private get indexes();
41
43
  protected allHandler(): Promise<PayloadWithMeta[]>;
42
44
  protected clearHandler(): Promise<void>;
43
- protected deleteHandler(hashes: string[]): Promise<string[]>;
45
+ protected deleteHandler(hashes: Hash[]): Promise<Hash[]>;
46
+ /**
47
+ * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)
48
+ * @param db The db instance to use
49
+ * @param storeName The name of the store to use
50
+ * @param indexName The index to use
51
+ * @param key The key to get from the index
52
+ * @returns The primary key and the payload, or undefined if not found
53
+ */
54
+ protected getFromIndexWithPrimaryKey(db: IDBPDatabase<PayloadStore>, storeName: string, indexName: string, key: IDBValidKey): Promise<[number, PayloadWithMeta] | undefined>;
44
55
  protected getHandler(hashes: string[]): Promise<PayloadWithMeta[]>;
45
56
  protected insertHandler(payloads: Payload[]): Promise<PayloadWithMeta[]>;
46
57
  protected startHandler(): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"Archivist.d.ts","sourceRoot":"","sources":["../../src/Archivist.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAKL,wBAAwB,EAGzB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAIrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AAEnD,MAAM,WAAW,YAAY;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAED,qBACa,kBAAkB,CAC7B,OAAO,SAAS,wBAAwB,GAAG,wBAAwB,EACnE,UAAU,SAAS,wBAAwB,GAAG,wBAAwB,CACtE,SAAQ,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC;IAC9C,OAAgB,aAAa,6CAAmC;IAChE,MAAM,CAAC,QAAQ,CAAC,aAAa,eAAc;IAC3C,MAAM,CAAC,QAAQ,CAAC,gBAAgB,KAAI;IACpC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,cAAa;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAC5G,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAA4E;IACjH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAA6E;IAEhH,MAAM,CAAC,QAAQ,CAAC,aAAa,SAAuD;IAEpF,MAAM,CAAC,QAAQ,CAAC,iBAAiB,SAA2D;IAE5F,MAAM,CAAC,QAAQ,CAAC,eAAe,SAAyD;IAExF;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED,IAAa,OAAO,aAEnB;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED;;OAEG;IACH,OAAO,KAAK,OAAO,GAElB;cAEwB,UAAU,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;cAOxC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;cAI7B,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;cA0BlD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cA4BxD,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAsC9D,YAAY;IAQrC;;;OAGG;YACW,gBAAgB;IAiD9B;;;;OAIG;YACW,KAAK;CAWpB"}
1
+ {"version":3,"file":"Archivist.d.ts","sourceRoot":"","sources":["../../src/Archivist.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAKL,wBAAwB,EAGzB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,YAAY,EAAU,MAAM,KAAK,CAAA;AAG1C,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AAEnD,MAAM,WAAW,YAAY;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAED,qBACa,kBAAkB,CAC7B,OAAO,SAAS,wBAAwB,GAAG,wBAAwB,EACnE,UAAU,SAAS,wBAAwB,GAAG,wBAAwB,CACtE,SAAQ,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC;IAC9C,OAAgB,aAAa,6CAAmC;IAChE,MAAM,CAAC,QAAQ,CAAC,aAAa,eAAc;IAC3C,MAAM,CAAC,QAAQ,CAAC,gBAAgB,KAAI;IACpC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,cAAa;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAC5G,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAA4E;IACjH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAA6E;IAEhH,MAAM,CAAC,QAAQ,CAAC,aAAa,SAAuD;IAEpF,MAAM,CAAC,QAAQ,CAAC,iBAAiB,SAA2D;IAE5F,MAAM,CAAC,QAAQ,CAAC,eAAe,SAAyD;IAExF;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED,IAAa,OAAO,aAEnB;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED;;OAEG;IACH,OAAO,KAAK,OAAO,GAElB;cAEwB,UAAU,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;cAOxC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;cAI7B,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IA0BvE;;;;;;;OAOG;cACa,0BAA0B,CACxC,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,CAAC;cAaxB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAmCxD,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAsC9D,YAAY;IAQrC;;;OAGG;YACW,gBAAgB;IAiD9B;;;;OAIG;YACW,KAAK;CAWpB"}
@@ -1,6 +1,8 @@
1
+ import { Hash } from '@xylabs/hex';
1
2
  import { AbstractArchivist } from '@xyo-network/archivist-abstract';
2
3
  import { ArchivistModuleEventData } from '@xyo-network/archivist-model';
3
4
  import { Payload, PayloadWithMeta } from '@xyo-network/payload-model';
5
+ import { IDBPDatabase } from 'idb';
4
6
  import { IndexedDbArchivistParams } from './Params';
5
7
  export interface PayloadStore {
6
8
  [s: string]: Payload;
@@ -40,7 +42,16 @@ export declare class IndexedDbArchivist<TParams extends IndexedDbArchivistParams
40
42
  private get indexes();
41
43
  protected allHandler(): Promise<PayloadWithMeta[]>;
42
44
  protected clearHandler(): Promise<void>;
43
- protected deleteHandler(hashes: string[]): Promise<string[]>;
45
+ protected deleteHandler(hashes: Hash[]): Promise<Hash[]>;
46
+ /**
47
+ * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)
48
+ * @param db The db instance to use
49
+ * @param storeName The name of the store to use
50
+ * @param indexName The index to use
51
+ * @param key The key to get from the index
52
+ * @returns The primary key and the payload, or undefined if not found
53
+ */
54
+ protected getFromIndexWithPrimaryKey(db: IDBPDatabase<PayloadStore>, storeName: string, indexName: string, key: IDBValidKey): Promise<[number, PayloadWithMeta] | undefined>;
44
55
  protected getHandler(hashes: string[]): Promise<PayloadWithMeta[]>;
45
56
  protected insertHandler(payloads: Payload[]): Promise<PayloadWithMeta[]>;
46
57
  protected startHandler(): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"Archivist.d.ts","sourceRoot":"","sources":["../../src/Archivist.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAKL,wBAAwB,EAGzB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAIrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AAEnD,MAAM,WAAW,YAAY;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAED,qBACa,kBAAkB,CAC7B,OAAO,SAAS,wBAAwB,GAAG,wBAAwB,EACnE,UAAU,SAAS,wBAAwB,GAAG,wBAAwB,CACtE,SAAQ,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC;IAC9C,OAAgB,aAAa,6CAAmC;IAChE,MAAM,CAAC,QAAQ,CAAC,aAAa,eAAc;IAC3C,MAAM,CAAC,QAAQ,CAAC,gBAAgB,KAAI;IACpC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,cAAa;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAC5G,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAA4E;IACjH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAA6E;IAEhH,MAAM,CAAC,QAAQ,CAAC,aAAa,SAAuD;IAEpF,MAAM,CAAC,QAAQ,CAAC,iBAAiB,SAA2D;IAE5F,MAAM,CAAC,QAAQ,CAAC,eAAe,SAAyD;IAExF;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED,IAAa,OAAO,aAEnB;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED;;OAEG;IACH,OAAO,KAAK,OAAO,GAElB;cAEwB,UAAU,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;cAOxC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;cAI7B,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;cA0BlD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cA4BxD,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAsC9D,YAAY;IAQrC;;;OAGG;YACW,gBAAgB;IAiD9B;;;;OAIG;YACW,KAAK;CAWpB"}
1
+ {"version":3,"file":"Archivist.d.ts","sourceRoot":"","sources":["../../src/Archivist.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAKL,wBAAwB,EAGzB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,YAAY,EAAU,MAAM,KAAK,CAAA;AAG1C,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AAEnD,MAAM,WAAW,YAAY;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAED,qBACa,kBAAkB,CAC7B,OAAO,SAAS,wBAAwB,GAAG,wBAAwB,EACnE,UAAU,SAAS,wBAAwB,GAAG,wBAAwB,CACtE,SAAQ,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC;IAC9C,OAAgB,aAAa,6CAAmC;IAChE,MAAM,CAAC,QAAQ,CAAC,aAAa,eAAc;IAC3C,MAAM,CAAC,QAAQ,CAAC,gBAAgB,KAAI;IACpC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,cAAa;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAC5G,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAA4E;IACjH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAA6E;IAEhH,MAAM,CAAC,QAAQ,CAAC,aAAa,SAAuD;IAEpF,MAAM,CAAC,QAAQ,CAAC,iBAAiB,SAA2D;IAE5F,MAAM,CAAC,QAAQ,CAAC,eAAe,SAAyD;IAExF;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED,IAAa,OAAO,aAEnB;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED;;OAEG;IACH,OAAO,KAAK,OAAO,GAElB;cAEwB,UAAU,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;cAOxC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;cAI7B,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IA0BvE;;;;;;;OAOG;cACa,0BAA0B,CACxC,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,CAAC;cAaxB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAmCxD,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAsC9D,YAAY;IAQrC;;;OAGG;YACW,gBAAgB;IAiD9B;;;;OAIG;YACW,KAAK;CAWpB"}
@@ -1,6 +1,8 @@
1
+ import { Hash } from '@xylabs/hex';
1
2
  import { AbstractArchivist } from '@xyo-network/archivist-abstract';
2
3
  import { ArchivistModuleEventData } from '@xyo-network/archivist-model';
3
4
  import { Payload, PayloadWithMeta } from '@xyo-network/payload-model';
5
+ import { IDBPDatabase } from 'idb';
4
6
  import { IndexedDbArchivistParams } from './Params';
5
7
  export interface PayloadStore {
6
8
  [s: string]: Payload;
@@ -40,7 +42,16 @@ export declare class IndexedDbArchivist<TParams extends IndexedDbArchivistParams
40
42
  private get indexes();
41
43
  protected allHandler(): Promise<PayloadWithMeta[]>;
42
44
  protected clearHandler(): Promise<void>;
43
- protected deleteHandler(hashes: string[]): Promise<string[]>;
45
+ protected deleteHandler(hashes: Hash[]): Promise<Hash[]>;
46
+ /**
47
+ * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)
48
+ * @param db The db instance to use
49
+ * @param storeName The name of the store to use
50
+ * @param indexName The index to use
51
+ * @param key The key to get from the index
52
+ * @returns The primary key and the payload, or undefined if not found
53
+ */
54
+ protected getFromIndexWithPrimaryKey(db: IDBPDatabase<PayloadStore>, storeName: string, indexName: string, key: IDBValidKey): Promise<[number, PayloadWithMeta] | undefined>;
44
55
  protected getHandler(hashes: string[]): Promise<PayloadWithMeta[]>;
45
56
  protected insertHandler(payloads: Payload[]): Promise<PayloadWithMeta[]>;
46
57
  protected startHandler(): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"Archivist.d.ts","sourceRoot":"","sources":["../../src/Archivist.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAKL,wBAAwB,EAGzB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAIrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AAEnD,MAAM,WAAW,YAAY;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAED,qBACa,kBAAkB,CAC7B,OAAO,SAAS,wBAAwB,GAAG,wBAAwB,EACnE,UAAU,SAAS,wBAAwB,GAAG,wBAAwB,CACtE,SAAQ,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC;IAC9C,OAAgB,aAAa,6CAAmC;IAChE,MAAM,CAAC,QAAQ,CAAC,aAAa,eAAc;IAC3C,MAAM,CAAC,QAAQ,CAAC,gBAAgB,KAAI;IACpC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,cAAa;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAC5G,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAA4E;IACjH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAA6E;IAEhH,MAAM,CAAC,QAAQ,CAAC,aAAa,SAAuD;IAEpF,MAAM,CAAC,QAAQ,CAAC,iBAAiB,SAA2D;IAE5F,MAAM,CAAC,QAAQ,CAAC,eAAe,SAAyD;IAExF;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED,IAAa,OAAO,aAEnB;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED;;OAEG;IACH,OAAO,KAAK,OAAO,GAElB;cAEwB,UAAU,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;cAOxC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;cAI7B,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;cA0BlD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cA4BxD,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAsC9D,YAAY;IAQrC;;;OAGG;YACW,gBAAgB;IAiD9B;;;;OAIG;YACW,KAAK;CAWpB"}
1
+ {"version":3,"file":"Archivist.d.ts","sourceRoot":"","sources":["../../src/Archivist.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAKL,wBAAwB,EAGzB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,YAAY,EAAU,MAAM,KAAK,CAAA;AAG1C,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AAEnD,MAAM,WAAW,YAAY;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAED,qBACa,kBAAkB,CAC7B,OAAO,SAAS,wBAAwB,GAAG,wBAAwB,EACnE,UAAU,SAAS,wBAAwB,GAAG,wBAAwB,CACtE,SAAQ,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC;IAC9C,OAAgB,aAAa,6CAAmC;IAChE,MAAM,CAAC,QAAQ,CAAC,aAAa,eAAc;IAC3C,MAAM,CAAC,QAAQ,CAAC,gBAAgB,KAAI;IACpC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,cAAa;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAC5G,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAA4E;IACjH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAA6E;IAEhH,MAAM,CAAC,QAAQ,CAAC,aAAa,SAAuD;IAEpF,MAAM,CAAC,QAAQ,CAAC,iBAAiB,SAA2D;IAE5F,MAAM,CAAC,QAAQ,CAAC,eAAe,SAAyD;IAExF;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED,IAAa,OAAO,aAEnB;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED;;OAEG;IACH,OAAO,KAAK,OAAO,GAElB;cAEwB,UAAU,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;cAOxC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;cAI7B,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IA0BvE;;;;;;;OAOG;cACa,0BAA0B,CACxC,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,CAAC;cAaxB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAmCxD,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAsC9D,YAAY;IAQrC;;;OAGG;YACW,gBAAgB;IAiD9B;;;;OAIG;YACW,KAAK;CAWpB"}
@@ -133,11 +133,33 @@ var _IndexedDbArchivist = class _IndexedDbArchivist extends import_archivist_abs
133
133
  return found.filter(import_exists.exists).filter((hash) => hashes.includes(hash));
134
134
  });
135
135
  }
136
+ /**
137
+ * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)
138
+ * @param db The db instance to use
139
+ * @param storeName The name of the store to use
140
+ * @param indexName The index to use
141
+ * @param key The key to get from the index
142
+ * @returns The primary key and the payload, or undefined if not found
143
+ */
144
+ async getFromIndexWithPrimaryKey(db, storeName, indexName, key) {
145
+ const transaction = db.transaction(storeName, "readonly");
146
+ const store = transaction.objectStore(storeName);
147
+ const index = store.index(indexName);
148
+ const cursor = await index.openCursor(key);
149
+ if (cursor) {
150
+ const singleValue = cursor.value;
151
+ const primaryKey = cursor.primaryKey;
152
+ return [
153
+ primaryKey,
154
+ singleValue
155
+ ];
156
+ }
157
+ }
136
158
  async getHandler(hashes) {
137
- const payloads = await this.useDb((db) => Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, _IndexedDbArchivist.hashIndexName, hash))));
138
- const payloadsFromDataHashes = await this.useDb((db) => Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, _IndexedDbArchivist.dataHashIndexName, hash))));
159
+ const payloads = await this.useDb((db) => Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, _IndexedDbArchivist.hashIndexName, hash))));
160
+ const payloadsFromDataHashes = await this.useDb((db) => Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, _IndexedDbArchivist.dataHashIndexName, hash))));
139
161
  const found = /* @__PURE__ */ new Set();
140
- const payloadsFromHash = payloads.filter(import_exists.exists).filter((payload) => {
162
+ const payloadsFromHash = payloads.filter(import_exists.exists).filter(([_key, payload]) => {
141
163
  if (found.has(payload.$hash)) {
142
164
  return false;
143
165
  } else {
@@ -145,7 +167,7 @@ var _IndexedDbArchivist = class _IndexedDbArchivist extends import_archivist_abs
145
167
  return true;
146
168
  }
147
169
  });
148
- const payloadsFromDataHash = payloadsFromDataHashes.filter(import_exists.exists).filter((payload) => {
170
+ const payloadsFromDataHash = payloadsFromDataHashes.filter(import_exists.exists).filter(([_key, payload]) => {
149
171
  if (found.has(payload.$hash)) {
150
172
  return false;
151
173
  } else {
@@ -153,10 +175,13 @@ var _IndexedDbArchivist = class _IndexedDbArchivist extends import_archivist_abs
153
175
  return true;
154
176
  }
155
177
  });
156
- return [
157
- ...payloadsFromHash,
158
- ...payloadsFromDataHash
159
- ];
178
+ return (
179
+ // Merge what we found from the hash and data hash indexes
180
+ [
181
+ ...payloadsFromHash,
182
+ ...payloadsFromDataHash
183
+ ].sort((a, b) => a[0] - b[0]).map(([_key, payload]) => payload)
184
+ );
160
185
  }
161
186
  async insertHandler(payloads) {
162
187
  const pairs = await import_payload_builder.PayloadBuilder.hashPairs(payloads);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.ts","../../src/Archivist.ts","../../src/Schema.ts","../../src/Config.ts"],"sourcesContent":["export * from './Archivist'\nexport * from './Config'\nexport * from './Params'\nexport * from './Schema'\n","import { exists } from '@xylabs/exists'\nimport { Hash } from '@xylabs/hex'\nimport { AbstractArchivist } from '@xyo-network/archivist-abstract'\nimport {\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ArchivistModuleEventData,\n buildStandardIndexName,\n IndexDescription,\n} from '@xyo-network/archivist-model'\nimport { creatableModule } from '@xyo-network/module-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, PayloadWithMeta } from '@xyo-network/payload-model'\nimport { IDBPDatabase, openDB } from 'idb'\n\nimport { IndexedDbArchivistConfigSchema } from './Config'\nimport { IndexedDbArchivistParams } from './Params'\n\nexport interface PayloadStore {\n [s: string]: Payload\n}\n\n@creatableModule()\nexport class IndexedDbArchivist<\n TParams extends IndexedDbArchivistParams = IndexedDbArchivistParams,\n TEventData extends ArchivistModuleEventData = ArchivistModuleEventData,\n> extends AbstractArchivist<TParams, TEventData> {\n static override configSchemas = [IndexedDbArchivistConfigSchema]\n static readonly defaultDbName = 'archivist'\n static readonly defaultDbVersion = 1\n static readonly defaultStoreName = 'payloads'\n private static readonly hashIndex: IndexDescription = { key: { _hash: 1 }, multiEntry: false, unique: true }\n private static readonly dataHashIndex: IndexDescription = { key: { $hash: 1 }, multiEntry: false, unique: false }\n private static readonly schemaIndex: IndexDescription = { key: { schema: 1 }, multiEntry: false, unique: false }\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly hashIndexName = buildStandardIndexName(IndexedDbArchivist.hashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly dataHashIndexName = buildStandardIndexName(IndexedDbArchivist.dataHashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly schemaIndexName = buildStandardIndexName(IndexedDbArchivist.schemaIndex)\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the module name (not guaranteed to be unique) and if module\n * name is not supplied, it defaults to `archivist`. This behavior\n * biases towards a single, isolated DB per archivist which seems to\n * make the most sense for 99% of use cases.\n */\n get dbName() {\n return this.config?.dbName ?? this.config?.name ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to 1.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\n }\n\n override get queries() {\n return [ArchivistAllQuerySchema, ArchivistClearQuerySchema, ArchivistDeleteQuerySchema, ArchivistInsertQuerySchema, ...super.queries]\n }\n\n /**\n * The name of the object store. If not supplied via config, it defaults\n * to `payloads`.\n */\n get storeName() {\n return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n /**\n * The indexes to create on the store\n */\n private get indexes() {\n return [IndexedDbArchivist.dataHashIndex, IndexedDbArchivist.hashIndex, IndexedDbArchivist.schemaIndex, ...(this.config?.storage?.indexes ?? [])]\n }\n\n protected override async allHandler(): Promise<PayloadWithMeta[]> {\n // Get all payloads from the store\n const payloads = await this.useDb((db) => db.getAll(this.storeName))\n // Remove any metadata before returning to the client\n return await Promise.all(payloads.map((payload) => PayloadBuilder.build(payload)))\n }\n\n protected override async clearHandler(): Promise<void> {\n await this.useDb((db) => db.clear(this.storeName))\n }\n\n protected override async deleteHandler(hashes: string[]): Promise<string[]> {\n const pairs = await PayloadBuilder.hashPairs(await this.getHandler(hashes))\n const hashesToDelete = pairs.flatMap<Hash>((pair) => [pair[0].$hash, pair[1]])\n // Remove any duplicates\n const distinctHashes = [...new Set(hashesToDelete)]\n return await this.useDb(async (db) => {\n // Only return hashes that were successfully deleted\n const found = await Promise.all(\n distinctHashes.map(async (hash) => {\n // Check if the hash exists\n const existing =\n (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash)) ??\n (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))\n // If it does exist\n if (existing) {\n // Delete it\n await db.delete(this.storeName, existing)\n // Return the hash so it gets added to the list of deleted hashes\n return hash\n }\n }),\n )\n return found.filter(exists).filter((hash) => hashes.includes(hash))\n })\n }\n\n protected override async getHandler(hashes: string[]): Promise<PayloadWithMeta[]> {\n const payloads = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash))),\n )\n const payloadsFromDataHashes = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))),\n )\n //filter out duplicates\n const found = new Set<string>()\n const payloadsFromHash = payloads.filter(exists).filter((payload) => {\n if (found.has(payload.$hash)) {\n return false\n } else {\n found.add(payload.$hash)\n return true\n }\n })\n const payloadsFromDataHash = payloadsFromDataHashes.filter(exists).filter((payload) => {\n if (found.has(payload.$hash)) {\n return false\n } else {\n found.add(payload.$hash)\n return true\n }\n })\n return [...payloadsFromHash, ...payloadsFromDataHash]\n }\n\n protected override async insertHandler(payloads: Payload[]): Promise<PayloadWithMeta[]> {\n const pairs = await PayloadBuilder.hashPairs(payloads)\n\n const db = await this.getInitializedDb()\n try {\n // Only return the payloads that were successfully inserted\n const inserted = await Promise.all(\n pairs.map(async ([payload, _hash]) => {\n // Perform each insert via a transaction to ensure it is atomic\n // with respect to checking for the pre-existence of the hash.\n // This is done to preserve iteration via insertion order.\n const tx = db.transaction(this.storeName, 'readwrite')\n try {\n // Get the object store\n const store = tx.objectStore(this.storeName)\n\n // Check if the hash already exists\n const existingTopHash = await store.index(IndexedDbArchivist.hashIndexName).get(_hash)\n // If it does not already exist\n if (!existingTopHash) {\n // Insert the payload\n await store.put({ ...payload, _hash })\n }\n\n // Return it so it gets added to the list of inserted payloads\n return payload\n } finally {\n // Close the transaction\n await tx.done\n }\n }),\n )\n return inserted.filter(exists)\n } finally {\n db.close()\n }\n }\n\n protected override async startHandler() {\n await super.startHandler()\n // NOTE: We could defer this creation to first access but we\n // want to fail fast here in case something is wrong\n await this.useDb(() => {})\n return true\n }\n\n /**\n * Returns that the desired DB/Store initialized to the correct version\n * @returns The initialized DB\n */\n private async getInitializedDb(): Promise<IDBPDatabase<PayloadStore>> {\n const { dbName, dbVersion, indexes, storeName } = this\n const db = await openDB<PayloadStore>(dbName, dbVersion, {\n blocked(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocked from upgrading from ${currentVersion} to ${blockedVersion}`, event)\n },\n blocking(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocking upgrade from ${currentVersion} to ${blockedVersion}`, event)\n },\n terminated() {\n console.log('IndexedDbArchivist: Terminated')\n },\n upgrade(database, oldVersion, newVersion, transaction) {\n // NOTE: This is called whenever the DB is created/updated. We could simply ensure the desired end\n // state but, out of an abundance of caution, we will just delete (so we know where we are starting\n // from a known good point) and recreate the desired state. This prioritizes resilience over data\n // retention but we can revisit that tradeoff when it becomes limiting. Because distributed browser\n // state is extremely hard to debug, this seems like fair tradeoff for now.\n if (oldVersion !== newVersion) {\n console.log(`IndexedDbArchivist: Upgrading from ${oldVersion} to ${newVersion}`)\n // Delete any existing databases that are not the current version\n const objectStores = transaction.objectStoreNames\n for (const name of objectStores) {\n try {\n database.deleteObjectStore(name)\n } catch {\n console.log(`IndexedDbArchivist: Failed to delete existing object store ${name}`)\n }\n }\n }\n // Create the store\n const store = database.createObjectStore(storeName, {\n // If it isn't explicitly set, create a value by auto incrementing.\n autoIncrement: true,\n })\n // Name the store\n store.name = storeName\n // Create an index on the hash\n for (const { key, multiEntry, unique } of indexes) {\n const indexKeys = Object.keys(key)\n const keys = indexKeys.length === 1 ? indexKeys[0] : indexKeys\n const indexName = buildStandardIndexName({ key, unique })\n store.createIndex(indexName, keys, { multiEntry, unique })\n }\n },\n })\n return db\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async useDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T> {\n // Get the initialized DB\n const db = await this.getInitializedDb()\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n}\n","export type IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\nexport const IndexedDbArchivistSchema: IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\n","import { ArchivistConfig, IndexDescription } from '@xyo-network/archivist-model'\n\nimport { IndexedDbArchivistSchema } from './Schema'\n\nexport type IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\nexport const IndexedDbArchivistConfigSchema: IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\n\nexport type IndexedDbArchivistConfig = ArchivistConfig<{\n /**\n * The database name\n */\n dbName?: string\n /**\n * The version of the DB, defaults to 1\n */\n dbVersion?: number\n schema: IndexedDbArchivistConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main archivist config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n"],"mappings":"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","names":["IndexedDbArchivistSchema","IndexedDbArchivistConfigSchema","IndexedDbArchivistSchema","IndexedDbArchivist","AbstractArchivist","dbName","config","name","defaultDbName","dbVersion","defaultDbVersion","queries","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","storeName","defaultStoreName","indexes","dataHashIndex","hashIndex","schemaIndex","storage","allHandler","payloads","useDb","db","getAll","Promise","all","map","payload","PayloadBuilder","build","clearHandler","clear","deleteHandler","hashes","pairs","hashPairs","getHandler","hashesToDelete","flatMap","pair","$hash","distinctHashes","Set","found","hash","existing","getKeyFromIndex","hashIndexName","dataHashIndexName","delete","filter","exists","includes","getFromIndex","payloadsFromDataHashes","payloadsFromHash","has","add","payloadsFromDataHash","insertHandler","getInitializedDb","inserted","_hash","tx","transaction","store","objectStore","existingTopHash","index","get","put","done","close","startHandler","openDB","blocked","currentVersion","blockedVersion","event","console","warn","blocking","terminated","log","upgrade","database","oldVersion","newVersion","objectStores","objectStoreNames","deleteObjectStore","createObjectStore","autoIncrement","key","multiEntry","unique","indexKeys","Object","keys","length","indexName","buildStandardIndexName","createIndex","callback","configSchemas","IndexedDbArchivistConfigSchema","schema","schemaIndexName","creatableModule"]}
1
+ {"version":3,"sources":["../../src/index.ts","../../src/Archivist.ts","../../src/Schema.ts","../../src/Config.ts"],"sourcesContent":["export * from './Archivist'\nexport * from './Config'\nexport * from './Params'\nexport * from './Schema'\n","import { exists } from '@xylabs/exists'\nimport { Hash } from '@xylabs/hex'\nimport { AbstractArchivist } from '@xyo-network/archivist-abstract'\nimport {\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ArchivistModuleEventData,\n buildStandardIndexName,\n IndexDescription,\n} from '@xyo-network/archivist-model'\nimport { creatableModule } from '@xyo-network/module-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, PayloadWithMeta } from '@xyo-network/payload-model'\nimport { IDBPDatabase, openDB } from 'idb'\n\nimport { IndexedDbArchivistConfigSchema } from './Config'\nimport { IndexedDbArchivistParams } from './Params'\n\nexport interface PayloadStore {\n [s: string]: Payload\n}\n\n@creatableModule()\nexport class IndexedDbArchivist<\n TParams extends IndexedDbArchivistParams = IndexedDbArchivistParams,\n TEventData extends ArchivistModuleEventData = ArchivistModuleEventData,\n> extends AbstractArchivist<TParams, TEventData> {\n static override configSchemas = [IndexedDbArchivistConfigSchema]\n static readonly defaultDbName = 'archivist'\n static readonly defaultDbVersion = 1\n static readonly defaultStoreName = 'payloads'\n private static readonly hashIndex: IndexDescription = { key: { _hash: 1 }, multiEntry: false, unique: true }\n private static readonly dataHashIndex: IndexDescription = { key: { $hash: 1 }, multiEntry: false, unique: false }\n private static readonly schemaIndex: IndexDescription = { key: { schema: 1 }, multiEntry: false, unique: false }\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly hashIndexName = buildStandardIndexName(IndexedDbArchivist.hashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly dataHashIndexName = buildStandardIndexName(IndexedDbArchivist.dataHashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly schemaIndexName = buildStandardIndexName(IndexedDbArchivist.schemaIndex)\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the module name (not guaranteed to be unique) and if module\n * name is not supplied, it defaults to `archivist`. This behavior\n * biases towards a single, isolated DB per archivist which seems to\n * make the most sense for 99% of use cases.\n */\n get dbName() {\n return this.config?.dbName ?? this.config?.name ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to 1.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\n }\n\n override get queries() {\n return [ArchivistAllQuerySchema, ArchivistClearQuerySchema, ArchivistDeleteQuerySchema, ArchivistInsertQuerySchema, ...super.queries]\n }\n\n /**\n * The name of the object store. If not supplied via config, it defaults\n * to `payloads`.\n */\n get storeName() {\n return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n /**\n * The indexes to create on the store\n */\n private get indexes() {\n return [IndexedDbArchivist.dataHashIndex, IndexedDbArchivist.hashIndex, IndexedDbArchivist.schemaIndex, ...(this.config?.storage?.indexes ?? [])]\n }\n\n protected override async allHandler(): Promise<PayloadWithMeta[]> {\n // Get all payloads from the store\n const payloads = await this.useDb((db) => db.getAll(this.storeName))\n // Remove any metadata before returning to the client\n return await Promise.all(payloads.map((payload) => PayloadBuilder.build(payload)))\n }\n\n protected override async clearHandler(): Promise<void> {\n await this.useDb((db) => db.clear(this.storeName))\n }\n\n protected override async deleteHandler(hashes: Hash[]): Promise<Hash[]> {\n const pairs = await PayloadBuilder.hashPairs(await this.getHandler(hashes))\n const hashesToDelete = pairs.flatMap<Hash>((pair) => [pair[0].$hash, pair[1]])\n // Remove any duplicates\n const distinctHashes = [...new Set(hashesToDelete)]\n return await this.useDb(async (db) => {\n // Only return hashes that were successfully deleted\n const found = await Promise.all(\n distinctHashes.map(async (hash) => {\n // Check if the hash exists\n const existing =\n (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash)) ??\n (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))\n // If it does exist\n if (existing) {\n // Delete it\n await db.delete(this.storeName, existing)\n // Return the hash so it gets added to the list of deleted hashes\n return hash\n }\n }),\n )\n return found.filter(exists).filter((hash) => hashes.includes(hash))\n })\n }\n\n /**\n * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)\n * @param db The db instance to use\n * @param storeName The name of the store to use\n * @param indexName The index to use\n * @param key The key to get from the index\n * @returns The primary key and the payload, or undefined if not found\n */\n protected async getFromIndexWithPrimaryKey(\n db: IDBPDatabase<PayloadStore>,\n storeName: string,\n indexName: string,\n key: IDBValidKey,\n ): Promise<[number, PayloadWithMeta] | undefined> {\n const transaction = db.transaction(storeName, 'readonly')\n const store = transaction.objectStore(storeName)\n const index = store.index(indexName)\n const cursor = await index.openCursor(key)\n if (cursor) {\n const singleValue = cursor.value\n // NOTE: It's known to be a number because we are using IndexedDB supplied auto-incrementing keys\n const primaryKey = cursor.primaryKey as number\n return [primaryKey, singleValue]\n }\n }\n\n protected override async getHandler(hashes: string[]): Promise<PayloadWithMeta[]> {\n const payloads = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.hashIndexName, hash))),\n )\n const payloadsFromDataHashes = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.dataHashIndexName, hash))),\n )\n //filter out duplicates\n const found = new Set<string>()\n const payloadsFromHash = payloads.filter(exists).filter(([_key, payload]) => {\n if (found.has(payload.$hash)) {\n return false\n } else {\n found.add(payload.$hash)\n return true\n }\n })\n const payloadsFromDataHash = payloadsFromDataHashes.filter(exists).filter(([_key, payload]) => {\n if (found.has(payload.$hash)) {\n return false\n } else {\n found.add(payload.$hash)\n return true\n }\n })\n return (\n // Merge what we found from the hash and data hash indexes\n [...payloadsFromHash, ...payloadsFromDataHash]\n // Sort in ascending order by primary key (for semi-predictable ordering in terms of insertion order)\n .sort((a, b) => a[0] - b[0])\n // Return just the payloads\n .map(([_key, payload]) => payload)\n )\n }\n\n protected override async insertHandler(payloads: Payload[]): Promise<PayloadWithMeta[]> {\n const pairs = await PayloadBuilder.hashPairs(payloads)\n\n const db = await this.getInitializedDb()\n try {\n // Only return the payloads that were successfully inserted\n const inserted = await Promise.all(\n pairs.map(async ([payload, _hash]) => {\n // Perform each insert via a transaction to ensure it is atomic\n // with respect to checking for the pre-existence of the hash.\n // This is done to preserve iteration via insertion order.\n const tx = db.transaction(this.storeName, 'readwrite')\n try {\n // Get the object store\n const store = tx.objectStore(this.storeName)\n\n // Check if the hash already exists\n const existingTopHash = await store.index(IndexedDbArchivist.hashIndexName).get(_hash)\n // If it does not already exist\n if (!existingTopHash) {\n // Insert the payload\n await store.put({ ...payload, _hash })\n }\n\n // Return it so it gets added to the list of inserted payloads\n return payload\n } finally {\n // Close the transaction\n await tx.done\n }\n }),\n )\n return inserted.filter(exists)\n } finally {\n db.close()\n }\n }\n\n protected override async startHandler() {\n await super.startHandler()\n // NOTE: We could defer this creation to first access but we\n // want to fail fast here in case something is wrong\n await this.useDb(() => {})\n return true\n }\n\n /**\n * Returns that the desired DB/Store initialized to the correct version\n * @returns The initialized DB\n */\n private async getInitializedDb(): Promise<IDBPDatabase<PayloadStore>> {\n const { dbName, dbVersion, indexes, storeName } = this\n const db = await openDB<PayloadStore>(dbName, dbVersion, {\n blocked(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocked from upgrading from ${currentVersion} to ${blockedVersion}`, event)\n },\n blocking(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocking upgrade from ${currentVersion} to ${blockedVersion}`, event)\n },\n terminated() {\n console.log('IndexedDbArchivist: Terminated')\n },\n upgrade(database, oldVersion, newVersion, transaction) {\n // NOTE: This is called whenever the DB is created/updated. We could simply ensure the desired end\n // state but, out of an abundance of caution, we will just delete (so we know where we are starting\n // from a known good point) and recreate the desired state. This prioritizes resilience over data\n // retention but we can revisit that tradeoff when it becomes limiting. Because distributed browser\n // state is extremely hard to debug, this seems like fair tradeoff for now.\n if (oldVersion !== newVersion) {\n console.log(`IndexedDbArchivist: Upgrading from ${oldVersion} to ${newVersion}`)\n // Delete any existing databases that are not the current version\n const objectStores = transaction.objectStoreNames\n for (const name of objectStores) {\n try {\n database.deleteObjectStore(name)\n } catch {\n console.log(`IndexedDbArchivist: Failed to delete existing object store ${name}`)\n }\n }\n }\n // Create the store\n const store = database.createObjectStore(storeName, {\n // If it isn't explicitly set, create a value by auto incrementing.\n autoIncrement: true,\n })\n // Name the store\n store.name = storeName\n // Create an index on the hash\n for (const { key, multiEntry, unique } of indexes) {\n const indexKeys = Object.keys(key)\n const keys = indexKeys.length === 1 ? indexKeys[0] : indexKeys\n const indexName = buildStandardIndexName({ key, unique })\n store.createIndex(indexName, keys, { multiEntry, unique })\n }\n },\n })\n return db\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async useDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T> {\n // Get the initialized DB\n const db = await this.getInitializedDb()\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n}\n","export type IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\nexport const IndexedDbArchivistSchema: IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\n","import { ArchivistConfig, IndexDescription } from '@xyo-network/archivist-model'\n\nimport { IndexedDbArchivistSchema } from './Schema'\n\nexport type IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\nexport const IndexedDbArchivistConfigSchema: IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\n\nexport type IndexedDbArchivistConfig = ArchivistConfig<{\n /**\n * The database name\n */\n dbName?: string\n /**\n * The version of the DB, defaults to 1\n */\n dbVersion?: number\n schema: IndexedDbArchivistConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main archivist config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n"],"mappings":"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","names":["IndexedDbArchivistSchema","IndexedDbArchivistConfigSchema","IndexedDbArchivistSchema","IndexedDbArchivist","AbstractArchivist","dbName","config","name","defaultDbName","dbVersion","defaultDbVersion","queries","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","storeName","defaultStoreName","indexes","dataHashIndex","hashIndex","schemaIndex","storage","allHandler","payloads","useDb","db","getAll","Promise","all","map","payload","PayloadBuilder","build","clearHandler","clear","deleteHandler","hashes","pairs","hashPairs","getHandler","hashesToDelete","flatMap","pair","$hash","distinctHashes","Set","found","hash","existing","getKeyFromIndex","hashIndexName","dataHashIndexName","delete","filter","exists","includes","getFromIndexWithPrimaryKey","indexName","key","transaction","store","objectStore","index","cursor","openCursor","singleValue","value","primaryKey","payloadsFromDataHashes","payloadsFromHash","_key","has","add","payloadsFromDataHash","sort","a","b","insertHandler","getInitializedDb","inserted","_hash","tx","existingTopHash","get","put","done","close","startHandler","openDB","blocked","currentVersion","blockedVersion","event","console","warn","blocking","terminated","log","upgrade","database","oldVersion","newVersion","objectStores","objectStoreNames","deleteObjectStore","createObjectStore","autoIncrement","multiEntry","unique","indexKeys","Object","keys","length","buildStandardIndexName","createIndex","callback","configSchemas","IndexedDbArchivistConfigSchema","schema","schemaIndexName","creatableModule"]}
@@ -107,11 +107,33 @@ var _IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
107
107
  return found.filter(exists).filter((hash) => hashes.includes(hash));
108
108
  });
109
109
  }
110
+ /**
111
+ * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)
112
+ * @param db The db instance to use
113
+ * @param storeName The name of the store to use
114
+ * @param indexName The index to use
115
+ * @param key The key to get from the index
116
+ * @returns The primary key and the payload, or undefined if not found
117
+ */
118
+ async getFromIndexWithPrimaryKey(db, storeName, indexName, key) {
119
+ const transaction = db.transaction(storeName, "readonly");
120
+ const store = transaction.objectStore(storeName);
121
+ const index = store.index(indexName);
122
+ const cursor = await index.openCursor(key);
123
+ if (cursor) {
124
+ const singleValue = cursor.value;
125
+ const primaryKey = cursor.primaryKey;
126
+ return [
127
+ primaryKey,
128
+ singleValue
129
+ ];
130
+ }
131
+ }
110
132
  async getHandler(hashes) {
111
- const payloads = await this.useDb((db) => Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, _IndexedDbArchivist.hashIndexName, hash))));
112
- const payloadsFromDataHashes = await this.useDb((db) => Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, _IndexedDbArchivist.dataHashIndexName, hash))));
133
+ const payloads = await this.useDb((db) => Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, _IndexedDbArchivist.hashIndexName, hash))));
134
+ const payloadsFromDataHashes = await this.useDb((db) => Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, _IndexedDbArchivist.dataHashIndexName, hash))));
113
135
  const found = /* @__PURE__ */ new Set();
114
- const payloadsFromHash = payloads.filter(exists).filter((payload) => {
136
+ const payloadsFromHash = payloads.filter(exists).filter(([_key, payload]) => {
115
137
  if (found.has(payload.$hash)) {
116
138
  return false;
117
139
  } else {
@@ -119,7 +141,7 @@ var _IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
119
141
  return true;
120
142
  }
121
143
  });
122
- const payloadsFromDataHash = payloadsFromDataHashes.filter(exists).filter((payload) => {
144
+ const payloadsFromDataHash = payloadsFromDataHashes.filter(exists).filter(([_key, payload]) => {
123
145
  if (found.has(payload.$hash)) {
124
146
  return false;
125
147
  } else {
@@ -127,10 +149,13 @@ var _IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
127
149
  return true;
128
150
  }
129
151
  });
130
- return [
131
- ...payloadsFromHash,
132
- ...payloadsFromDataHash
133
- ];
152
+ return (
153
+ // Merge what we found from the hash and data hash indexes
154
+ [
155
+ ...payloadsFromHash,
156
+ ...payloadsFromDataHash
157
+ ].sort((a, b) => a[0] - b[0]).map(([_key, payload]) => payload)
158
+ );
134
159
  }
135
160
  async insertHandler(payloads) {
136
161
  const pairs = await PayloadBuilder.hashPairs(payloads);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/Archivist.ts","../../src/Schema.ts","../../src/Config.ts"],"sourcesContent":["import { exists } from '@xylabs/exists'\nimport { Hash } from '@xylabs/hex'\nimport { AbstractArchivist } from '@xyo-network/archivist-abstract'\nimport {\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ArchivistModuleEventData,\n buildStandardIndexName,\n IndexDescription,\n} from '@xyo-network/archivist-model'\nimport { creatableModule } from '@xyo-network/module-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, PayloadWithMeta } from '@xyo-network/payload-model'\nimport { IDBPDatabase, openDB } from 'idb'\n\nimport { IndexedDbArchivistConfigSchema } from './Config'\nimport { IndexedDbArchivistParams } from './Params'\n\nexport interface PayloadStore {\n [s: string]: Payload\n}\n\n@creatableModule()\nexport class IndexedDbArchivist<\n TParams extends IndexedDbArchivistParams = IndexedDbArchivistParams,\n TEventData extends ArchivistModuleEventData = ArchivistModuleEventData,\n> extends AbstractArchivist<TParams, TEventData> {\n static override configSchemas = [IndexedDbArchivistConfigSchema]\n static readonly defaultDbName = 'archivist'\n static readonly defaultDbVersion = 1\n static readonly defaultStoreName = 'payloads'\n private static readonly hashIndex: IndexDescription = { key: { _hash: 1 }, multiEntry: false, unique: true }\n private static readonly dataHashIndex: IndexDescription = { key: { $hash: 1 }, multiEntry: false, unique: false }\n private static readonly schemaIndex: IndexDescription = { key: { schema: 1 }, multiEntry: false, unique: false }\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly hashIndexName = buildStandardIndexName(IndexedDbArchivist.hashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly dataHashIndexName = buildStandardIndexName(IndexedDbArchivist.dataHashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly schemaIndexName = buildStandardIndexName(IndexedDbArchivist.schemaIndex)\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the module name (not guaranteed to be unique) and if module\n * name is not supplied, it defaults to `archivist`. This behavior\n * biases towards a single, isolated DB per archivist which seems to\n * make the most sense for 99% of use cases.\n */\n get dbName() {\n return this.config?.dbName ?? this.config?.name ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to 1.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\n }\n\n override get queries() {\n return [ArchivistAllQuerySchema, ArchivistClearQuerySchema, ArchivistDeleteQuerySchema, ArchivistInsertQuerySchema, ...super.queries]\n }\n\n /**\n * The name of the object store. If not supplied via config, it defaults\n * to `payloads`.\n */\n get storeName() {\n return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n /**\n * The indexes to create on the store\n */\n private get indexes() {\n return [IndexedDbArchivist.dataHashIndex, IndexedDbArchivist.hashIndex, IndexedDbArchivist.schemaIndex, ...(this.config?.storage?.indexes ?? [])]\n }\n\n protected override async allHandler(): Promise<PayloadWithMeta[]> {\n // Get all payloads from the store\n const payloads = await this.useDb((db) => db.getAll(this.storeName))\n // Remove any metadata before returning to the client\n return await Promise.all(payloads.map((payload) => PayloadBuilder.build(payload)))\n }\n\n protected override async clearHandler(): Promise<void> {\n await this.useDb((db) => db.clear(this.storeName))\n }\n\n protected override async deleteHandler(hashes: string[]): Promise<string[]> {\n const pairs = await PayloadBuilder.hashPairs(await this.getHandler(hashes))\n const hashesToDelete = pairs.flatMap<Hash>((pair) => [pair[0].$hash, pair[1]])\n // Remove any duplicates\n const distinctHashes = [...new Set(hashesToDelete)]\n return await this.useDb(async (db) => {\n // Only return hashes that were successfully deleted\n const found = await Promise.all(\n distinctHashes.map(async (hash) => {\n // Check if the hash exists\n const existing =\n (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash)) ??\n (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))\n // If it does exist\n if (existing) {\n // Delete it\n await db.delete(this.storeName, existing)\n // Return the hash so it gets added to the list of deleted hashes\n return hash\n }\n }),\n )\n return found.filter(exists).filter((hash) => hashes.includes(hash))\n })\n }\n\n protected override async getHandler(hashes: string[]): Promise<PayloadWithMeta[]> {\n const payloads = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash))),\n )\n const payloadsFromDataHashes = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))),\n )\n //filter out duplicates\n const found = new Set<string>()\n const payloadsFromHash = payloads.filter(exists).filter((payload) => {\n if (found.has(payload.$hash)) {\n return false\n } else {\n found.add(payload.$hash)\n return true\n }\n })\n const payloadsFromDataHash = payloadsFromDataHashes.filter(exists).filter((payload) => {\n if (found.has(payload.$hash)) {\n return false\n } else {\n found.add(payload.$hash)\n return true\n }\n })\n return [...payloadsFromHash, ...payloadsFromDataHash]\n }\n\n protected override async insertHandler(payloads: Payload[]): Promise<PayloadWithMeta[]> {\n const pairs = await PayloadBuilder.hashPairs(payloads)\n\n const db = await this.getInitializedDb()\n try {\n // Only return the payloads that were successfully inserted\n const inserted = await Promise.all(\n pairs.map(async ([payload, _hash]) => {\n // Perform each insert via a transaction to ensure it is atomic\n // with respect to checking for the pre-existence of the hash.\n // This is done to preserve iteration via insertion order.\n const tx = db.transaction(this.storeName, 'readwrite')\n try {\n // Get the object store\n const store = tx.objectStore(this.storeName)\n\n // Check if the hash already exists\n const existingTopHash = await store.index(IndexedDbArchivist.hashIndexName).get(_hash)\n // If it does not already exist\n if (!existingTopHash) {\n // Insert the payload\n await store.put({ ...payload, _hash })\n }\n\n // Return it so it gets added to the list of inserted payloads\n return payload\n } finally {\n // Close the transaction\n await tx.done\n }\n }),\n )\n return inserted.filter(exists)\n } finally {\n db.close()\n }\n }\n\n protected override async startHandler() {\n await super.startHandler()\n // NOTE: We could defer this creation to first access but we\n // want to fail fast here in case something is wrong\n await this.useDb(() => {})\n return true\n }\n\n /**\n * Returns that the desired DB/Store initialized to the correct version\n * @returns The initialized DB\n */\n private async getInitializedDb(): Promise<IDBPDatabase<PayloadStore>> {\n const { dbName, dbVersion, indexes, storeName } = this\n const db = await openDB<PayloadStore>(dbName, dbVersion, {\n blocked(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocked from upgrading from ${currentVersion} to ${blockedVersion}`, event)\n },\n blocking(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocking upgrade from ${currentVersion} to ${blockedVersion}`, event)\n },\n terminated() {\n console.log('IndexedDbArchivist: Terminated')\n },\n upgrade(database, oldVersion, newVersion, transaction) {\n // NOTE: This is called whenever the DB is created/updated. We could simply ensure the desired end\n // state but, out of an abundance of caution, we will just delete (so we know where we are starting\n // from a known good point) and recreate the desired state. This prioritizes resilience over data\n // retention but we can revisit that tradeoff when it becomes limiting. Because distributed browser\n // state is extremely hard to debug, this seems like fair tradeoff for now.\n if (oldVersion !== newVersion) {\n console.log(`IndexedDbArchivist: Upgrading from ${oldVersion} to ${newVersion}`)\n // Delete any existing databases that are not the current version\n const objectStores = transaction.objectStoreNames\n for (const name of objectStores) {\n try {\n database.deleteObjectStore(name)\n } catch {\n console.log(`IndexedDbArchivist: Failed to delete existing object store ${name}`)\n }\n }\n }\n // Create the store\n const store = database.createObjectStore(storeName, {\n // If it isn't explicitly set, create a value by auto incrementing.\n autoIncrement: true,\n })\n // Name the store\n store.name = storeName\n // Create an index on the hash\n for (const { key, multiEntry, unique } of indexes) {\n const indexKeys = Object.keys(key)\n const keys = indexKeys.length === 1 ? indexKeys[0] : indexKeys\n const indexName = buildStandardIndexName({ key, unique })\n store.createIndex(indexName, keys, { multiEntry, unique })\n }\n },\n })\n return db\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async useDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T> {\n // Get the initialized DB\n const db = await this.getInitializedDb()\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n}\n","export type IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\nexport const IndexedDbArchivistSchema: IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\n","import { ArchivistConfig, IndexDescription } from '@xyo-network/archivist-model'\n\nimport { IndexedDbArchivistSchema } from './Schema'\n\nexport type IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\nexport const IndexedDbArchivistConfigSchema: IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\n\nexport type IndexedDbArchivistConfig = ArchivistConfig<{\n /**\n * The database name\n */\n dbName?: string\n /**\n * The version of the DB, defaults to 1\n */\n dbVersion?: number\n schema: IndexedDbArchivistConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main archivist config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n"],"mappings":";;;;;;;;;AAAA,SAASA,cAAc;AAEvB,SAASC,yBAAyB;AAClC,SACEC,yBACAC,2BACAC,4BACAC,4BAEAC,8BAEK;AACP,SAASC,uBAAuB;AAChC,SAASC,sBAAsB;AAE/B,SAAuBC,cAAc;;;ACd9B,IAAMC,2BAAqD;;;ACI3D,IAAMC,iCAAiE,GAAGC,wBAAAA;;;;;;;;;;;;;;AFoB1E,IAAMC,sBAAN,MAAMA,4BAGHC,kBAAAA;;;;;;;;EAsBR,IAAIC,SAAS;;AACX,aAAO,UAAKC,WAAL,mBAAaD,aAAU,UAAKC,WAAL,mBAAaC,SAAQJ,oBAAmBK;EACxE;;;;EAKA,IAAIC,YAAY;;AACd,aAAO,UAAKH,WAAL,mBAAaG,cAAaN,oBAAmBO;EACtD;EAEA,IAAaC,UAAU;AACrB,WAAO;MAACC;MAAyBC;MAA2BC;MAA4BC;SAA+B,MAAMJ;;EAC/H;;;;;EAMA,IAAIK,YAAY;;AACd,aAAO,UAAKV,WAAL,mBAAaU,cAAab,oBAAmBc;EACtD;;;;EAKA,IAAYC,UAAU;;AACpB,WAAO;MAACf,oBAAmBgB;MAAehB,oBAAmBiB;MAAWjB,oBAAmBkB;WAAiB,gBAAKf,WAAL,mBAAagB,YAAb,mBAAsBJ,YAAW,CAAA;;EAC/I;EAEA,MAAyBK,aAAyC;AAEhE,UAAMC,WAAW,MAAM,KAAKC,MAAM,CAACC,OAAOA,GAAGC,OAAO,KAAKX,SAAS,CAAA;AAElE,WAAO,MAAMY,QAAQC,IAAIL,SAASM,IAAI,CAACC,YAAYC,eAAeC,MAAMF,OAAAA,CAAAA,CAAAA;EAC1E;EAEA,MAAyBG,eAA8B;AACrD,UAAM,KAAKT,MAAM,CAACC,OAAOA,GAAGS,MAAM,KAAKnB,SAAS,CAAA;EAClD;EAEA,MAAyBoB,cAAcC,QAAqC;AAC1E,UAAMC,QAAQ,MAAMN,eAAeO,UAAU,MAAM,KAAKC,WAAWH,MAAAA,CAAAA;AACnE,UAAMI,iBAAiBH,MAAMI,QAAc,CAACC,SAAS;MAACA,KAAK,CAAA,EAAGC;MAAOD,KAAK,CAAA;KAAG;AAE7E,UAAME,iBAAiB;SAAI,IAAIC,IAAIL,cAAAA;;AACnC,WAAO,MAAM,KAAKhB,MAAM,OAAOC,OAAAA;AAE7B,YAAMqB,QAAQ,MAAMnB,QAAQC,IAC1BgB,eAAef,IAAI,OAAOkB,SAAAA;AAExB,cAAMC,WACH,MAAMvB,GAAGwB,gBAAgB,KAAKlC,WAAWb,oBAAmBgD,eAAeH,IAAAA,KAC3E,MAAMtB,GAAGwB,gBAAgB,KAAKlC,WAAWb,oBAAmBiD,mBAAmBJ,IAAAA;AAElF,YAAIC,UAAU;AAEZ,gBAAMvB,GAAG2B,OAAO,KAAKrC,WAAWiC,QAAAA;AAEhC,iBAAOD;QACT;MACF,CAAA,CAAA;AAEF,aAAOD,MAAMO,OAAOC,MAAAA,EAAQD,OAAO,CAACN,SAASX,OAAOmB,SAASR,IAAAA,CAAAA;IAC/D,CAAA;EACF;EAEA,MAAyBR,WAAWH,QAA8C;AAChF,UAAMb,WAAW,MAAM,KAAKC,MAAM,CAACC,OACjCE,QAAQC,IAAIQ,OAAOP,IAAI,CAACkB,SAAStB,GAAG+B,aAAa,KAAKzC,WAAWb,oBAAmBgD,eAAeH,IAAAA,CAAAA,CAAAA,CAAAA;AAErG,UAAMU,yBAAyB,MAAM,KAAKjC,MAAM,CAACC,OAC/CE,QAAQC,IAAIQ,OAAOP,IAAI,CAACkB,SAAStB,GAAG+B,aAAa,KAAKzC,WAAWb,oBAAmBiD,mBAAmBJ,IAAAA,CAAAA,CAAAA,CAAAA;AAGzG,UAAMD,QAAQ,oBAAID,IAAAA;AAClB,UAAMa,mBAAmBnC,SAAS8B,OAAOC,MAAAA,EAAQD,OAAO,CAACvB,YAAAA;AACvD,UAAIgB,MAAMa,IAAI7B,QAAQa,KAAK,GAAG;AAC5B,eAAO;MACT,OAAO;AACLG,cAAMc,IAAI9B,QAAQa,KAAK;AACvB,eAAO;MACT;IACF,CAAA;AACA,UAAMkB,uBAAuBJ,uBAAuBJ,OAAOC,MAAAA,EAAQD,OAAO,CAACvB,YAAAA;AACzE,UAAIgB,MAAMa,IAAI7B,QAAQa,KAAK,GAAG;AAC5B,eAAO;MACT,OAAO;AACLG,cAAMc,IAAI9B,QAAQa,KAAK;AACvB,eAAO;MACT;IACF,CAAA;AACA,WAAO;SAAIe;SAAqBG;;EAClC;EAEA,MAAyBC,cAAcvC,UAAiD;AACtF,UAAMc,QAAQ,MAAMN,eAAeO,UAAUf,QAAAA;AAE7C,UAAME,KAAK,MAAM,KAAKsC,iBAAgB;AACtC,QAAI;AAEF,YAAMC,WAAW,MAAMrC,QAAQC,IAC7BS,MAAMR,IAAI,OAAO,CAACC,SAASmC,KAAAA,MAAM;AAI/B,cAAMC,KAAKzC,GAAG0C,YAAY,KAAKpD,WAAW,WAAA;AAC1C,YAAI;AAEF,gBAAMqD,QAAQF,GAAGG,YAAY,KAAKtD,SAAS;AAG3C,gBAAMuD,kBAAkB,MAAMF,MAAMG,MAAMrE,oBAAmBgD,aAAa,EAAEsB,IAAIP,KAAAA;AAEhF,cAAI,CAACK,iBAAiB;AAEpB,kBAAMF,MAAMK,IAAI;cAAE,GAAG3C;cAASmC;YAAM,CAAA;UACtC;AAGA,iBAAOnC;QACT,UAAA;AAEE,gBAAMoC,GAAGQ;QACX;MACF,CAAA,CAAA;AAEF,aAAOV,SAASX,OAAOC,MAAAA;IACzB,UAAA;AACE7B,SAAGkD,MAAK;IACV;EACF;EAEA,MAAyBC,eAAe;AACtC,UAAM,MAAMA,aAAAA;AAGZ,UAAM,KAAKpD,MAAM,MAAA;IAAO,CAAA;AACxB,WAAO;EACT;;;;;EAMA,MAAcuC,mBAAwD;AACpE,UAAM,EAAE3D,QAAQI,WAAWS,SAASF,UAAS,IAAK;AAClD,UAAMU,KAAK,MAAMoD,OAAqBzE,QAAQI,WAAW;MACvDsE,QAAQC,gBAAgBC,gBAAgBC,OAAK;AAC3CC,gBAAQC,KAAK,mDAAmDJ,cAAAA,OAAqBC,cAAAA,IAAkBC,KAAAA;MACzG;MACAG,SAASL,gBAAgBC,gBAAgBC,OAAK;AAC5CC,gBAAQC,KAAK,6CAA6CJ,cAAAA,OAAqBC,cAAAA,IAAkBC,KAAAA;MACnG;MACAI,aAAAA;AACEH,gBAAQI,IAAI,gCAAA;MACd;MACAC,QAAQC,UAAUC,YAAYC,YAAYvB,aAAW;AAMnD,YAAIsB,eAAeC,YAAY;AAC7BR,kBAAQI,IAAI,sCAAsCG,UAAAA,OAAiBC,UAAAA,EAAY;AAE/E,gBAAMC,eAAexB,YAAYyB;AACjC,qBAAWtF,QAAQqF,cAAc;AAC/B,gBAAI;AACFH,uBAASK,kBAAkBvF,IAAAA;YAC7B,QAAQ;AACN4E,sBAAQI,IAAI,8DAA8DhF,IAAAA,EAAM;YAClF;UACF;QACF;AAEA,cAAM8D,QAAQoB,SAASM,kBAAkB/E,WAAW;;UAElDgF,eAAe;QACjB,CAAA;AAEA3B,cAAM9D,OAAOS;AAEb,mBAAW,EAAEiF,KAAKC,YAAYC,OAAM,KAAMjF,SAAS;AACjD,gBAAMkF,YAAYC,OAAOC,KAAKL,GAAAA;AAC9B,gBAAMK,OAAOF,UAAUG,WAAW,IAAIH,UAAU,CAAA,IAAKA;AACrD,gBAAMI,YAAYC,uBAAuB;YAAER;YAAKE;UAAO,CAAA;AACvD9B,gBAAMqC,YAAYF,WAAWF,MAAM;YAAEJ;YAAYC;UAAO,CAAA;QAC1D;MACF;IACF,CAAA;AACA,WAAOzE;EACT;;;;;;EAOA,MAAcD,MAASkF,UAA0E;AAE/F,UAAMjF,KAAK,MAAM,KAAKsC,iBAAgB;AACtC,QAAI;AAEF,aAAO,MAAM2C,SAASjF,EAAAA;IACxB,UAAA;AAEEA,SAAGkD,MAAK;IACV;EACF;AACF;AAxOUxE;AACR,cAJWD,qBAIKyG,iBAAgB;EAACC;;AACjC,cALW1G,qBAKKK,iBAAgB;AAChC,cANWL,qBAMKO,oBAAmB;AACnC,cAPWP,qBAOKc,oBAAmB;AACnC,cARWd,qBAQaiB,aAA8B;EAAE6E,KAAK;IAAE/B,OAAO;EAAE;EAAGgC,YAAY;EAAOC,QAAQ;AAAK;AAC3G,cATWhG,qBASagB,iBAAkC;EAAE8E,KAAK;IAAErD,OAAO;EAAE;EAAGsD,YAAY;EAAOC,QAAQ;AAAM;AAChH,cAVWhG,qBAUakB,eAAgC;EAAE4E,KAAK;IAAEa,QAAQ;EAAE;EAAGZ,YAAY;EAAOC,QAAQ;AAAM;;AAE/G,cAZWhG,qBAYKgD,iBAAgBsD,uBAAuBtG,oBAAmBiB,SAAS;;AAEnF,cAdWjB,qBAcKiD,qBAAoBqD,uBAAuBtG,oBAAmBgB,aAAa;;AAE3F,cAhBWhB,qBAgBK4G,mBAAkBN,uBAAuBtG,oBAAmBkB,WAAW;AAhBlF,IAAMlB,qBAAN;AAAMA,qBAAAA,aAAAA;EADZ6G,gBAAAA;GACY7G,kBAAAA;","names":["exists","AbstractArchivist","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","buildStandardIndexName","creatableModule","PayloadBuilder","openDB","IndexedDbArchivistSchema","IndexedDbArchivistConfigSchema","IndexedDbArchivistSchema","IndexedDbArchivist","AbstractArchivist","dbName","config","name","defaultDbName","dbVersion","defaultDbVersion","queries","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","storeName","defaultStoreName","indexes","dataHashIndex","hashIndex","schemaIndex","storage","allHandler","payloads","useDb","db","getAll","Promise","all","map","payload","PayloadBuilder","build","clearHandler","clear","deleteHandler","hashes","pairs","hashPairs","getHandler","hashesToDelete","flatMap","pair","$hash","distinctHashes","Set","found","hash","existing","getKeyFromIndex","hashIndexName","dataHashIndexName","delete","filter","exists","includes","getFromIndex","payloadsFromDataHashes","payloadsFromHash","has","add","payloadsFromDataHash","insertHandler","getInitializedDb","inserted","_hash","tx","transaction","store","objectStore","existingTopHash","index","get","put","done","close","startHandler","openDB","blocked","currentVersion","blockedVersion","event","console","warn","blocking","terminated","log","upgrade","database","oldVersion","newVersion","objectStores","objectStoreNames","deleteObjectStore","createObjectStore","autoIncrement","key","multiEntry","unique","indexKeys","Object","keys","length","indexName","buildStandardIndexName","createIndex","callback","configSchemas","IndexedDbArchivistConfigSchema","schema","schemaIndexName","creatableModule"]}
1
+ {"version":3,"sources":["../../src/Archivist.ts","../../src/Schema.ts","../../src/Config.ts"],"sourcesContent":["import { exists } from '@xylabs/exists'\nimport { Hash } from '@xylabs/hex'\nimport { AbstractArchivist } from '@xyo-network/archivist-abstract'\nimport {\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ArchivistModuleEventData,\n buildStandardIndexName,\n IndexDescription,\n} from '@xyo-network/archivist-model'\nimport { creatableModule } from '@xyo-network/module-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, PayloadWithMeta } from '@xyo-network/payload-model'\nimport { IDBPDatabase, openDB } from 'idb'\n\nimport { IndexedDbArchivistConfigSchema } from './Config'\nimport { IndexedDbArchivistParams } from './Params'\n\nexport interface PayloadStore {\n [s: string]: Payload\n}\n\n@creatableModule()\nexport class IndexedDbArchivist<\n TParams extends IndexedDbArchivistParams = IndexedDbArchivistParams,\n TEventData extends ArchivistModuleEventData = ArchivistModuleEventData,\n> extends AbstractArchivist<TParams, TEventData> {\n static override configSchemas = [IndexedDbArchivistConfigSchema]\n static readonly defaultDbName = 'archivist'\n static readonly defaultDbVersion = 1\n static readonly defaultStoreName = 'payloads'\n private static readonly hashIndex: IndexDescription = { key: { _hash: 1 }, multiEntry: false, unique: true }\n private static readonly dataHashIndex: IndexDescription = { key: { $hash: 1 }, multiEntry: false, unique: false }\n private static readonly schemaIndex: IndexDescription = { key: { schema: 1 }, multiEntry: false, unique: false }\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly hashIndexName = buildStandardIndexName(IndexedDbArchivist.hashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly dataHashIndexName = buildStandardIndexName(IndexedDbArchivist.dataHashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly schemaIndexName = buildStandardIndexName(IndexedDbArchivist.schemaIndex)\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the module name (not guaranteed to be unique) and if module\n * name is not supplied, it defaults to `archivist`. This behavior\n * biases towards a single, isolated DB per archivist which seems to\n * make the most sense for 99% of use cases.\n */\n get dbName() {\n return this.config?.dbName ?? this.config?.name ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to 1.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\n }\n\n override get queries() {\n return [ArchivistAllQuerySchema, ArchivistClearQuerySchema, ArchivistDeleteQuerySchema, ArchivistInsertQuerySchema, ...super.queries]\n }\n\n /**\n * The name of the object store. If not supplied via config, it defaults\n * to `payloads`.\n */\n get storeName() {\n return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n /**\n * The indexes to create on the store\n */\n private get indexes() {\n return [IndexedDbArchivist.dataHashIndex, IndexedDbArchivist.hashIndex, IndexedDbArchivist.schemaIndex, ...(this.config?.storage?.indexes ?? [])]\n }\n\n protected override async allHandler(): Promise<PayloadWithMeta[]> {\n // Get all payloads from the store\n const payloads = await this.useDb((db) => db.getAll(this.storeName))\n // Remove any metadata before returning to the client\n return await Promise.all(payloads.map((payload) => PayloadBuilder.build(payload)))\n }\n\n protected override async clearHandler(): Promise<void> {\n await this.useDb((db) => db.clear(this.storeName))\n }\n\n protected override async deleteHandler(hashes: Hash[]): Promise<Hash[]> {\n const pairs = await PayloadBuilder.hashPairs(await this.getHandler(hashes))\n const hashesToDelete = pairs.flatMap<Hash>((pair) => [pair[0].$hash, pair[1]])\n // Remove any duplicates\n const distinctHashes = [...new Set(hashesToDelete)]\n return await this.useDb(async (db) => {\n // Only return hashes that were successfully deleted\n const found = await Promise.all(\n distinctHashes.map(async (hash) => {\n // Check if the hash exists\n const existing =\n (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash)) ??\n (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))\n // If it does exist\n if (existing) {\n // Delete it\n await db.delete(this.storeName, existing)\n // Return the hash so it gets added to the list of deleted hashes\n return hash\n }\n }),\n )\n return found.filter(exists).filter((hash) => hashes.includes(hash))\n })\n }\n\n /**\n * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)\n * @param db The db instance to use\n * @param storeName The name of the store to use\n * @param indexName The index to use\n * @param key The key to get from the index\n * @returns The primary key and the payload, or undefined if not found\n */\n protected async getFromIndexWithPrimaryKey(\n db: IDBPDatabase<PayloadStore>,\n storeName: string,\n indexName: string,\n key: IDBValidKey,\n ): Promise<[number, PayloadWithMeta] | undefined> {\n const transaction = db.transaction(storeName, 'readonly')\n const store = transaction.objectStore(storeName)\n const index = store.index(indexName)\n const cursor = await index.openCursor(key)\n if (cursor) {\n const singleValue = cursor.value\n // NOTE: It's known to be a number because we are using IndexedDB supplied auto-incrementing keys\n const primaryKey = cursor.primaryKey as number\n return [primaryKey, singleValue]\n }\n }\n\n protected override async getHandler(hashes: string[]): Promise<PayloadWithMeta[]> {\n const payloads = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.hashIndexName, hash))),\n )\n const payloadsFromDataHashes = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.dataHashIndexName, hash))),\n )\n //filter out duplicates\n const found = new Set<string>()\n const payloadsFromHash = payloads.filter(exists).filter(([_key, payload]) => {\n if (found.has(payload.$hash)) {\n return false\n } else {\n found.add(payload.$hash)\n return true\n }\n })\n const payloadsFromDataHash = payloadsFromDataHashes.filter(exists).filter(([_key, payload]) => {\n if (found.has(payload.$hash)) {\n return false\n } else {\n found.add(payload.$hash)\n return true\n }\n })\n return (\n // Merge what we found from the hash and data hash indexes\n [...payloadsFromHash, ...payloadsFromDataHash]\n // Sort in ascending order by primary key (for semi-predictable ordering in terms of insertion order)\n .sort((a, b) => a[0] - b[0])\n // Return just the payloads\n .map(([_key, payload]) => payload)\n )\n }\n\n protected override async insertHandler(payloads: Payload[]): Promise<PayloadWithMeta[]> {\n const pairs = await PayloadBuilder.hashPairs(payloads)\n\n const db = await this.getInitializedDb()\n try {\n // Only return the payloads that were successfully inserted\n const inserted = await Promise.all(\n pairs.map(async ([payload, _hash]) => {\n // Perform each insert via a transaction to ensure it is atomic\n // with respect to checking for the pre-existence of the hash.\n // This is done to preserve iteration via insertion order.\n const tx = db.transaction(this.storeName, 'readwrite')\n try {\n // Get the object store\n const store = tx.objectStore(this.storeName)\n\n // Check if the hash already exists\n const existingTopHash = await store.index(IndexedDbArchivist.hashIndexName).get(_hash)\n // If it does not already exist\n if (!existingTopHash) {\n // Insert the payload\n await store.put({ ...payload, _hash })\n }\n\n // Return it so it gets added to the list of inserted payloads\n return payload\n } finally {\n // Close the transaction\n await tx.done\n }\n }),\n )\n return inserted.filter(exists)\n } finally {\n db.close()\n }\n }\n\n protected override async startHandler() {\n await super.startHandler()\n // NOTE: We could defer this creation to first access but we\n // want to fail fast here in case something is wrong\n await this.useDb(() => {})\n return true\n }\n\n /**\n * Returns that the desired DB/Store initialized to the correct version\n * @returns The initialized DB\n */\n private async getInitializedDb(): Promise<IDBPDatabase<PayloadStore>> {\n const { dbName, dbVersion, indexes, storeName } = this\n const db = await openDB<PayloadStore>(dbName, dbVersion, {\n blocked(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocked from upgrading from ${currentVersion} to ${blockedVersion}`, event)\n },\n blocking(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocking upgrade from ${currentVersion} to ${blockedVersion}`, event)\n },\n terminated() {\n console.log('IndexedDbArchivist: Terminated')\n },\n upgrade(database, oldVersion, newVersion, transaction) {\n // NOTE: This is called whenever the DB is created/updated. We could simply ensure the desired end\n // state but, out of an abundance of caution, we will just delete (so we know where we are starting\n // from a known good point) and recreate the desired state. This prioritizes resilience over data\n // retention but we can revisit that tradeoff when it becomes limiting. Because distributed browser\n // state is extremely hard to debug, this seems like fair tradeoff for now.\n if (oldVersion !== newVersion) {\n console.log(`IndexedDbArchivist: Upgrading from ${oldVersion} to ${newVersion}`)\n // Delete any existing databases that are not the current version\n const objectStores = transaction.objectStoreNames\n for (const name of objectStores) {\n try {\n database.deleteObjectStore(name)\n } catch {\n console.log(`IndexedDbArchivist: Failed to delete existing object store ${name}`)\n }\n }\n }\n // Create the store\n const store = database.createObjectStore(storeName, {\n // If it isn't explicitly set, create a value by auto incrementing.\n autoIncrement: true,\n })\n // Name the store\n store.name = storeName\n // Create an index on the hash\n for (const { key, multiEntry, unique } of indexes) {\n const indexKeys = Object.keys(key)\n const keys = indexKeys.length === 1 ? indexKeys[0] : indexKeys\n const indexName = buildStandardIndexName({ key, unique })\n store.createIndex(indexName, keys, { multiEntry, unique })\n }\n },\n })\n return db\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async useDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T> {\n // Get the initialized DB\n const db = await this.getInitializedDb()\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n}\n","export type IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\nexport const IndexedDbArchivistSchema: IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\n","import { ArchivistConfig, IndexDescription } from '@xyo-network/archivist-model'\n\nimport { IndexedDbArchivistSchema } from './Schema'\n\nexport type IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\nexport const IndexedDbArchivistConfigSchema: IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\n\nexport type IndexedDbArchivistConfig = ArchivistConfig<{\n /**\n * The database name\n */\n dbName?: string\n /**\n * The version of the DB, defaults to 1\n */\n dbVersion?: number\n schema: IndexedDbArchivistConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main archivist config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n"],"mappings":"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","names":["exists","AbstractArchivist","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","buildStandardIndexName","creatableModule","PayloadBuilder","openDB","IndexedDbArchivistSchema","IndexedDbArchivistConfigSchema","IndexedDbArchivistSchema","IndexedDbArchivist","AbstractArchivist","dbName","config","name","defaultDbName","dbVersion","defaultDbVersion","queries","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","storeName","defaultStoreName","indexes","dataHashIndex","hashIndex","schemaIndex","storage","allHandler","payloads","useDb","db","getAll","Promise","all","map","payload","PayloadBuilder","build","clearHandler","clear","deleteHandler","hashes","pairs","hashPairs","getHandler","hashesToDelete","flatMap","pair","$hash","distinctHashes","Set","found","hash","existing","getKeyFromIndex","hashIndexName","dataHashIndexName","delete","filter","exists","includes","getFromIndexWithPrimaryKey","indexName","key","transaction","store","objectStore","index","cursor","openCursor","singleValue","value","primaryKey","payloadsFromDataHashes","payloadsFromHash","_key","has","add","payloadsFromDataHash","sort","a","b","insertHandler","getInitializedDb","inserted","_hash","tx","existingTopHash","get","put","done","close","startHandler","openDB","blocked","currentVersion","blockedVersion","event","console","warn","blocking","terminated","log","upgrade","database","oldVersion","newVersion","objectStores","objectStoreNames","deleteObjectStore","createObjectStore","autoIncrement","multiEntry","unique","indexKeys","Object","keys","length","buildStandardIndexName","createIndex","callback","configSchemas","IndexedDbArchivistConfigSchema","schema","schemaIndexName","creatableModule"]}
package/package.json CHANGED
@@ -10,21 +10,21 @@
10
10
  "url": "https://github.com/XYOracleNetwork/sdk-xyo-client-js/issues"
11
11
  },
12
12
  "dependencies": {
13
- "@xylabs/exists": "^3.0.0",
14
- "@xylabs/hex": "^3.0.0",
15
- "@xyo-network/archivist-abstract": "~2.91.0",
16
- "@xyo-network/archivist-model": "~2.91.0",
17
- "@xyo-network/module-model": "~2.91.0",
18
- "@xyo-network/payload-builder": "~2.91.0",
19
- "@xyo-network/payload-model": "~2.91.0",
13
+ "@xylabs/exists": "^3.0.2",
14
+ "@xylabs/hex": "^3.0.2",
15
+ "@xyo-network/archivist-abstract": "~2.91.1",
16
+ "@xyo-network/archivist-model": "~2.91.1",
17
+ "@xyo-network/module-model": "~2.91.1",
18
+ "@xyo-network/payload-builder": "~2.91.1",
19
+ "@xyo-network/payload-model": "~2.91.1",
20
20
  "idb": "^8.0.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@xylabs/ts-scripts-yarn3": "^3.4.1",
24
24
  "@xylabs/tsconfig": "^3.4.1",
25
- "@xyo-network/account": "~2.91.0",
26
- "@xyo-network/id-payload-plugin": "~2.91.0",
27
- "@xyo-network/payload-wrapper": "~2.91.0",
25
+ "@xyo-network/account": "~2.91.1",
26
+ "@xyo-network/id-payload-plugin": "~2.91.1",
27
+ "@xyo-network/payload-wrapper": "~2.91.1",
28
28
  "fake-indexeddb": "^5.0.2",
29
29
  "typescript": "^5.3.3"
30
30
  },
@@ -67,6 +67,6 @@
67
67
  "url": "https://github.com/XYOracleNetwork/sdk-xyo-client-js.git"
68
68
  },
69
69
  "sideEffects": false,
70
- "version": "2.91.0",
70
+ "version": "2.91.1",
71
71
  "type": "module"
72
72
  }
package/src/Archivist.ts CHANGED
@@ -89,7 +89,7 @@ export class IndexedDbArchivist<
89
89
  await this.useDb((db) => db.clear(this.storeName))
90
90
  }
91
91
 
92
- protected override async deleteHandler(hashes: string[]): Promise<string[]> {
92
+ protected override async deleteHandler(hashes: Hash[]): Promise<Hash[]> {
93
93
  const pairs = await PayloadBuilder.hashPairs(await this.getHandler(hashes))
94
94
  const hashesToDelete = pairs.flatMap<Hash>((pair) => [pair[0].$hash, pair[1]])
95
95
  // Remove any duplicates
@@ -115,16 +115,42 @@ export class IndexedDbArchivist<
115
115
  })
116
116
  }
117
117
 
118
+ /**
119
+ * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)
120
+ * @param db The db instance to use
121
+ * @param storeName The name of the store to use
122
+ * @param indexName The index to use
123
+ * @param key The key to get from the index
124
+ * @returns The primary key and the payload, or undefined if not found
125
+ */
126
+ protected async getFromIndexWithPrimaryKey(
127
+ db: IDBPDatabase<PayloadStore>,
128
+ storeName: string,
129
+ indexName: string,
130
+ key: IDBValidKey,
131
+ ): Promise<[number, PayloadWithMeta] | undefined> {
132
+ const transaction = db.transaction(storeName, 'readonly')
133
+ const store = transaction.objectStore(storeName)
134
+ const index = store.index(indexName)
135
+ const cursor = await index.openCursor(key)
136
+ if (cursor) {
137
+ const singleValue = cursor.value
138
+ // NOTE: It's known to be a number because we are using IndexedDB supplied auto-incrementing keys
139
+ const primaryKey = cursor.primaryKey as number
140
+ return [primaryKey, singleValue]
141
+ }
142
+ }
143
+
118
144
  protected override async getHandler(hashes: string[]): Promise<PayloadWithMeta[]> {
119
145
  const payloads = await this.useDb((db) =>
120
- Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash))),
146
+ Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.hashIndexName, hash))),
121
147
  )
122
148
  const payloadsFromDataHashes = await this.useDb((db) =>
123
- Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))),
149
+ Promise.all(hashes.map((hash) => this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.dataHashIndexName, hash))),
124
150
  )
125
151
  //filter out duplicates
126
152
  const found = new Set<string>()
127
- const payloadsFromHash = payloads.filter(exists).filter((payload) => {
153
+ const payloadsFromHash = payloads.filter(exists).filter(([_key, payload]) => {
128
154
  if (found.has(payload.$hash)) {
129
155
  return false
130
156
  } else {
@@ -132,7 +158,7 @@ export class IndexedDbArchivist<
132
158
  return true
133
159
  }
134
160
  })
135
- const payloadsFromDataHash = payloadsFromDataHashes.filter(exists).filter((payload) => {
161
+ const payloadsFromDataHash = payloadsFromDataHashes.filter(exists).filter(([_key, payload]) => {
136
162
  if (found.has(payload.$hash)) {
137
163
  return false
138
164
  } else {
@@ -140,7 +166,14 @@ export class IndexedDbArchivist<
140
166
  return true
141
167
  }
142
168
  })
143
- return [...payloadsFromHash, ...payloadsFromDataHash]
169
+ return (
170
+ // Merge what we found from the hash and data hash indexes
171
+ [...payloadsFromHash, ...payloadsFromDataHash]
172
+ // Sort in ascending order by primary key (for semi-predictable ordering in terms of insertion order)
173
+ .sort((a, b) => a[0] - b[0])
174
+ // Return just the payloads
175
+ .map(([_key, payload]) => payload)
176
+ )
144
177
  }
145
178
 
146
179
  protected override async insertHandler(payloads: Payload[]): Promise<PayloadWithMeta[]> {