@xyo-network/diviner-payload-indexeddb 2.87.0-rc.2 → 2.87.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/Diviner.d.cts +7 -13
- package/dist/browser/Diviner.d.cts.map +1 -1
- package/dist/browser/Diviner.d.mts +7 -13
- package/dist/browser/Diviner.d.mts.map +1 -1
- package/dist/browser/Diviner.d.ts +7 -13
- package/dist/browser/Diviner.d.ts.map +1 -1
- package/dist/browser/index.cjs +16 -22
- package/dist/browser/index.cjs.map +1 -1
- package/dist/browser/index.js +16 -22
- package/dist/browser/index.js.map +1 -1
- package/dist/node/Diviner.d.cts +7 -13
- package/dist/node/Diviner.d.cts.map +1 -1
- package/dist/node/Diviner.d.mts +7 -13
- package/dist/node/Diviner.d.mts.map +1 -1
- package/dist/node/Diviner.d.ts +7 -13
- package/dist/node/Diviner.d.ts.map +1 -1
- package/dist/node/index.cjs +16 -23
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.js +16 -23
- package/dist/node/index.js.map +1 -1
- package/package.json +13 -13
- package/src/Diviner.ts +15 -22
|
@@ -5,25 +5,19 @@ import { Payload } from '@xyo-network/payload-model';
|
|
|
5
5
|
import { IndexedDbPayloadDivinerParams } from './Params';
|
|
6
6
|
export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams, TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload, TOut extends Payload = Payload, TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {
|
|
7
7
|
static configSchemas: string[];
|
|
8
|
-
static defaultDbName: string;
|
|
9
|
-
static defaultDbVersion: number;
|
|
10
|
-
static defaultStoreName: string;
|
|
11
8
|
private _db;
|
|
12
9
|
/**
|
|
13
|
-
* The database name. If not supplied via config it defaults
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* The database name. If not supplied via config, it defaults
|
|
11
|
+
* to the archivist's name and if archivist's name is not supplied,
|
|
12
|
+
* it defaults to `archivist`. This behavior
|
|
13
|
+
* biases towards a single, isolated DB per archivist which seems to
|
|
14
|
+
* make the most sense for 99% of use cases.
|
|
17
15
|
*/
|
|
18
16
|
get dbName(): string;
|
|
19
17
|
/**
|
|
20
|
-
* The database version. If not supplied via config, it defaults to
|
|
18
|
+
* The database version. If not supplied via config, it defaults to the archivist default version.
|
|
21
19
|
*/
|
|
22
20
|
get dbVersion(): number;
|
|
23
|
-
/**
|
|
24
|
-
* The database indexes.
|
|
25
|
-
*/
|
|
26
|
-
get indexes(): import("@xyo-network/archivist-model").IndexDescription[];
|
|
27
21
|
/**
|
|
28
22
|
* The name of the object store. If not supplied via config, it defaults
|
|
29
23
|
* to `payloads`.
|
|
@@ -32,7 +26,7 @@ export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDiv
|
|
|
32
26
|
private get db();
|
|
33
27
|
protected divineHandler(payloads?: TIn[]): Promise<TOut[]>;
|
|
34
28
|
protected startHandler(): Promise<boolean>;
|
|
35
|
-
private
|
|
29
|
+
private getKeyRangeValue;
|
|
36
30
|
private selectBestIndex;
|
|
37
31
|
}
|
|
38
32
|
//# sourceMappingURL=Diviner.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAMxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED,OAAO,KAAK,EAAE,GAEb;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAqChD,YAAY;IAQrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;CA2BxB"}
|
|
@@ -5,25 +5,19 @@ import { Payload } from '@xyo-network/payload-model';
|
|
|
5
5
|
import { IndexedDbPayloadDivinerParams } from './Params';
|
|
6
6
|
export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams, TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload, TOut extends Payload = Payload, TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {
|
|
7
7
|
static configSchemas: string[];
|
|
8
|
-
static defaultDbName: string;
|
|
9
|
-
static defaultDbVersion: number;
|
|
10
|
-
static defaultStoreName: string;
|
|
11
8
|
private _db;
|
|
12
9
|
/**
|
|
13
|
-
* The database name. If not supplied via config it defaults
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* The database name. If not supplied via config, it defaults
|
|
11
|
+
* to the archivist's name and if archivist's name is not supplied,
|
|
12
|
+
* it defaults to `archivist`. This behavior
|
|
13
|
+
* biases towards a single, isolated DB per archivist which seems to
|
|
14
|
+
* make the most sense for 99% of use cases.
|
|
17
15
|
*/
|
|
18
16
|
get dbName(): string;
|
|
19
17
|
/**
|
|
20
|
-
* The database version. If not supplied via config, it defaults to
|
|
18
|
+
* The database version. If not supplied via config, it defaults to the archivist default version.
|
|
21
19
|
*/
|
|
22
20
|
get dbVersion(): number;
|
|
23
|
-
/**
|
|
24
|
-
* The database indexes.
|
|
25
|
-
*/
|
|
26
|
-
get indexes(): import("@xyo-network/archivist-model").IndexDescription[];
|
|
27
21
|
/**
|
|
28
22
|
* The name of the object store. If not supplied via config, it defaults
|
|
29
23
|
* to `payloads`.
|
|
@@ -32,7 +26,7 @@ export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDiv
|
|
|
32
26
|
private get db();
|
|
33
27
|
protected divineHandler(payloads?: TIn[]): Promise<TOut[]>;
|
|
34
28
|
protected startHandler(): Promise<boolean>;
|
|
35
|
-
private
|
|
29
|
+
private getKeyRangeValue;
|
|
36
30
|
private selectBestIndex;
|
|
37
31
|
}
|
|
38
32
|
//# sourceMappingURL=Diviner.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAMxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED,OAAO,KAAK,EAAE,GAEb;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAqChD,YAAY;IAQrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;CA2BxB"}
|
|
@@ -5,25 +5,19 @@ import { Payload } from '@xyo-network/payload-model';
|
|
|
5
5
|
import { IndexedDbPayloadDivinerParams } from './Params';
|
|
6
6
|
export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams, TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload, TOut extends Payload = Payload, TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {
|
|
7
7
|
static configSchemas: string[];
|
|
8
|
-
static defaultDbName: string;
|
|
9
|
-
static defaultDbVersion: number;
|
|
10
|
-
static defaultStoreName: string;
|
|
11
8
|
private _db;
|
|
12
9
|
/**
|
|
13
|
-
* The database name. If not supplied via config it defaults
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* The database name. If not supplied via config, it defaults
|
|
11
|
+
* to the archivist's name and if archivist's name is not supplied,
|
|
12
|
+
* it defaults to `archivist`. This behavior
|
|
13
|
+
* biases towards a single, isolated DB per archivist which seems to
|
|
14
|
+
* make the most sense for 99% of use cases.
|
|
17
15
|
*/
|
|
18
16
|
get dbName(): string;
|
|
19
17
|
/**
|
|
20
|
-
* The database version. If not supplied via config, it defaults to
|
|
18
|
+
* The database version. If not supplied via config, it defaults to the archivist default version.
|
|
21
19
|
*/
|
|
22
20
|
get dbVersion(): number;
|
|
23
|
-
/**
|
|
24
|
-
* The database indexes.
|
|
25
|
-
*/
|
|
26
|
-
get indexes(): import("@xyo-network/archivist-model").IndexDescription[];
|
|
27
21
|
/**
|
|
28
22
|
* The name of the object store. If not supplied via config, it defaults
|
|
29
23
|
* to `payloads`.
|
|
@@ -32,7 +26,7 @@ export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDiv
|
|
|
32
26
|
private get db();
|
|
33
27
|
protected divineHandler(payloads?: TIn[]): Promise<TOut[]>;
|
|
34
28
|
protected startHandler(): Promise<boolean>;
|
|
35
|
-
private
|
|
29
|
+
private getKeyRangeValue;
|
|
36
30
|
private selectBestIndex;
|
|
37
31
|
}
|
|
38
32
|
//# sourceMappingURL=Diviner.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAMxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED,OAAO,KAAK,EAAE,GAEb;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAqChD,YAAY;IAQrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;CA2BxB"}
|
package/dist/browser/index.cjs
CHANGED
|
@@ -36,49 +36,42 @@ var IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.conf
|
|
|
36
36
|
|
|
37
37
|
// src/Diviner.ts
|
|
38
38
|
var import_assert = require("@xylabs/assert");
|
|
39
|
+
var import_archivist_indexeddb = require("@xyo-network/archivist-indexeddb");
|
|
39
40
|
var import_archivist_model = require("@xyo-network/archivist-model");
|
|
40
41
|
var import_diviner_payload_abstract = require("@xyo-network/diviner-payload-abstract");
|
|
41
42
|
var import_diviner_payload_model2 = require("@xyo-network/diviner-payload-model");
|
|
42
43
|
var import_hash = require("@xyo-network/hash");
|
|
43
44
|
var import_idb = require("idb");
|
|
44
|
-
var IndexedDbPayloadDiviner = class
|
|
45
|
+
var IndexedDbPayloadDiviner = class extends import_diviner_payload_abstract.PayloadDiviner {
|
|
45
46
|
static {
|
|
46
47
|
__name(this, "IndexedDbPayloadDiviner");
|
|
47
48
|
}
|
|
48
49
|
static configSchemas = [
|
|
49
50
|
IndexedDbPayloadDivinerConfigSchema
|
|
50
51
|
];
|
|
51
|
-
static defaultDbName = "archivist";
|
|
52
|
-
static defaultDbVersion = 1;
|
|
53
|
-
static defaultStoreName = "payloads";
|
|
54
52
|
_db;
|
|
55
53
|
/**
|
|
56
|
-
* The database name. If not supplied via config it defaults
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
54
|
+
* The database name. If not supplied via config, it defaults
|
|
55
|
+
* to the archivist's name and if archivist's name is not supplied,
|
|
56
|
+
* it defaults to `archivist`. This behavior
|
|
57
|
+
* biases towards a single, isolated DB per archivist which seems to
|
|
58
|
+
* make the most sense for 99% of use cases.
|
|
60
59
|
*/
|
|
61
60
|
get dbName() {
|
|
62
|
-
return this.config?.dbName ??
|
|
61
|
+
return this.config?.dbName ?? this.config?.archivist ?? import_archivist_indexeddb.IndexedDbArchivist.defaultDbName;
|
|
63
62
|
}
|
|
64
63
|
/**
|
|
65
|
-
* The database version. If not supplied via config, it defaults to
|
|
64
|
+
* The database version. If not supplied via config, it defaults to the archivist default version.
|
|
66
65
|
*/
|
|
67
66
|
get dbVersion() {
|
|
68
|
-
return this.config?.dbVersion ??
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* The database indexes.
|
|
72
|
-
*/
|
|
73
|
-
get indexes() {
|
|
74
|
-
return this.config?.storage?.indexes ?? [];
|
|
67
|
+
return this.config?.dbVersion ?? import_archivist_indexeddb.IndexedDbArchivist.defaultDbVersion;
|
|
75
68
|
}
|
|
76
69
|
/**
|
|
77
70
|
* The name of the object store. If not supplied via config, it defaults
|
|
78
71
|
* to `payloads`.
|
|
79
72
|
*/
|
|
80
73
|
get storeName() {
|
|
81
|
-
return this.config?.storeName ??
|
|
74
|
+
return this.config?.storeName ?? import_archivist_indexeddb.IndexedDbArchivist.defaultStoreName;
|
|
82
75
|
}
|
|
83
76
|
get db() {
|
|
84
77
|
return (0, import_assert.assertEx)(this._db, "DB not initialized");
|
|
@@ -103,8 +96,8 @@ var IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends import_divi
|
|
|
103
96
|
};
|
|
104
97
|
const direction = order === "desc" ? "prev" : "next";
|
|
105
98
|
const suggestedIndex = this.selectBestIndex(filter, store);
|
|
106
|
-
const
|
|
107
|
-
let cursor = suggestedIndex ? await store.index(suggestedIndex).openCursor(IDBKeyRange.only(
|
|
99
|
+
const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter);
|
|
100
|
+
let cursor = suggestedIndex ? await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction) : await store.openCursor(suggestedIndex, direction);
|
|
108
101
|
while (cursor && parsedOffset > 0) {
|
|
109
102
|
cursor = await cursor.advance(parsedOffset);
|
|
110
103
|
parsedOffset = 0;
|
|
@@ -121,14 +114,15 @@ var IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends import_divi
|
|
|
121
114
|
this._db = await (0, import_idb.openDB)(this.dbName, this.dbVersion);
|
|
122
115
|
return true;
|
|
123
116
|
}
|
|
124
|
-
|
|
117
|
+
getKeyRangeValue(indexName, query) {
|
|
125
118
|
if (!indexName)
|
|
126
119
|
return [];
|
|
127
120
|
const extractFields = /* @__PURE__ */ __name((indexName2) => {
|
|
128
121
|
return indexName2.slice(3).split(import_archivist_model.IndexSeparator).map((field) => field.toLowerCase());
|
|
129
122
|
}, "extractFields");
|
|
130
123
|
const indexFields = extractFields(indexName);
|
|
131
|
-
|
|
124
|
+
const keyRangeValue = indexFields.map((field) => query[field]);
|
|
125
|
+
return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue;
|
|
132
126
|
}
|
|
133
127
|
selectBestIndex(query, store) {
|
|
134
128
|
const { indexNames } = store;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/index.ts","../../src/Schema.ts","../../src/Config.ts","../../src/Diviner.ts"],"sourcesContent":["export * from './Config'\nexport * from './Diviner'\nexport * from './Params'\nexport * from './Schema'\n","import { PayloadDivinerSchema } from '@xyo-network/diviner-payload-model'\n\nexport const IndexedDbPayloadDivinerSchema = `${PayloadDivinerSchema}.indexeddb`\nexport type IndexedDbPayloadDivinerSchema = typeof IndexedDbPayloadDivinerSchema\n","import { IndexDescription } from '@xyo-network/archivist-model'\nimport { DivinerConfig } from '@xyo-network/diviner-model'\n\nimport { IndexedDbPayloadDivinerSchema } from './Schema'\n\nexport const IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.config`\nexport type IndexedDbPayloadDivinerConfigSchema = typeof IndexedDbPayloadDivinerConfigSchema\n\nexport type IndexedDbPayloadDivinerConfig = DivinerConfig<{\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: IndexedDbPayloadDivinerConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main diviner 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","import { assertEx } from '@xylabs/assert'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { AnyObject } from '@xyo-network/object'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, IDBPObjectStore, openDB } from 'idb'\n\nimport { IndexedDbPayloadDivinerConfigSchema } from './Config'\nimport { IndexedDbPayloadDivinerParams } from './Params'\n\ninterface PayloadStore {\n [s: string]: Payload\n}\n\nexport class IndexedDbPayloadDiviner<\n TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams,\n TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload,\n TOut extends Payload = Payload,\n TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]\n static defaultDbName = 'archivist'\n static defaultDbVersion = 1\n static defaultStoreName = 'payloads'\n\n private _db: IDBPDatabase<PayloadStore> | undefined\n\n /**\n * The database name. If not supplied via config it defaults to\n * `archivist`. This behavior biases towards a single, isolated\n * DB per archivist which seems to make the most sense for 99% of\n * use cases.\n */\n get dbName() {\n return this.config?.dbName ?? IndexedDbPayloadDiviner.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 ?? IndexedDbPayloadDiviner.defaultDbVersion\n }\n\n /**\n * The database indexes.\n */\n get indexes() {\n return this.config?.storage?.indexes ?? []\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 ?? IndexedDbPayloadDiviner.defaultStoreName\n }\n\n private get db(): IDBPDatabase<PayloadStore> {\n return assertEx(this._db, 'DB not initialized')\n }\n\n protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {\n const query = assertEx(payloads?.filter(isPayloadDivinerQueryPayload)?.pop(), 'Missing query payload')\n if (!query) return []\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }\n const tx = this.db.transaction(this.storeName, 'readonly')\n const store = tx.objectStore(this.storeName)\n const results: TOut[] = []\n let parsedOffset = offset ?? 0\n const parsedLimit = limit ?? 10\n assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')\n const filterSchema = schemas?.[0]\n const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }\n const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'\n const suggestedIndex = this.selectBestIndex(filter, store)\n const filterValues = this.getKeyValuesFromQuery(suggestedIndex, filter)\n let cursor = suggestedIndex\n ? // Conditionally filter on schemas\n await store.index(suggestedIndex).openCursor(IDBKeyRange.only(filterValues.length === 1 ? filterValues[0] : filterValues), direction)\n : // Just iterate all records\n await store.openCursor(suggestedIndex, direction)\n\n // Skip records until the offset is reached\n while (cursor && parsedOffset > 0) {\n cursor = await cursor.advance(parsedOffset)\n parsedOffset = 0 // Reset offset after skipping\n }\n // Collect results up to the limit\n while (cursor && results.length < parsedLimit) {\n results.push(cursor.value)\n cursor = await cursor.continue()\n }\n await tx.done\n // Remove any metadata before returning to the client\n return results.map((payload) => PayloadHasher.jsonPayload(payload))\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 this._db = await openDB<PayloadStore>(this.dbName, this.dbVersion)\n return true\n }\n\n private getKeyValuesFromQuery(indexName: string | null, query: AnyObject): unknown[] {\n if (!indexName) return []\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Extracting the relevant fields from the index name\n const indexFields = extractFields(indexName)\n\n // Collecting the values for these fields from the query object\n return indexFields.map((field) => query[field as keyof AnyObject])\n }\n\n private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {\n // List of available indexes\n const { indexNames } = store\n\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Convert query object keys to a set for easier comparison\n const queryKeys = new Set(Object.keys(query).map((key) => key.toLowerCase()))\n\n // Find the best matching index\n let bestMatch: { indexName: string; matchCount: number } = { indexName: '', matchCount: 0 }\n\n for (const indexName of indexNames) {\n const indexFields = extractFields(indexName)\n const matchCount = indexFields.filter((field) => queryKeys.has(field)).length\n if (matchCount > bestMatch.matchCount) {\n bestMatch = { indexName, matchCount }\n }\n }\n return bestMatch.matchCount > 0 ? bestMatch.indexName : null\n }\n}\n"],"mappings":"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","names":["IndexedDbPayloadDivinerSchema","PayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","import_diviner_payload_model","IndexedDbPayloadDiviner","PayloadDiviner","configSchemas","IndexedDbPayloadDivinerConfigSchema","defaultDbName","defaultDbVersion","defaultStoreName","_db","dbName","config","dbVersion","indexes","storage","storeName","db","assertEx","divineHandler","payloads","query","filter","isPayloadDivinerQueryPayload","pop","schemas","limit","offset","hash","order","schema","_schema","sources","props","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","length","filterSchema","direction","suggestedIndex","selectBestIndex","filterValues","getKeyValuesFromQuery","cursor","index","openCursor","IDBKeyRange","only","advance","push","value","continue","done","map","payload","PayloadHasher","jsonPayload","startHandler","openDB","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","Object","keys","key","bestMatch","matchCount","has"]}
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts","../../src/Schema.ts","../../src/Config.ts","../../src/Diviner.ts"],"sourcesContent":["export * from './Config'\nexport * from './Diviner'\nexport * from './Params'\nexport * from './Schema'\n","import { PayloadDivinerSchema } from '@xyo-network/diviner-payload-model'\n\nexport const IndexedDbPayloadDivinerSchema = `${PayloadDivinerSchema}.indexeddb`\nexport type IndexedDbPayloadDivinerSchema = typeof IndexedDbPayloadDivinerSchema\n","import { IndexDescription } from '@xyo-network/archivist-model'\nimport { DivinerConfig } from '@xyo-network/diviner-model'\n\nimport { IndexedDbPayloadDivinerSchema } from './Schema'\n\nexport const IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.config`\nexport type IndexedDbPayloadDivinerConfigSchema = typeof IndexedDbPayloadDivinerConfigSchema\n\nexport type IndexedDbPayloadDivinerConfig = DivinerConfig<{\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: IndexedDbPayloadDivinerConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main diviner 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","import { assertEx } from '@xylabs/assert'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { AnyObject } from '@xyo-network/object'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, IDBPObjectStore, openDB } from 'idb'\n\nimport { IndexedDbPayloadDivinerConfigSchema } from './Config'\nimport { IndexedDbPayloadDivinerParams } from './Params'\n\ninterface PayloadStore {\n [s: string]: Payload\n}\n\nexport class IndexedDbPayloadDiviner<\n TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams,\n TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload,\n TOut extends Payload = Payload,\n TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]\n\n private _db: IDBPDatabase<PayloadStore> | undefined\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the archivist's name and if archivist's name is not supplied,\n * 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?.archivist ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to the archivist default version.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\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 private get db(): IDBPDatabase<PayloadStore> {\n return assertEx(this._db, 'DB not initialized')\n }\n\n protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {\n const query = assertEx(payloads?.filter(isPayloadDivinerQueryPayload)?.pop(), 'Missing query payload')\n if (!query) return []\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }\n const tx = this.db.transaction(this.storeName, 'readonly')\n const store = tx.objectStore(this.storeName)\n const results: TOut[] = []\n let parsedOffset = offset ?? 0\n const parsedLimit = limit ?? 10\n assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')\n const filterSchema = schemas?.[0]\n const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }\n const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'\n const suggestedIndex = this.selectBestIndex(filter, store)\n const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter)\n let cursor = suggestedIndex\n ? // Conditionally filter on schemas\n await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction)\n : // Just iterate all records\n await store.openCursor(suggestedIndex, direction)\n\n // Skip records until the offset is reached\n while (cursor && parsedOffset > 0) {\n cursor = await cursor.advance(parsedOffset)\n parsedOffset = 0 // Reset offset after skipping\n }\n // Collect results up to the limit\n while (cursor && results.length < parsedLimit) {\n results.push(cursor.value)\n cursor = await cursor.continue()\n }\n await tx.done\n // Remove any metadata before returning to the client\n return results.map((payload) => PayloadHasher.jsonPayload(payload))\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 this._db = await openDB<PayloadStore>(this.dbName, this.dbVersion)\n return true\n }\n\n private getKeyRangeValue(indexName: string | null, query: AnyObject): unknown | unknown[] {\n if (!indexName) return []\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Extracting the relevant fields from the index name\n const indexFields = extractFields(indexName)\n\n // Collecting the values for these fields from the query object\n const keyRangeValue = indexFields.map((field) => query[field as keyof AnyObject])\n return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue\n }\n\n private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {\n // List of available indexes\n const { indexNames } = store\n\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Convert query object keys to a set for easier comparison\n const queryKeys = new Set(Object.keys(query).map((key) => key.toLowerCase()))\n\n // Find the best matching index\n let bestMatch: { indexName: string; matchCount: number } = { indexName: '', matchCount: 0 }\n\n for (const indexName of indexNames) {\n const indexFields = extractFields(indexName)\n const matchCount = indexFields.filter((field) => queryKeys.has(field)).length\n if (matchCount > bestMatch.matchCount) {\n bestMatch = { indexName, matchCount }\n }\n }\n return bestMatch.matchCount > 0 ? bestMatch.indexName : null\n }\n}\n"],"mappings":"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","names":["IndexedDbPayloadDivinerSchema","PayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","import_diviner_payload_model","IndexedDbPayloadDiviner","PayloadDiviner","configSchemas","IndexedDbPayloadDivinerConfigSchema","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","db","assertEx","divineHandler","payloads","query","filter","isPayloadDivinerQueryPayload","pop","schemas","limit","offset","hash","order","schema","_schema","sources","props","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","length","filterSchema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","cursor","index","openCursor","IDBKeyRange","only","advance","push","value","continue","done","map","payload","PayloadHasher","jsonPayload","startHandler","openDB","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","Object","keys","key","bestMatch","matchCount","has"]}
|
package/dist/browser/index.js
CHANGED
|
@@ -10,49 +10,42 @@ var IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.conf
|
|
|
10
10
|
|
|
11
11
|
// src/Diviner.ts
|
|
12
12
|
import { assertEx } from "@xylabs/assert";
|
|
13
|
+
import { IndexedDbArchivist } from "@xyo-network/archivist-indexeddb";
|
|
13
14
|
import { IndexSeparator } from "@xyo-network/archivist-model";
|
|
14
15
|
import { PayloadDiviner } from "@xyo-network/diviner-payload-abstract";
|
|
15
16
|
import { isPayloadDivinerQueryPayload } from "@xyo-network/diviner-payload-model";
|
|
16
17
|
import { PayloadHasher } from "@xyo-network/hash";
|
|
17
18
|
import { openDB } from "idb";
|
|
18
|
-
var IndexedDbPayloadDiviner = class
|
|
19
|
+
var IndexedDbPayloadDiviner = class extends PayloadDiviner {
|
|
19
20
|
static {
|
|
20
21
|
__name(this, "IndexedDbPayloadDiviner");
|
|
21
22
|
}
|
|
22
23
|
static configSchemas = [
|
|
23
24
|
IndexedDbPayloadDivinerConfigSchema
|
|
24
25
|
];
|
|
25
|
-
static defaultDbName = "archivist";
|
|
26
|
-
static defaultDbVersion = 1;
|
|
27
|
-
static defaultStoreName = "payloads";
|
|
28
26
|
_db;
|
|
29
27
|
/**
|
|
30
|
-
* The database name. If not supplied via config it defaults
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
28
|
+
* The database name. If not supplied via config, it defaults
|
|
29
|
+
* to the archivist's name and if archivist's name is not supplied,
|
|
30
|
+
* it defaults to `archivist`. This behavior
|
|
31
|
+
* biases towards a single, isolated DB per archivist which seems to
|
|
32
|
+
* make the most sense for 99% of use cases.
|
|
34
33
|
*/
|
|
35
34
|
get dbName() {
|
|
36
|
-
return this.config?.dbName ??
|
|
35
|
+
return this.config?.dbName ?? this.config?.archivist ?? IndexedDbArchivist.defaultDbName;
|
|
37
36
|
}
|
|
38
37
|
/**
|
|
39
|
-
* The database version. If not supplied via config, it defaults to
|
|
38
|
+
* The database version. If not supplied via config, it defaults to the archivist default version.
|
|
40
39
|
*/
|
|
41
40
|
get dbVersion() {
|
|
42
|
-
return this.config?.dbVersion ??
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* The database indexes.
|
|
46
|
-
*/
|
|
47
|
-
get indexes() {
|
|
48
|
-
return this.config?.storage?.indexes ?? [];
|
|
41
|
+
return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion;
|
|
49
42
|
}
|
|
50
43
|
/**
|
|
51
44
|
* The name of the object store. If not supplied via config, it defaults
|
|
52
45
|
* to `payloads`.
|
|
53
46
|
*/
|
|
54
47
|
get storeName() {
|
|
55
|
-
return this.config?.storeName ??
|
|
48
|
+
return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName;
|
|
56
49
|
}
|
|
57
50
|
get db() {
|
|
58
51
|
return assertEx(this._db, "DB not initialized");
|
|
@@ -77,8 +70,8 @@ var IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends PayloadDivi
|
|
|
77
70
|
};
|
|
78
71
|
const direction = order === "desc" ? "prev" : "next";
|
|
79
72
|
const suggestedIndex = this.selectBestIndex(filter, store);
|
|
80
|
-
const
|
|
81
|
-
let cursor = suggestedIndex ? await store.index(suggestedIndex).openCursor(IDBKeyRange.only(
|
|
73
|
+
const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter);
|
|
74
|
+
let cursor = suggestedIndex ? await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction) : await store.openCursor(suggestedIndex, direction);
|
|
82
75
|
while (cursor && parsedOffset > 0) {
|
|
83
76
|
cursor = await cursor.advance(parsedOffset);
|
|
84
77
|
parsedOffset = 0;
|
|
@@ -95,14 +88,15 @@ var IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends PayloadDivi
|
|
|
95
88
|
this._db = await openDB(this.dbName, this.dbVersion);
|
|
96
89
|
return true;
|
|
97
90
|
}
|
|
98
|
-
|
|
91
|
+
getKeyRangeValue(indexName, query) {
|
|
99
92
|
if (!indexName)
|
|
100
93
|
return [];
|
|
101
94
|
const extractFields = /* @__PURE__ */ __name((indexName2) => {
|
|
102
95
|
return indexName2.slice(3).split(IndexSeparator).map((field) => field.toLowerCase());
|
|
103
96
|
}, "extractFields");
|
|
104
97
|
const indexFields = extractFields(indexName);
|
|
105
|
-
|
|
98
|
+
const keyRangeValue = indexFields.map((field) => query[field]);
|
|
99
|
+
return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue;
|
|
106
100
|
}
|
|
107
101
|
selectBestIndex(query, store) {
|
|
108
102
|
const { indexNames } = store;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/Schema.ts","../../src/Config.ts","../../src/Diviner.ts"],"sourcesContent":["import { PayloadDivinerSchema } from '@xyo-network/diviner-payload-model'\n\nexport const IndexedDbPayloadDivinerSchema = `${PayloadDivinerSchema}.indexeddb`\nexport type IndexedDbPayloadDivinerSchema = typeof IndexedDbPayloadDivinerSchema\n","import { IndexDescription } from '@xyo-network/archivist-model'\nimport { DivinerConfig } from '@xyo-network/diviner-model'\n\nimport { IndexedDbPayloadDivinerSchema } from './Schema'\n\nexport const IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.config`\nexport type IndexedDbPayloadDivinerConfigSchema = typeof IndexedDbPayloadDivinerConfigSchema\n\nexport type IndexedDbPayloadDivinerConfig = DivinerConfig<{\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: IndexedDbPayloadDivinerConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main diviner 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","import { assertEx } from '@xylabs/assert'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { AnyObject } from '@xyo-network/object'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, IDBPObjectStore, openDB } from 'idb'\n\nimport { IndexedDbPayloadDivinerConfigSchema } from './Config'\nimport { IndexedDbPayloadDivinerParams } from './Params'\n\ninterface PayloadStore {\n [s: string]: Payload\n}\n\nexport class IndexedDbPayloadDiviner<\n TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams,\n TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload,\n TOut extends Payload = Payload,\n TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]\n static defaultDbName = 'archivist'\n static defaultDbVersion = 1\n static defaultStoreName = 'payloads'\n\n private _db: IDBPDatabase<PayloadStore> | undefined\n\n /**\n * The database name. If not supplied via config it defaults to\n * `archivist`. This behavior biases towards a single, isolated\n * DB per archivist which seems to make the most sense for 99% of\n * use cases.\n */\n get dbName() {\n return this.config?.dbName ?? IndexedDbPayloadDiviner.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 ?? IndexedDbPayloadDiviner.defaultDbVersion\n }\n\n /**\n * The database indexes.\n */\n get indexes() {\n return this.config?.storage?.indexes ?? []\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 ?? IndexedDbPayloadDiviner.defaultStoreName\n }\n\n private get db(): IDBPDatabase<PayloadStore> {\n return assertEx(this._db, 'DB not initialized')\n }\n\n protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {\n const query = assertEx(payloads?.filter(isPayloadDivinerQueryPayload)?.pop(), 'Missing query payload')\n if (!query) return []\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }\n const tx = this.db.transaction(this.storeName, 'readonly')\n const store = tx.objectStore(this.storeName)\n const results: TOut[] = []\n let parsedOffset = offset ?? 0\n const parsedLimit = limit ?? 10\n assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')\n const filterSchema = schemas?.[0]\n const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }\n const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'\n const suggestedIndex = this.selectBestIndex(filter, store)\n const filterValues = this.getKeyValuesFromQuery(suggestedIndex, filter)\n let cursor = suggestedIndex\n ? // Conditionally filter on schemas\n await store.index(suggestedIndex).openCursor(IDBKeyRange.only(filterValues.length === 1 ? filterValues[0] : filterValues), direction)\n : // Just iterate all records\n await store.openCursor(suggestedIndex, direction)\n\n // Skip records until the offset is reached\n while (cursor && parsedOffset > 0) {\n cursor = await cursor.advance(parsedOffset)\n parsedOffset = 0 // Reset offset after skipping\n }\n // Collect results up to the limit\n while (cursor && results.length < parsedLimit) {\n results.push(cursor.value)\n cursor = await cursor.continue()\n }\n await tx.done\n // Remove any metadata before returning to the client\n return results.map((payload) => PayloadHasher.jsonPayload(payload))\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 this._db = await openDB<PayloadStore>(this.dbName, this.dbVersion)\n return true\n }\n\n private getKeyValuesFromQuery(indexName: string | null, query: AnyObject): unknown[] {\n if (!indexName) return []\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Extracting the relevant fields from the index name\n const indexFields = extractFields(indexName)\n\n // Collecting the values for these fields from the query object\n return indexFields.map((field) => query[field as keyof AnyObject])\n }\n\n private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {\n // List of available indexes\n const { indexNames } = store\n\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Convert query object keys to a set for easier comparison\n const queryKeys = new Set(Object.keys(query).map((key) => key.toLowerCase()))\n\n // Find the best matching index\n let bestMatch: { indexName: string; matchCount: number } = { indexName: '', matchCount: 0 }\n\n for (const indexName of indexNames) {\n const indexFields = extractFields(indexName)\n const matchCount = indexFields.filter((field) => queryKeys.has(field)).length\n if (matchCount > bestMatch.matchCount) {\n bestMatch = { indexName, matchCount }\n }\n }\n return bestMatch.matchCount > 0 ? bestMatch.indexName : null\n }\n}\n"],"mappings":"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","names":["PayloadDivinerSchema","IndexedDbPayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","assertEx","IndexSeparator","PayloadDiviner","isPayloadDivinerQueryPayload","PayloadHasher","openDB","IndexedDbPayloadDiviner","PayloadDiviner","configSchemas","IndexedDbPayloadDivinerConfigSchema","defaultDbName","defaultDbVersion","defaultStoreName","_db","dbName","config","dbVersion","indexes","storage","storeName","db","assertEx","divineHandler","payloads","query","filter","isPayloadDivinerQueryPayload","pop","schemas","limit","offset","hash","order","schema","_schema","sources","props","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","length","filterSchema","direction","suggestedIndex","selectBestIndex","filterValues","getKeyValuesFromQuery","cursor","index","openCursor","IDBKeyRange","only","advance","push","value","continue","done","map","payload","PayloadHasher","jsonPayload","startHandler","openDB","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","Object","keys","key","bestMatch","matchCount","has"]}
|
|
1
|
+
{"version":3,"sources":["../../src/Schema.ts","../../src/Config.ts","../../src/Diviner.ts"],"sourcesContent":["import { PayloadDivinerSchema } from '@xyo-network/diviner-payload-model'\n\nexport const IndexedDbPayloadDivinerSchema = `${PayloadDivinerSchema}.indexeddb`\nexport type IndexedDbPayloadDivinerSchema = typeof IndexedDbPayloadDivinerSchema\n","import { IndexDescription } from '@xyo-network/archivist-model'\nimport { DivinerConfig } from '@xyo-network/diviner-model'\n\nimport { IndexedDbPayloadDivinerSchema } from './Schema'\n\nexport const IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.config`\nexport type IndexedDbPayloadDivinerConfigSchema = typeof IndexedDbPayloadDivinerConfigSchema\n\nexport type IndexedDbPayloadDivinerConfig = DivinerConfig<{\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: IndexedDbPayloadDivinerConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main diviner 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","import { assertEx } from '@xylabs/assert'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { AnyObject } from '@xyo-network/object'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, IDBPObjectStore, openDB } from 'idb'\n\nimport { IndexedDbPayloadDivinerConfigSchema } from './Config'\nimport { IndexedDbPayloadDivinerParams } from './Params'\n\ninterface PayloadStore {\n [s: string]: Payload\n}\n\nexport class IndexedDbPayloadDiviner<\n TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams,\n TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload,\n TOut extends Payload = Payload,\n TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]\n\n private _db: IDBPDatabase<PayloadStore> | undefined\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the archivist's name and if archivist's name is not supplied,\n * 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?.archivist ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to the archivist default version.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\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 private get db(): IDBPDatabase<PayloadStore> {\n return assertEx(this._db, 'DB not initialized')\n }\n\n protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {\n const query = assertEx(payloads?.filter(isPayloadDivinerQueryPayload)?.pop(), 'Missing query payload')\n if (!query) return []\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }\n const tx = this.db.transaction(this.storeName, 'readonly')\n const store = tx.objectStore(this.storeName)\n const results: TOut[] = []\n let parsedOffset = offset ?? 0\n const parsedLimit = limit ?? 10\n assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')\n const filterSchema = schemas?.[0]\n const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }\n const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'\n const suggestedIndex = this.selectBestIndex(filter, store)\n const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter)\n let cursor = suggestedIndex\n ? // Conditionally filter on schemas\n await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction)\n : // Just iterate all records\n await store.openCursor(suggestedIndex, direction)\n\n // Skip records until the offset is reached\n while (cursor && parsedOffset > 0) {\n cursor = await cursor.advance(parsedOffset)\n parsedOffset = 0 // Reset offset after skipping\n }\n // Collect results up to the limit\n while (cursor && results.length < parsedLimit) {\n results.push(cursor.value)\n cursor = await cursor.continue()\n }\n await tx.done\n // Remove any metadata before returning to the client\n return results.map((payload) => PayloadHasher.jsonPayload(payload))\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 this._db = await openDB<PayloadStore>(this.dbName, this.dbVersion)\n return true\n }\n\n private getKeyRangeValue(indexName: string | null, query: AnyObject): unknown | unknown[] {\n if (!indexName) return []\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Extracting the relevant fields from the index name\n const indexFields = extractFields(indexName)\n\n // Collecting the values for these fields from the query object\n const keyRangeValue = indexFields.map((field) => query[field as keyof AnyObject])\n return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue\n }\n\n private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {\n // List of available indexes\n const { indexNames } = store\n\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Convert query object keys to a set for easier comparison\n const queryKeys = new Set(Object.keys(query).map((key) => key.toLowerCase()))\n\n // Find the best matching index\n let bestMatch: { indexName: string; matchCount: number } = { indexName: '', matchCount: 0 }\n\n for (const indexName of indexNames) {\n const indexFields = extractFields(indexName)\n const matchCount = indexFields.filter((field) => queryKeys.has(field)).length\n if (matchCount > bestMatch.matchCount) {\n bestMatch = { indexName, matchCount }\n }\n }\n return bestMatch.matchCount > 0 ? bestMatch.indexName : null\n }\n}\n"],"mappings":"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","names":["PayloadDivinerSchema","IndexedDbPayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","assertEx","IndexedDbArchivist","IndexSeparator","PayloadDiviner","isPayloadDivinerQueryPayload","PayloadHasher","openDB","IndexedDbPayloadDiviner","PayloadDiviner","configSchemas","IndexedDbPayloadDivinerConfigSchema","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","db","assertEx","divineHandler","payloads","query","filter","isPayloadDivinerQueryPayload","pop","schemas","limit","offset","hash","order","schema","_schema","sources","props","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","length","filterSchema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","cursor","index","openCursor","IDBKeyRange","only","advance","push","value","continue","done","map","payload","PayloadHasher","jsonPayload","startHandler","openDB","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","Object","keys","key","bestMatch","matchCount","has"]}
|
package/dist/node/Diviner.d.cts
CHANGED
|
@@ -5,25 +5,19 @@ import { Payload } from '@xyo-network/payload-model';
|
|
|
5
5
|
import { IndexedDbPayloadDivinerParams } from './Params';
|
|
6
6
|
export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams, TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload, TOut extends Payload = Payload, TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {
|
|
7
7
|
static configSchemas: string[];
|
|
8
|
-
static defaultDbName: string;
|
|
9
|
-
static defaultDbVersion: number;
|
|
10
|
-
static defaultStoreName: string;
|
|
11
8
|
private _db;
|
|
12
9
|
/**
|
|
13
|
-
* The database name. If not supplied via config it defaults
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* The database name. If not supplied via config, it defaults
|
|
11
|
+
* to the archivist's name and if archivist's name is not supplied,
|
|
12
|
+
* it defaults to `archivist`. This behavior
|
|
13
|
+
* biases towards a single, isolated DB per archivist which seems to
|
|
14
|
+
* make the most sense for 99% of use cases.
|
|
17
15
|
*/
|
|
18
16
|
get dbName(): string;
|
|
19
17
|
/**
|
|
20
|
-
* The database version. If not supplied via config, it defaults to
|
|
18
|
+
* The database version. If not supplied via config, it defaults to the archivist default version.
|
|
21
19
|
*/
|
|
22
20
|
get dbVersion(): number;
|
|
23
|
-
/**
|
|
24
|
-
* The database indexes.
|
|
25
|
-
*/
|
|
26
|
-
get indexes(): import("@xyo-network/archivist-model").IndexDescription[];
|
|
27
21
|
/**
|
|
28
22
|
* The name of the object store. If not supplied via config, it defaults
|
|
29
23
|
* to `payloads`.
|
|
@@ -32,7 +26,7 @@ export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDiv
|
|
|
32
26
|
private get db();
|
|
33
27
|
protected divineHandler(payloads?: TIn[]): Promise<TOut[]>;
|
|
34
28
|
protected startHandler(): Promise<boolean>;
|
|
35
|
-
private
|
|
29
|
+
private getKeyRangeValue;
|
|
36
30
|
private selectBestIndex;
|
|
37
31
|
}
|
|
38
32
|
//# sourceMappingURL=Diviner.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAMxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED,OAAO,KAAK,EAAE,GAEb;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAqChD,YAAY;IAQrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;CA2BxB"}
|
package/dist/node/Diviner.d.mts
CHANGED
|
@@ -5,25 +5,19 @@ import { Payload } from '@xyo-network/payload-model';
|
|
|
5
5
|
import { IndexedDbPayloadDivinerParams } from './Params';
|
|
6
6
|
export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams, TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload, TOut extends Payload = Payload, TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {
|
|
7
7
|
static configSchemas: string[];
|
|
8
|
-
static defaultDbName: string;
|
|
9
|
-
static defaultDbVersion: number;
|
|
10
|
-
static defaultStoreName: string;
|
|
11
8
|
private _db;
|
|
12
9
|
/**
|
|
13
|
-
* The database name. If not supplied via config it defaults
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* The database name. If not supplied via config, it defaults
|
|
11
|
+
* to the archivist's name and if archivist's name is not supplied,
|
|
12
|
+
* it defaults to `archivist`. This behavior
|
|
13
|
+
* biases towards a single, isolated DB per archivist which seems to
|
|
14
|
+
* make the most sense for 99% of use cases.
|
|
17
15
|
*/
|
|
18
16
|
get dbName(): string;
|
|
19
17
|
/**
|
|
20
|
-
* The database version. If not supplied via config, it defaults to
|
|
18
|
+
* The database version. If not supplied via config, it defaults to the archivist default version.
|
|
21
19
|
*/
|
|
22
20
|
get dbVersion(): number;
|
|
23
|
-
/**
|
|
24
|
-
* The database indexes.
|
|
25
|
-
*/
|
|
26
|
-
get indexes(): import("@xyo-network/archivist-model").IndexDescription[];
|
|
27
21
|
/**
|
|
28
22
|
* The name of the object store. If not supplied via config, it defaults
|
|
29
23
|
* to `payloads`.
|
|
@@ -32,7 +26,7 @@ export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDiv
|
|
|
32
26
|
private get db();
|
|
33
27
|
protected divineHandler(payloads?: TIn[]): Promise<TOut[]>;
|
|
34
28
|
protected startHandler(): Promise<boolean>;
|
|
35
|
-
private
|
|
29
|
+
private getKeyRangeValue;
|
|
36
30
|
private selectBestIndex;
|
|
37
31
|
}
|
|
38
32
|
//# sourceMappingURL=Diviner.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAMxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED,OAAO,KAAK,EAAE,GAEb;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAqChD,YAAY;IAQrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;CA2BxB"}
|
package/dist/node/Diviner.d.ts
CHANGED
|
@@ -5,25 +5,19 @@ import { Payload } from '@xyo-network/payload-model';
|
|
|
5
5
|
import { IndexedDbPayloadDivinerParams } from './Params';
|
|
6
6
|
export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams, TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload, TOut extends Payload = Payload, TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {
|
|
7
7
|
static configSchemas: string[];
|
|
8
|
-
static defaultDbName: string;
|
|
9
|
-
static defaultDbVersion: number;
|
|
10
|
-
static defaultStoreName: string;
|
|
11
8
|
private _db;
|
|
12
9
|
/**
|
|
13
|
-
* The database name. If not supplied via config it defaults
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* The database name. If not supplied via config, it defaults
|
|
11
|
+
* to the archivist's name and if archivist's name is not supplied,
|
|
12
|
+
* it defaults to `archivist`. This behavior
|
|
13
|
+
* biases towards a single, isolated DB per archivist which seems to
|
|
14
|
+
* make the most sense for 99% of use cases.
|
|
17
15
|
*/
|
|
18
16
|
get dbName(): string;
|
|
19
17
|
/**
|
|
20
|
-
* The database version. If not supplied via config, it defaults to
|
|
18
|
+
* The database version. If not supplied via config, it defaults to the archivist default version.
|
|
21
19
|
*/
|
|
22
20
|
get dbVersion(): number;
|
|
23
|
-
/**
|
|
24
|
-
* The database indexes.
|
|
25
|
-
*/
|
|
26
|
-
get indexes(): import("@xyo-network/archivist-model").IndexDescription[];
|
|
27
21
|
/**
|
|
28
22
|
* The name of the object store. If not supplied via config, it defaults
|
|
29
23
|
* to `payloads`.
|
|
@@ -32,7 +26,7 @@ export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDiv
|
|
|
32
26
|
private get db();
|
|
33
27
|
protected divineHandler(payloads?: TIn[]): Promise<TOut[]>;
|
|
34
28
|
protected startHandler(): Promise<boolean>;
|
|
35
|
-
private
|
|
29
|
+
private getKeyRangeValue;
|
|
36
30
|
private selectBestIndex;
|
|
37
31
|
}
|
|
38
32
|
//# sourceMappingURL=Diviner.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAMxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED,OAAO,KAAK,EAAE,GAEb;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAqChD,YAAY;IAQrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;CA2BxB"}
|
package/dist/node/index.cjs
CHANGED
|
@@ -41,6 +41,7 @@ var IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.conf
|
|
|
41
41
|
|
|
42
42
|
// src/Diviner.ts
|
|
43
43
|
var import_assert = require("@xylabs/assert");
|
|
44
|
+
var import_archivist_indexeddb = require("@xyo-network/archivist-indexeddb");
|
|
44
45
|
var import_archivist_model = require("@xyo-network/archivist-model");
|
|
45
46
|
var import_diviner_payload_abstract = require("@xyo-network/diviner-payload-abstract");
|
|
46
47
|
var import_diviner_payload_model2 = require("@xyo-network/diviner-payload-model");
|
|
@@ -49,28 +50,22 @@ var import_idb = require("idb");
|
|
|
49
50
|
var _IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends import_diviner_payload_abstract.PayloadDiviner {
|
|
50
51
|
_db;
|
|
51
52
|
/**
|
|
52
|
-
* The database name. If not supplied via config it defaults
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
53
|
+
* The database name. If not supplied via config, it defaults
|
|
54
|
+
* to the archivist's name and if archivist's name is not supplied,
|
|
55
|
+
* it defaults to `archivist`. This behavior
|
|
56
|
+
* biases towards a single, isolated DB per archivist which seems to
|
|
57
|
+
* make the most sense for 99% of use cases.
|
|
56
58
|
*/
|
|
57
59
|
get dbName() {
|
|
58
|
-
var _a;
|
|
59
|
-
return ((_a = this.config) == null ? void 0 : _a.dbName) ??
|
|
60
|
+
var _a, _b;
|
|
61
|
+
return ((_a = this.config) == null ? void 0 : _a.dbName) ?? ((_b = this.config) == null ? void 0 : _b.archivist) ?? import_archivist_indexeddb.IndexedDbArchivist.defaultDbName;
|
|
60
62
|
}
|
|
61
63
|
/**
|
|
62
|
-
* The database version. If not supplied via config, it defaults to
|
|
64
|
+
* The database version. If not supplied via config, it defaults to the archivist default version.
|
|
63
65
|
*/
|
|
64
66
|
get dbVersion() {
|
|
65
67
|
var _a;
|
|
66
|
-
return ((_a = this.config) == null ? void 0 : _a.dbVersion) ??
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* The database indexes.
|
|
70
|
-
*/
|
|
71
|
-
get indexes() {
|
|
72
|
-
var _a, _b;
|
|
73
|
-
return ((_b = (_a = this.config) == null ? void 0 : _a.storage) == null ? void 0 : _b.indexes) ?? [];
|
|
68
|
+
return ((_a = this.config) == null ? void 0 : _a.dbVersion) ?? import_archivist_indexeddb.IndexedDbArchivist.defaultDbVersion;
|
|
74
69
|
}
|
|
75
70
|
/**
|
|
76
71
|
* The name of the object store. If not supplied via config, it defaults
|
|
@@ -78,7 +73,7 @@ var _IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends import_div
|
|
|
78
73
|
*/
|
|
79
74
|
get storeName() {
|
|
80
75
|
var _a;
|
|
81
|
-
return ((_a = this.config) == null ? void 0 : _a.storeName) ??
|
|
76
|
+
return ((_a = this.config) == null ? void 0 : _a.storeName) ?? import_archivist_indexeddb.IndexedDbArchivist.defaultStoreName;
|
|
82
77
|
}
|
|
83
78
|
get db() {
|
|
84
79
|
return (0, import_assert.assertEx)(this._db, "DB not initialized");
|
|
@@ -104,8 +99,8 @@ var _IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends import_div
|
|
|
104
99
|
};
|
|
105
100
|
const direction = order === "desc" ? "prev" : "next";
|
|
106
101
|
const suggestedIndex = this.selectBestIndex(filter, store);
|
|
107
|
-
const
|
|
108
|
-
let cursor = suggestedIndex ? await store.index(suggestedIndex).openCursor(IDBKeyRange.only(
|
|
102
|
+
const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter);
|
|
103
|
+
let cursor = suggestedIndex ? await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction) : await store.openCursor(suggestedIndex, direction);
|
|
109
104
|
while (cursor && parsedOffset > 0) {
|
|
110
105
|
cursor = await cursor.advance(parsedOffset);
|
|
111
106
|
parsedOffset = 0;
|
|
@@ -122,14 +117,15 @@ var _IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends import_div
|
|
|
122
117
|
this._db = await (0, import_idb.openDB)(this.dbName, this.dbVersion);
|
|
123
118
|
return true;
|
|
124
119
|
}
|
|
125
|
-
|
|
120
|
+
getKeyRangeValue(indexName, query) {
|
|
126
121
|
if (!indexName)
|
|
127
122
|
return [];
|
|
128
123
|
const extractFields = /* @__PURE__ */ __name((indexName2) => {
|
|
129
124
|
return indexName2.slice(3).split(import_archivist_model.IndexSeparator).map((field) => field.toLowerCase());
|
|
130
125
|
}, "extractFields");
|
|
131
126
|
const indexFields = extractFields(indexName);
|
|
132
|
-
|
|
127
|
+
const keyRangeValue = indexFields.map((field) => query[field]);
|
|
128
|
+
return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue;
|
|
133
129
|
}
|
|
134
130
|
selectBestIndex(query, store) {
|
|
135
131
|
const { indexNames } = store;
|
|
@@ -158,9 +154,6 @@ __name(_IndexedDbPayloadDiviner, "IndexedDbPayloadDiviner");
|
|
|
158
154
|
__publicField(_IndexedDbPayloadDiviner, "configSchemas", [
|
|
159
155
|
IndexedDbPayloadDivinerConfigSchema
|
|
160
156
|
]);
|
|
161
|
-
__publicField(_IndexedDbPayloadDiviner, "defaultDbName", "archivist");
|
|
162
|
-
__publicField(_IndexedDbPayloadDiviner, "defaultDbVersion", 1);
|
|
163
|
-
__publicField(_IndexedDbPayloadDiviner, "defaultStoreName", "payloads");
|
|
164
157
|
var IndexedDbPayloadDiviner = _IndexedDbPayloadDiviner;
|
|
165
158
|
// Annotate the CommonJS export names for ESM import in node:
|
|
166
159
|
0 && (module.exports = {
|
package/dist/node/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/index.ts","../../src/Schema.ts","../../src/Config.ts","../../src/Diviner.ts"],"sourcesContent":["export * from './Config'\nexport * from './Diviner'\nexport * from './Params'\nexport * from './Schema'\n","import { PayloadDivinerSchema } from '@xyo-network/diviner-payload-model'\n\nexport const IndexedDbPayloadDivinerSchema = `${PayloadDivinerSchema}.indexeddb`\nexport type IndexedDbPayloadDivinerSchema = typeof IndexedDbPayloadDivinerSchema\n","import { IndexDescription } from '@xyo-network/archivist-model'\nimport { DivinerConfig } from '@xyo-network/diviner-model'\n\nimport { IndexedDbPayloadDivinerSchema } from './Schema'\n\nexport const IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.config`\nexport type IndexedDbPayloadDivinerConfigSchema = typeof IndexedDbPayloadDivinerConfigSchema\n\nexport type IndexedDbPayloadDivinerConfig = DivinerConfig<{\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: IndexedDbPayloadDivinerConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main diviner 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","import { assertEx } from '@xylabs/assert'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { AnyObject } from '@xyo-network/object'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, IDBPObjectStore, openDB } from 'idb'\n\nimport { IndexedDbPayloadDivinerConfigSchema } from './Config'\nimport { IndexedDbPayloadDivinerParams } from './Params'\n\ninterface PayloadStore {\n [s: string]: Payload\n}\n\nexport class IndexedDbPayloadDiviner<\n TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams,\n TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload,\n TOut extends Payload = Payload,\n TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]\n static defaultDbName = 'archivist'\n static defaultDbVersion = 1\n static defaultStoreName = 'payloads'\n\n private _db: IDBPDatabase<PayloadStore> | undefined\n\n /**\n * The database name. If not supplied via config it defaults to\n * `archivist`. This behavior biases towards a single, isolated\n * DB per archivist which seems to make the most sense for 99% of\n * use cases.\n */\n get dbName() {\n return this.config?.dbName ?? IndexedDbPayloadDiviner.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 ?? IndexedDbPayloadDiviner.defaultDbVersion\n }\n\n /**\n * The database indexes.\n */\n get indexes() {\n return this.config?.storage?.indexes ?? []\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 ?? IndexedDbPayloadDiviner.defaultStoreName\n }\n\n private get db(): IDBPDatabase<PayloadStore> {\n return assertEx(this._db, 'DB not initialized')\n }\n\n protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {\n const query = assertEx(payloads?.filter(isPayloadDivinerQueryPayload)?.pop(), 'Missing query payload')\n if (!query) return []\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }\n const tx = this.db.transaction(this.storeName, 'readonly')\n const store = tx.objectStore(this.storeName)\n const results: TOut[] = []\n let parsedOffset = offset ?? 0\n const parsedLimit = limit ?? 10\n assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')\n const filterSchema = schemas?.[0]\n const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }\n const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'\n const suggestedIndex = this.selectBestIndex(filter, store)\n const filterValues = this.getKeyValuesFromQuery(suggestedIndex, filter)\n let cursor = suggestedIndex\n ? // Conditionally filter on schemas\n await store.index(suggestedIndex).openCursor(IDBKeyRange.only(filterValues.length === 1 ? filterValues[0] : filterValues), direction)\n : // Just iterate all records\n await store.openCursor(suggestedIndex, direction)\n\n // Skip records until the offset is reached\n while (cursor && parsedOffset > 0) {\n cursor = await cursor.advance(parsedOffset)\n parsedOffset = 0 // Reset offset after skipping\n }\n // Collect results up to the limit\n while (cursor && results.length < parsedLimit) {\n results.push(cursor.value)\n cursor = await cursor.continue()\n }\n await tx.done\n // Remove any metadata before returning to the client\n return results.map((payload) => PayloadHasher.jsonPayload(payload))\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 this._db = await openDB<PayloadStore>(this.dbName, this.dbVersion)\n return true\n }\n\n private getKeyValuesFromQuery(indexName: string | null, query: AnyObject): unknown[] {\n if (!indexName) return []\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Extracting the relevant fields from the index name\n const indexFields = extractFields(indexName)\n\n // Collecting the values for these fields from the query object\n return indexFields.map((field) => query[field as keyof AnyObject])\n }\n\n private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {\n // List of available indexes\n const { indexNames } = store\n\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Convert query object keys to a set for easier comparison\n const queryKeys = new Set(Object.keys(query).map((key) => key.toLowerCase()))\n\n // Find the best matching index\n let bestMatch: { indexName: string; matchCount: number } = { indexName: '', matchCount: 0 }\n\n for (const indexName of indexNames) {\n const indexFields = extractFields(indexName)\n const matchCount = indexFields.filter((field) => queryKeys.has(field)).length\n if (matchCount > bestMatch.matchCount) {\n bestMatch = { indexName, matchCount }\n }\n }\n return bestMatch.matchCount > 0 ? bestMatch.indexName : null\n }\n}\n"],"mappings":"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","names":["IndexedDbPayloadDivinerSchema","PayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","import_diviner_payload_model","IndexedDbPayloadDiviner","PayloadDiviner","_db","dbName","config","defaultDbName","dbVersion","defaultDbVersion","indexes","storage","storeName","defaultStoreName","db","assertEx","divineHandler","payloads","query","filter","isPayloadDivinerQueryPayload","pop","schemas","limit","offset","hash","order","schema","_schema","sources","props","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","length","filterSchema","direction","suggestedIndex","selectBestIndex","filterValues","getKeyValuesFromQuery","cursor","index","openCursor","IDBKeyRange","only","advance","push","value","continue","done","map","payload","PayloadHasher","jsonPayload","startHandler","openDB","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","Object","keys","key","bestMatch","matchCount","has","configSchemas","IndexedDbPayloadDivinerConfigSchema"]}
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts","../../src/Schema.ts","../../src/Config.ts","../../src/Diviner.ts"],"sourcesContent":["export * from './Config'\nexport * from './Diviner'\nexport * from './Params'\nexport * from './Schema'\n","import { PayloadDivinerSchema } from '@xyo-network/diviner-payload-model'\n\nexport const IndexedDbPayloadDivinerSchema = `${PayloadDivinerSchema}.indexeddb`\nexport type IndexedDbPayloadDivinerSchema = typeof IndexedDbPayloadDivinerSchema\n","import { IndexDescription } from '@xyo-network/archivist-model'\nimport { DivinerConfig } from '@xyo-network/diviner-model'\n\nimport { IndexedDbPayloadDivinerSchema } from './Schema'\n\nexport const IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.config`\nexport type IndexedDbPayloadDivinerConfigSchema = typeof IndexedDbPayloadDivinerConfigSchema\n\nexport type IndexedDbPayloadDivinerConfig = DivinerConfig<{\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: IndexedDbPayloadDivinerConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main diviner 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","import { assertEx } from '@xylabs/assert'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { AnyObject } from '@xyo-network/object'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, IDBPObjectStore, openDB } from 'idb'\n\nimport { IndexedDbPayloadDivinerConfigSchema } from './Config'\nimport { IndexedDbPayloadDivinerParams } from './Params'\n\ninterface PayloadStore {\n [s: string]: Payload\n}\n\nexport class IndexedDbPayloadDiviner<\n TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams,\n TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload,\n TOut extends Payload = Payload,\n TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]\n\n private _db: IDBPDatabase<PayloadStore> | undefined\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the archivist's name and if archivist's name is not supplied,\n * 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?.archivist ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to the archivist default version.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\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 private get db(): IDBPDatabase<PayloadStore> {\n return assertEx(this._db, 'DB not initialized')\n }\n\n protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {\n const query = assertEx(payloads?.filter(isPayloadDivinerQueryPayload)?.pop(), 'Missing query payload')\n if (!query) return []\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }\n const tx = this.db.transaction(this.storeName, 'readonly')\n const store = tx.objectStore(this.storeName)\n const results: TOut[] = []\n let parsedOffset = offset ?? 0\n const parsedLimit = limit ?? 10\n assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')\n const filterSchema = schemas?.[0]\n const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }\n const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'\n const suggestedIndex = this.selectBestIndex(filter, store)\n const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter)\n let cursor = suggestedIndex\n ? // Conditionally filter on schemas\n await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction)\n : // Just iterate all records\n await store.openCursor(suggestedIndex, direction)\n\n // Skip records until the offset is reached\n while (cursor && parsedOffset > 0) {\n cursor = await cursor.advance(parsedOffset)\n parsedOffset = 0 // Reset offset after skipping\n }\n // Collect results up to the limit\n while (cursor && results.length < parsedLimit) {\n results.push(cursor.value)\n cursor = await cursor.continue()\n }\n await tx.done\n // Remove any metadata before returning to the client\n return results.map((payload) => PayloadHasher.jsonPayload(payload))\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 this._db = await openDB<PayloadStore>(this.dbName, this.dbVersion)\n return true\n }\n\n private getKeyRangeValue(indexName: string | null, query: AnyObject): unknown | unknown[] {\n if (!indexName) return []\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Extracting the relevant fields from the index name\n const indexFields = extractFields(indexName)\n\n // Collecting the values for these fields from the query object\n const keyRangeValue = indexFields.map((field) => query[field as keyof AnyObject])\n return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue\n }\n\n private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {\n // List of available indexes\n const { indexNames } = store\n\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Convert query object keys to a set for easier comparison\n const queryKeys = new Set(Object.keys(query).map((key) => key.toLowerCase()))\n\n // Find the best matching index\n let bestMatch: { indexName: string; matchCount: number } = { indexName: '', matchCount: 0 }\n\n for (const indexName of indexNames) {\n const indexFields = extractFields(indexName)\n const matchCount = indexFields.filter((field) => queryKeys.has(field)).length\n if (matchCount > bestMatch.matchCount) {\n bestMatch = { indexName, matchCount }\n }\n }\n return bestMatch.matchCount > 0 ? bestMatch.indexName : null\n }\n}\n"],"mappings":"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","names":["IndexedDbPayloadDivinerSchema","PayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","import_diviner_payload_model","IndexedDbPayloadDiviner","PayloadDiviner","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","db","assertEx","divineHandler","payloads","query","filter","isPayloadDivinerQueryPayload","pop","schemas","limit","offset","hash","order","schema","_schema","sources","props","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","length","filterSchema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","cursor","index","openCursor","IDBKeyRange","only","advance","push","value","continue","done","map","payload","PayloadHasher","jsonPayload","startHandler","openDB","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","Object","keys","key","bestMatch","matchCount","has","configSchemas","IndexedDbPayloadDivinerConfigSchema"]}
|
package/dist/node/index.js
CHANGED
|
@@ -15,6 +15,7 @@ var IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.conf
|
|
|
15
15
|
|
|
16
16
|
// src/Diviner.ts
|
|
17
17
|
import { assertEx } from "@xylabs/assert";
|
|
18
|
+
import { IndexedDbArchivist } from "@xyo-network/archivist-indexeddb";
|
|
18
19
|
import { IndexSeparator } from "@xyo-network/archivist-model";
|
|
19
20
|
import { PayloadDiviner } from "@xyo-network/diviner-payload-abstract";
|
|
20
21
|
import { isPayloadDivinerQueryPayload } from "@xyo-network/diviner-payload-model";
|
|
@@ -23,28 +24,22 @@ import { openDB } from "idb";
|
|
|
23
24
|
var _IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends PayloadDiviner {
|
|
24
25
|
_db;
|
|
25
26
|
/**
|
|
26
|
-
* The database name. If not supplied via config it defaults
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* The database name. If not supplied via config, it defaults
|
|
28
|
+
* to the archivist's name and if archivist's name is not supplied,
|
|
29
|
+
* it defaults to `archivist`. This behavior
|
|
30
|
+
* biases towards a single, isolated DB per archivist which seems to
|
|
31
|
+
* make the most sense for 99% of use cases.
|
|
30
32
|
*/
|
|
31
33
|
get dbName() {
|
|
32
|
-
var _a;
|
|
33
|
-
return ((_a = this.config) == null ? void 0 : _a.dbName) ??
|
|
34
|
+
var _a, _b;
|
|
35
|
+
return ((_a = this.config) == null ? void 0 : _a.dbName) ?? ((_b = this.config) == null ? void 0 : _b.archivist) ?? IndexedDbArchivist.defaultDbName;
|
|
34
36
|
}
|
|
35
37
|
/**
|
|
36
|
-
* The database version. If not supplied via config, it defaults to
|
|
38
|
+
* The database version. If not supplied via config, it defaults to the archivist default version.
|
|
37
39
|
*/
|
|
38
40
|
get dbVersion() {
|
|
39
41
|
var _a;
|
|
40
|
-
return ((_a = this.config) == null ? void 0 : _a.dbVersion) ??
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* The database indexes.
|
|
44
|
-
*/
|
|
45
|
-
get indexes() {
|
|
46
|
-
var _a, _b;
|
|
47
|
-
return ((_b = (_a = this.config) == null ? void 0 : _a.storage) == null ? void 0 : _b.indexes) ?? [];
|
|
42
|
+
return ((_a = this.config) == null ? void 0 : _a.dbVersion) ?? IndexedDbArchivist.defaultDbVersion;
|
|
48
43
|
}
|
|
49
44
|
/**
|
|
50
45
|
* The name of the object store. If not supplied via config, it defaults
|
|
@@ -52,7 +47,7 @@ var _IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends PayloadDiv
|
|
|
52
47
|
*/
|
|
53
48
|
get storeName() {
|
|
54
49
|
var _a;
|
|
55
|
-
return ((_a = this.config) == null ? void 0 : _a.storeName) ??
|
|
50
|
+
return ((_a = this.config) == null ? void 0 : _a.storeName) ?? IndexedDbArchivist.defaultStoreName;
|
|
56
51
|
}
|
|
57
52
|
get db() {
|
|
58
53
|
return assertEx(this._db, "DB not initialized");
|
|
@@ -78,8 +73,8 @@ var _IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends PayloadDiv
|
|
|
78
73
|
};
|
|
79
74
|
const direction = order === "desc" ? "prev" : "next";
|
|
80
75
|
const suggestedIndex = this.selectBestIndex(filter, store);
|
|
81
|
-
const
|
|
82
|
-
let cursor = suggestedIndex ? await store.index(suggestedIndex).openCursor(IDBKeyRange.only(
|
|
76
|
+
const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter);
|
|
77
|
+
let cursor = suggestedIndex ? await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction) : await store.openCursor(suggestedIndex, direction);
|
|
83
78
|
while (cursor && parsedOffset > 0) {
|
|
84
79
|
cursor = await cursor.advance(parsedOffset);
|
|
85
80
|
parsedOffset = 0;
|
|
@@ -96,14 +91,15 @@ var _IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends PayloadDiv
|
|
|
96
91
|
this._db = await openDB(this.dbName, this.dbVersion);
|
|
97
92
|
return true;
|
|
98
93
|
}
|
|
99
|
-
|
|
94
|
+
getKeyRangeValue(indexName, query) {
|
|
100
95
|
if (!indexName)
|
|
101
96
|
return [];
|
|
102
97
|
const extractFields = /* @__PURE__ */ __name((indexName2) => {
|
|
103
98
|
return indexName2.slice(3).split(IndexSeparator).map((field) => field.toLowerCase());
|
|
104
99
|
}, "extractFields");
|
|
105
100
|
const indexFields = extractFields(indexName);
|
|
106
|
-
|
|
101
|
+
const keyRangeValue = indexFields.map((field) => query[field]);
|
|
102
|
+
return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue;
|
|
107
103
|
}
|
|
108
104
|
selectBestIndex(query, store) {
|
|
109
105
|
const { indexNames } = store;
|
|
@@ -132,9 +128,6 @@ __name(_IndexedDbPayloadDiviner, "IndexedDbPayloadDiviner");
|
|
|
132
128
|
__publicField(_IndexedDbPayloadDiviner, "configSchemas", [
|
|
133
129
|
IndexedDbPayloadDivinerConfigSchema
|
|
134
130
|
]);
|
|
135
|
-
__publicField(_IndexedDbPayloadDiviner, "defaultDbName", "archivist");
|
|
136
|
-
__publicField(_IndexedDbPayloadDiviner, "defaultDbVersion", 1);
|
|
137
|
-
__publicField(_IndexedDbPayloadDiviner, "defaultStoreName", "payloads");
|
|
138
131
|
var IndexedDbPayloadDiviner = _IndexedDbPayloadDiviner;
|
|
139
132
|
export {
|
|
140
133
|
IndexedDbPayloadDiviner,
|
package/dist/node/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/Schema.ts","../../src/Config.ts","../../src/Diviner.ts"],"sourcesContent":["import { PayloadDivinerSchema } from '@xyo-network/diviner-payload-model'\n\nexport const IndexedDbPayloadDivinerSchema = `${PayloadDivinerSchema}.indexeddb`\nexport type IndexedDbPayloadDivinerSchema = typeof IndexedDbPayloadDivinerSchema\n","import { IndexDescription } from '@xyo-network/archivist-model'\nimport { DivinerConfig } from '@xyo-network/diviner-model'\n\nimport { IndexedDbPayloadDivinerSchema } from './Schema'\n\nexport const IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.config`\nexport type IndexedDbPayloadDivinerConfigSchema = typeof IndexedDbPayloadDivinerConfigSchema\n\nexport type IndexedDbPayloadDivinerConfig = DivinerConfig<{\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: IndexedDbPayloadDivinerConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main diviner 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","import { assertEx } from '@xylabs/assert'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { AnyObject } from '@xyo-network/object'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, IDBPObjectStore, openDB } from 'idb'\n\nimport { IndexedDbPayloadDivinerConfigSchema } from './Config'\nimport { IndexedDbPayloadDivinerParams } from './Params'\n\ninterface PayloadStore {\n [s: string]: Payload\n}\n\nexport class IndexedDbPayloadDiviner<\n TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams,\n TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload,\n TOut extends Payload = Payload,\n TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]\n static defaultDbName = 'archivist'\n static defaultDbVersion = 1\n static defaultStoreName = 'payloads'\n\n private _db: IDBPDatabase<PayloadStore> | undefined\n\n /**\n * The database name. If not supplied via config it defaults to\n * `archivist`. This behavior biases towards a single, isolated\n * DB per archivist which seems to make the most sense for 99% of\n * use cases.\n */\n get dbName() {\n return this.config?.dbName ?? IndexedDbPayloadDiviner.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 ?? IndexedDbPayloadDiviner.defaultDbVersion\n }\n\n /**\n * The database indexes.\n */\n get indexes() {\n return this.config?.storage?.indexes ?? []\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 ?? IndexedDbPayloadDiviner.defaultStoreName\n }\n\n private get db(): IDBPDatabase<PayloadStore> {\n return assertEx(this._db, 'DB not initialized')\n }\n\n protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {\n const query = assertEx(payloads?.filter(isPayloadDivinerQueryPayload)?.pop(), 'Missing query payload')\n if (!query) return []\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }\n const tx = this.db.transaction(this.storeName, 'readonly')\n const store = tx.objectStore(this.storeName)\n const results: TOut[] = []\n let parsedOffset = offset ?? 0\n const parsedLimit = limit ?? 10\n assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')\n const filterSchema = schemas?.[0]\n const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }\n const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'\n const suggestedIndex = this.selectBestIndex(filter, store)\n const filterValues = this.getKeyValuesFromQuery(suggestedIndex, filter)\n let cursor = suggestedIndex\n ? // Conditionally filter on schemas\n await store.index(suggestedIndex).openCursor(IDBKeyRange.only(filterValues.length === 1 ? filterValues[0] : filterValues), direction)\n : // Just iterate all records\n await store.openCursor(suggestedIndex, direction)\n\n // Skip records until the offset is reached\n while (cursor && parsedOffset > 0) {\n cursor = await cursor.advance(parsedOffset)\n parsedOffset = 0 // Reset offset after skipping\n }\n // Collect results up to the limit\n while (cursor && results.length < parsedLimit) {\n results.push(cursor.value)\n cursor = await cursor.continue()\n }\n await tx.done\n // Remove any metadata before returning to the client\n return results.map((payload) => PayloadHasher.jsonPayload(payload))\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 this._db = await openDB<PayloadStore>(this.dbName, this.dbVersion)\n return true\n }\n\n private getKeyValuesFromQuery(indexName: string | null, query: AnyObject): unknown[] {\n if (!indexName) return []\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Extracting the relevant fields from the index name\n const indexFields = extractFields(indexName)\n\n // Collecting the values for these fields from the query object\n return indexFields.map((field) => query[field as keyof AnyObject])\n }\n\n private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {\n // List of available indexes\n const { indexNames } = store\n\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Convert query object keys to a set for easier comparison\n const queryKeys = new Set(Object.keys(query).map((key) => key.toLowerCase()))\n\n // Find the best matching index\n let bestMatch: { indexName: string; matchCount: number } = { indexName: '', matchCount: 0 }\n\n for (const indexName of indexNames) {\n const indexFields = extractFields(indexName)\n const matchCount = indexFields.filter((field) => queryKeys.has(field)).length\n if (matchCount > bestMatch.matchCount) {\n bestMatch = { indexName, matchCount }\n }\n }\n return bestMatch.matchCount > 0 ? bestMatch.indexName : null\n }\n}\n"],"mappings":"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","names":["PayloadDivinerSchema","IndexedDbPayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","assertEx","IndexSeparator","PayloadDiviner","isPayloadDivinerQueryPayload","PayloadHasher","openDB","IndexedDbPayloadDiviner","PayloadDiviner","_db","dbName","config","defaultDbName","dbVersion","defaultDbVersion","indexes","storage","storeName","defaultStoreName","db","assertEx","divineHandler","payloads","query","filter","isPayloadDivinerQueryPayload","pop","schemas","limit","offset","hash","order","schema","_schema","sources","props","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","length","filterSchema","direction","suggestedIndex","selectBestIndex","filterValues","getKeyValuesFromQuery","cursor","index","openCursor","IDBKeyRange","only","advance","push","value","continue","done","map","payload","PayloadHasher","jsonPayload","startHandler","openDB","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","Object","keys","key","bestMatch","matchCount","has","configSchemas","IndexedDbPayloadDivinerConfigSchema"]}
|
|
1
|
+
{"version":3,"sources":["../../src/Schema.ts","../../src/Config.ts","../../src/Diviner.ts"],"sourcesContent":["import { PayloadDivinerSchema } from '@xyo-network/diviner-payload-model'\n\nexport const IndexedDbPayloadDivinerSchema = `${PayloadDivinerSchema}.indexeddb`\nexport type IndexedDbPayloadDivinerSchema = typeof IndexedDbPayloadDivinerSchema\n","import { IndexDescription } from '@xyo-network/archivist-model'\nimport { DivinerConfig } from '@xyo-network/diviner-model'\n\nimport { IndexedDbPayloadDivinerSchema } from './Schema'\n\nexport const IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.config`\nexport type IndexedDbPayloadDivinerConfigSchema = typeof IndexedDbPayloadDivinerConfigSchema\n\nexport type IndexedDbPayloadDivinerConfig = DivinerConfig<{\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: IndexedDbPayloadDivinerConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main diviner 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","import { assertEx } from '@xylabs/assert'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { AnyObject } from '@xyo-network/object'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, IDBPObjectStore, openDB } from 'idb'\n\nimport { IndexedDbPayloadDivinerConfigSchema } from './Config'\nimport { IndexedDbPayloadDivinerParams } from './Params'\n\ninterface PayloadStore {\n [s: string]: Payload\n}\n\nexport class IndexedDbPayloadDiviner<\n TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams,\n TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload,\n TOut extends Payload = Payload,\n TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]\n\n private _db: IDBPDatabase<PayloadStore> | undefined\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the archivist's name and if archivist's name is not supplied,\n * 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?.archivist ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to the archivist default version.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\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 private get db(): IDBPDatabase<PayloadStore> {\n return assertEx(this._db, 'DB not initialized')\n }\n\n protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {\n const query = assertEx(payloads?.filter(isPayloadDivinerQueryPayload)?.pop(), 'Missing query payload')\n if (!query) return []\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }\n const tx = this.db.transaction(this.storeName, 'readonly')\n const store = tx.objectStore(this.storeName)\n const results: TOut[] = []\n let parsedOffset = offset ?? 0\n const parsedLimit = limit ?? 10\n assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')\n const filterSchema = schemas?.[0]\n const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }\n const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'\n const suggestedIndex = this.selectBestIndex(filter, store)\n const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter)\n let cursor = suggestedIndex\n ? // Conditionally filter on schemas\n await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction)\n : // Just iterate all records\n await store.openCursor(suggestedIndex, direction)\n\n // Skip records until the offset is reached\n while (cursor && parsedOffset > 0) {\n cursor = await cursor.advance(parsedOffset)\n parsedOffset = 0 // Reset offset after skipping\n }\n // Collect results up to the limit\n while (cursor && results.length < parsedLimit) {\n results.push(cursor.value)\n cursor = await cursor.continue()\n }\n await tx.done\n // Remove any metadata before returning to the client\n return results.map((payload) => PayloadHasher.jsonPayload(payload))\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 this._db = await openDB<PayloadStore>(this.dbName, this.dbVersion)\n return true\n }\n\n private getKeyRangeValue(indexName: string | null, query: AnyObject): unknown | unknown[] {\n if (!indexName) return []\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Extracting the relevant fields from the index name\n const indexFields = extractFields(indexName)\n\n // Collecting the values for these fields from the query object\n const keyRangeValue = indexFields.map((field) => query[field as keyof AnyObject])\n return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue\n }\n\n private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {\n // List of available indexes\n const { indexNames } = store\n\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Convert query object keys to a set for easier comparison\n const queryKeys = new Set(Object.keys(query).map((key) => key.toLowerCase()))\n\n // Find the best matching index\n let bestMatch: { indexName: string; matchCount: number } = { indexName: '', matchCount: 0 }\n\n for (const indexName of indexNames) {\n const indexFields = extractFields(indexName)\n const matchCount = indexFields.filter((field) => queryKeys.has(field)).length\n if (matchCount > bestMatch.matchCount) {\n bestMatch = { indexName, matchCount }\n }\n }\n return bestMatch.matchCount > 0 ? bestMatch.indexName : null\n }\n}\n"],"mappings":"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","names":["PayloadDivinerSchema","IndexedDbPayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","assertEx","IndexedDbArchivist","IndexSeparator","PayloadDiviner","isPayloadDivinerQueryPayload","PayloadHasher","openDB","IndexedDbPayloadDiviner","PayloadDiviner","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","db","assertEx","divineHandler","payloads","query","filter","isPayloadDivinerQueryPayload","pop","schemas","limit","offset","hash","order","schema","_schema","sources","props","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","length","filterSchema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","cursor","index","openCursor","IDBKeyRange","only","advance","push","value","continue","done","map","payload","PayloadHasher","jsonPayload","startHandler","openDB","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","Object","keys","key","bestMatch","matchCount","has","configSchemas","IndexedDbPayloadDivinerConfigSchema"]}
|
package/package.json
CHANGED
|
@@ -11,23 +11,23 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@xylabs/assert": "^2.13.23",
|
|
14
|
-
"@xyo-network/
|
|
15
|
-
"@xyo-network/diviner-
|
|
16
|
-
"@xyo-network/diviner-payload-
|
|
17
|
-
"@xyo-network/
|
|
18
|
-
"@xyo-network/
|
|
14
|
+
"@xyo-network/archivist-model": "~2.87.0-rc.3",
|
|
15
|
+
"@xyo-network/diviner-model": "~2.87.0-rc.3",
|
|
16
|
+
"@xyo-network/diviner-payload-abstract": "~2.87.0-rc.3",
|
|
17
|
+
"@xyo-network/diviner-payload-model": "~2.87.0-rc.3",
|
|
18
|
+
"@xyo-network/hash": "~2.87.0-rc.3",
|
|
19
|
+
"@xyo-network/payload-model": "~2.87.0-rc.3",
|
|
19
20
|
"idb": "^8.0.0"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
23
|
"@xylabs/ts-scripts-yarn3": "^3.2.33",
|
|
23
24
|
"@xylabs/tsconfig": "^3.2.33",
|
|
24
|
-
"@xyo-network/account": "~2.87.0-rc.
|
|
25
|
-
"@xyo-network/archivist-indexeddb": "~2.87.0-rc.
|
|
26
|
-
"@xyo-network/
|
|
27
|
-
"@xyo-network/
|
|
28
|
-
"@xyo-network/
|
|
29
|
-
"@xyo-network/
|
|
30
|
-
"@xyo-network/payload-builder": "~2.87.0-rc.2",
|
|
25
|
+
"@xyo-network/account": "~2.87.0-rc.3",
|
|
26
|
+
"@xyo-network/archivist-indexeddb": "~2.87.0-rc.3",
|
|
27
|
+
"@xyo-network/module-model": "~2.87.0-rc.3",
|
|
28
|
+
"@xyo-network/node-memory": "~2.87.0-rc.3",
|
|
29
|
+
"@xyo-network/object": "~2.87.0-rc.3",
|
|
30
|
+
"@xyo-network/payload-builder": "~2.87.0-rc.3",
|
|
31
31
|
"fake-indexeddb": "^5.0.2",
|
|
32
32
|
"typescript": "^5.3.3"
|
|
33
33
|
},
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"url": "https://github.com/XYOracleNetwork/sdk-xyo-client-js.git"
|
|
71
71
|
},
|
|
72
72
|
"sideEffects": false,
|
|
73
|
-
"version": "2.87.0-rc.
|
|
73
|
+
"version": "2.87.0-rc.3",
|
|
74
74
|
"type": "module",
|
|
75
75
|
"stableVersion": "2.86.1"
|
|
76
76
|
}
|
package/src/Diviner.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assertEx } from '@xylabs/assert'
|
|
2
|
+
import { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'
|
|
2
3
|
import { IndexSeparator } from '@xyo-network/archivist-model'
|
|
3
4
|
import { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'
|
|
4
5
|
import { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'
|
|
@@ -22,34 +23,25 @@ export class IndexedDbPayloadDiviner<
|
|
|
22
23
|
TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,
|
|
23
24
|
> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {
|
|
24
25
|
static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]
|
|
25
|
-
static defaultDbName = 'archivist'
|
|
26
|
-
static defaultDbVersion = 1
|
|
27
|
-
static defaultStoreName = 'payloads'
|
|
28
26
|
|
|
29
27
|
private _db: IDBPDatabase<PayloadStore> | undefined
|
|
30
28
|
|
|
31
29
|
/**
|
|
32
|
-
* The database name. If not supplied via config it defaults
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
30
|
+
* The database name. If not supplied via config, it defaults
|
|
31
|
+
* to the archivist's name and if archivist's name is not supplied,
|
|
32
|
+
* it defaults to `archivist`. This behavior
|
|
33
|
+
* biases towards a single, isolated DB per archivist which seems to
|
|
34
|
+
* make the most sense for 99% of use cases.
|
|
36
35
|
*/
|
|
37
36
|
get dbName() {
|
|
38
|
-
return this.config?.dbName ??
|
|
37
|
+
return this.config?.dbName ?? this.config?.archivist ?? IndexedDbArchivist.defaultDbName
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
/**
|
|
42
|
-
* The database version. If not supplied via config, it defaults to
|
|
41
|
+
* The database version. If not supplied via config, it defaults to the archivist default version.
|
|
43
42
|
*/
|
|
44
43
|
get dbVersion() {
|
|
45
|
-
return this.config?.dbVersion ??
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* The database indexes.
|
|
50
|
-
*/
|
|
51
|
-
get indexes() {
|
|
52
|
-
return this.config?.storage?.indexes ?? []
|
|
44
|
+
return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion
|
|
53
45
|
}
|
|
54
46
|
|
|
55
47
|
/**
|
|
@@ -57,7 +49,7 @@ export class IndexedDbPayloadDiviner<
|
|
|
57
49
|
* to `payloads`.
|
|
58
50
|
*/
|
|
59
51
|
get storeName() {
|
|
60
|
-
return this.config?.storeName ??
|
|
52
|
+
return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName
|
|
61
53
|
}
|
|
62
54
|
|
|
63
55
|
private get db(): IDBPDatabase<PayloadStore> {
|
|
@@ -79,10 +71,10 @@ export class IndexedDbPayloadDiviner<
|
|
|
79
71
|
const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }
|
|
80
72
|
const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'
|
|
81
73
|
const suggestedIndex = this.selectBestIndex(filter, store)
|
|
82
|
-
const
|
|
74
|
+
const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter)
|
|
83
75
|
let cursor = suggestedIndex
|
|
84
76
|
? // Conditionally filter on schemas
|
|
85
|
-
await store.index(suggestedIndex).openCursor(IDBKeyRange.only(
|
|
77
|
+
await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction)
|
|
86
78
|
: // Just iterate all records
|
|
87
79
|
await store.openCursor(suggestedIndex, direction)
|
|
88
80
|
|
|
@@ -109,7 +101,7 @@ export class IndexedDbPayloadDiviner<
|
|
|
109
101
|
return true
|
|
110
102
|
}
|
|
111
103
|
|
|
112
|
-
private
|
|
104
|
+
private getKeyRangeValue(indexName: string | null, query: AnyObject): unknown | unknown[] {
|
|
113
105
|
if (!indexName) return []
|
|
114
106
|
// Function to extract fields from an index name
|
|
115
107
|
const extractFields = (indexName: string): string[] => {
|
|
@@ -123,7 +115,8 @@ export class IndexedDbPayloadDiviner<
|
|
|
123
115
|
const indexFields = extractFields(indexName)
|
|
124
116
|
|
|
125
117
|
// Collecting the values for these fields from the query object
|
|
126
|
-
|
|
118
|
+
const keyRangeValue = indexFields.map((field) => query[field as keyof AnyObject])
|
|
119
|
+
return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue
|
|
127
120
|
}
|
|
128
121
|
|
|
129
122
|
private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {
|