@naturalcycles/datastore-lib 3.39.3 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DatastoreStreamReadable.d.ts +3 -3
- package/dist/DatastoreStreamReadable.js +29 -24
- package/dist/datastore.db.d.ts +11 -10
- package/dist/datastore.db.js +114 -86
- package/dist/datastore.model.d.ts +2 -2
- package/dist/datastore.model.js +2 -5
- package/dist/datastoreKeyValueDB.d.ts +4 -5
- package/dist/datastoreKeyValueDB.js +16 -18
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -6
- package/dist/query.util.d.ts +4 -4
- package/dist/query.util.js +2 -6
- package/package.json +7 -4
- package/src/DatastoreStreamReadable.ts +5 -4
- package/src/datastore.db.ts +114 -69
- package/src/datastore.model.ts +6 -2
- package/src/datastoreKeyValueDB.ts +7 -11
- package/src/index.ts +3 -3
- package/src/query.util.ts +5 -4
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Readable } from 'node:stream';
|
|
2
|
-
import { Query } from '@google-cloud/datastore';
|
|
3
|
-
import { CommonLogger } from '@naturalcycles/js-lib';
|
|
2
|
+
import type { Query } from '@google-cloud/datastore';
|
|
3
|
+
import type { CommonLogger } from '@naturalcycles/js-lib';
|
|
4
4
|
import type { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
5
|
-
import type { DatastoreDBStreamOptions } from './datastore.model';
|
|
5
|
+
import type { DatastoreDBStreamOptions } from './datastore.model.js';
|
|
6
6
|
export declare class DatastoreStreamReadable<T = any> extends Readable implements ReadableTyped<T> {
|
|
7
7
|
private q;
|
|
8
8
|
private logger;
|
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import { _ms, pRetry } from '@naturalcycles/js-lib';
|
|
3
|
+
export class DatastoreStreamReadable extends Readable {
|
|
4
|
+
q;
|
|
5
|
+
logger;
|
|
6
|
+
originalLimit;
|
|
7
|
+
rowsRetrieved = 0;
|
|
8
|
+
endCursor;
|
|
9
|
+
running = false;
|
|
10
|
+
done = false;
|
|
11
|
+
lastQueryDone;
|
|
12
|
+
totalWait = 0;
|
|
13
|
+
table;
|
|
14
|
+
/**
|
|
15
|
+
* Used to support maxWait
|
|
16
|
+
*/
|
|
17
|
+
lastReadTimestamp = 0;
|
|
18
|
+
maxWaitInterval;
|
|
19
|
+
opt;
|
|
20
|
+
dsOpt;
|
|
7
21
|
constructor(q, opt, logger) {
|
|
8
22
|
super({ objectMode: true });
|
|
9
23
|
this.q = q;
|
|
10
24
|
this.logger = logger;
|
|
11
|
-
this.rowsRetrieved = 0;
|
|
12
|
-
this.running = false;
|
|
13
|
-
this.done = false;
|
|
14
|
-
this.totalWait = 0;
|
|
15
|
-
/**
|
|
16
|
-
* Used to support maxWait
|
|
17
|
-
*/
|
|
18
|
-
this.lastReadTimestamp = 0;
|
|
19
|
-
/**
|
|
20
|
-
* Counts how many times _read was called.
|
|
21
|
-
* For debugging.
|
|
22
|
-
*/
|
|
23
|
-
this.count = 0;
|
|
24
25
|
this.opt = {
|
|
25
26
|
rssLimitMB: 1000,
|
|
26
27
|
batchSize: 1000,
|
|
@@ -75,7 +76,7 @@ class DatastoreStreamReadable extends node_stream_1.Readable {
|
|
|
75
76
|
let rows = [];
|
|
76
77
|
let info = {};
|
|
77
78
|
try {
|
|
78
|
-
await
|
|
79
|
+
await pRetry(async () => {
|
|
79
80
|
const res = await q.run(this.dsOpt);
|
|
80
81
|
rows = res[0];
|
|
81
82
|
info = res[1];
|
|
@@ -98,9 +99,9 @@ class DatastoreStreamReadable extends node_stream_1.Readable {
|
|
|
98
99
|
return;
|
|
99
100
|
}
|
|
100
101
|
this.rowsRetrieved += rows.length;
|
|
101
|
-
this.logger.log(`${this.table} got ${rows.length} rows, ${this.rowsRetrieved} rowsRetrieved, totalWait: ${
|
|
102
|
+
this.logger.log(`${this.table} got ${rows.length} rows, ${this.rowsRetrieved} rowsRetrieved, totalWait: ${_ms(this.totalWait)}`, info.moreResults);
|
|
102
103
|
if (!rows.length) {
|
|
103
|
-
this.logger.warn(`${this.table} got 0 rows, totalWait: ${
|
|
104
|
+
this.logger.warn(`${this.table} got 0 rows, totalWait: ${_ms(this.totalWait)}`, info.moreResults);
|
|
104
105
|
}
|
|
105
106
|
this.endCursor = info.endCursor;
|
|
106
107
|
this.running = false; // ready to take more _reads
|
|
@@ -109,7 +110,7 @@ class DatastoreStreamReadable extends node_stream_1.Readable {
|
|
|
109
110
|
if (!info.endCursor ||
|
|
110
111
|
info.moreResults === 'NO_MORE_RESULTS' ||
|
|
111
112
|
(this.originalLimit && this.rowsRetrieved >= this.originalLimit)) {
|
|
112
|
-
this.logger.log(`!!!! DONE! ${this.rowsRetrieved} rowsRetrieved, totalWait: ${
|
|
113
|
+
this.logger.log(`!!!! DONE! ${this.rowsRetrieved} rowsRetrieved, totalWait: ${_ms(this.totalWait)}`);
|
|
113
114
|
this.push(null);
|
|
114
115
|
this.done = true;
|
|
115
116
|
clearInterval(this.maxWaitInterval);
|
|
@@ -128,6 +129,11 @@ class DatastoreStreamReadable extends node_stream_1.Readable {
|
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Counts how many times _read was called.
|
|
134
|
+
* For debugging.
|
|
135
|
+
*/
|
|
136
|
+
count = 0;
|
|
131
137
|
_read() {
|
|
132
138
|
this.lastReadTimestamp = Date.now();
|
|
133
139
|
// console.log(`_read called ${++this.count}, wasRunning: ${this.running}`) // debugging
|
|
@@ -147,4 +153,3 @@ class DatastoreStreamReadable extends node_stream_1.Readable {
|
|
|
147
153
|
}
|
|
148
154
|
}
|
|
149
155
|
}
|
|
150
|
-
exports.DatastoreStreamReadable = DatastoreStreamReadable;
|
package/dist/datastore.db.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { Datastore, Key } from '@google-cloud/datastore';
|
|
2
|
-
import {
|
|
3
|
-
import { BaseCommonDB
|
|
4
|
-
import { CommonLogger, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId } from '@naturalcycles/js-lib';
|
|
5
|
-
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
6
|
-
import { DatastoreDBCfg, DatastoreDBOptions, DatastoreDBReadOptions, DatastoreDBSaveOptions, DatastoreDBStreamOptions, DatastorePropertyStats, DatastoreStats } from './datastore.model';
|
|
1
|
+
import type { Datastore, Key, Transaction } from '@google-cloud/datastore';
|
|
2
|
+
import type { CommonDB, CommonDBOptions, CommonDBReadOptions, CommonDBSaveOptions, CommonDBSupport, CommonDBTransactionOptions, DBQuery, DBTransaction, DBTransactionFn, RunQueryResult } from '@naturalcycles/db-lib';
|
|
3
|
+
import { BaseCommonDB } from '@naturalcycles/db-lib';
|
|
4
|
+
import type { CommonLogger, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId } from '@naturalcycles/js-lib';
|
|
5
|
+
import type { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
6
|
+
import type { DatastoreDBCfg, DatastoreDBOptions, DatastoreDBReadOptions, DatastoreDBSaveOptions, DatastoreDBStreamOptions, DatastorePropertyStats, DatastoreStats } from './datastore.model.js';
|
|
7
7
|
/**
|
|
8
8
|
* Datastore API:
|
|
9
9
|
* https://googlecloudplatform.github.io/google-cloud-node/#/docs/datastore/1.0.3/datastore
|
|
@@ -20,14 +20,15 @@ export declare class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
20
20
|
* Datastore.KEY
|
|
21
21
|
*/
|
|
22
22
|
protected KEY: symbol;
|
|
23
|
-
ds(): Datastore
|
|
23
|
+
ds(): Promise<Datastore>;
|
|
24
|
+
private getPropertyFilter;
|
|
25
|
+
private getDatastoreLib;
|
|
24
26
|
ping(): Promise<void>;
|
|
25
27
|
getByIds<ROW extends ObjectWithId>(table: string, ids: string[], opt?: DatastoreDBReadOptions): Promise<ROW[]>;
|
|
26
28
|
runQuery<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, opt?: DatastoreDBReadOptions): Promise<RunQueryResult<ROW>>;
|
|
27
29
|
runQueryCount<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, opt?: DatastoreDBReadOptions): Promise<number>;
|
|
28
30
|
private runDatastoreQuery;
|
|
29
|
-
|
|
30
|
-
streamQuery<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, opt?: DatastoreDBStreamOptions): ReadableTyped<ROW>;
|
|
31
|
+
streamQuery<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, _opt?: DatastoreDBStreamOptions): ReadableTyped<ROW>;
|
|
31
32
|
/**
|
|
32
33
|
* Returns saved entities with generated id/updated/created (non-mutating!)
|
|
33
34
|
*/
|
|
@@ -48,7 +49,7 @@ export declare class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
48
49
|
getTableProperties(table: string): Promise<DatastorePropertyStats[]>;
|
|
49
50
|
private mapId;
|
|
50
51
|
private toDatastoreEntity;
|
|
51
|
-
key(kind: string, id: string): Key;
|
|
52
|
+
key(ds: Datastore, kind: string, id: string): Key;
|
|
52
53
|
private getDsKey;
|
|
53
54
|
getKey(key: Key): string | undefined;
|
|
54
55
|
createTable<ROW extends ObjectWithId>(_table: string, _schema: JsonSchemaObject<ROW>): Promise<void>;
|
package/dist/datastore.db.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const datastore_model_1 = require("./datastore.model");
|
|
9
|
-
const DatastoreStreamReadable_1 = require("./DatastoreStreamReadable");
|
|
10
|
-
const query_util_1 = require("./query.util");
|
|
1
|
+
import { Transform } from 'node:stream';
|
|
2
|
+
import { BaseCommonDB, commonDBFullSupport } from '@naturalcycles/db-lib';
|
|
3
|
+
import { _assert, _chunk, _errorDataAppend, _omit, commonLoggerMinLevel, pMap, pRetry, pRetryFn, pTimeout, TimeoutError, } from '@naturalcycles/js-lib';
|
|
4
|
+
import { boldWhite } from '@naturalcycles/nodejs-lib';
|
|
5
|
+
import { DatastoreType } from './datastore.model.js';
|
|
6
|
+
import { DatastoreStreamReadable } from './DatastoreStreamReadable.js';
|
|
7
|
+
import { dbQueryToDatastoreQuery } from './query.util.js';
|
|
11
8
|
// Datastore (also Firestore and other Google APIs) supports max 500 of items when saving/deleting, etc.
|
|
12
9
|
const MAX_ITEMS = 500;
|
|
13
10
|
// It's an empyrical value, but anything less than infinity is better than infinity
|
|
@@ -33,29 +30,33 @@ const methodMap = {
|
|
|
33
30
|
* https://googlecloudplatform.github.io/google-cloud-node/#/docs/datastore/1.0.3/datastore
|
|
34
31
|
* https://cloud.google.com/datastore/docs/datastore-api-tutorial
|
|
35
32
|
*/
|
|
36
|
-
class DatastoreDB extends
|
|
33
|
+
export class DatastoreDB extends BaseCommonDB {
|
|
34
|
+
support = {
|
|
35
|
+
...commonDBFullSupport,
|
|
36
|
+
patchByQuery: false,
|
|
37
|
+
increment: false,
|
|
38
|
+
};
|
|
37
39
|
constructor(cfg = {}) {
|
|
38
40
|
super();
|
|
39
|
-
this.support = {
|
|
40
|
-
...db_lib_1.commonDBFullSupport,
|
|
41
|
-
patchByQuery: false,
|
|
42
|
-
increment: false,
|
|
43
|
-
};
|
|
44
41
|
this.cfg = {
|
|
45
42
|
logger: console,
|
|
46
43
|
...cfg,
|
|
47
44
|
};
|
|
48
45
|
}
|
|
46
|
+
cfg;
|
|
47
|
+
cachedDatastore;
|
|
48
|
+
/**
|
|
49
|
+
* Datastore.KEY
|
|
50
|
+
*/
|
|
51
|
+
KEY;
|
|
49
52
|
// @memo() // not used to be able to connect to many DBs in the same server instance
|
|
50
|
-
ds() {
|
|
53
|
+
async ds() {
|
|
51
54
|
if (!this.cachedDatastore) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const datastoreLib = require('@google-cloud/datastore');
|
|
55
|
-
const DS = datastoreLib.Datastore;
|
|
55
|
+
_assert(process.env['APP_ENV'] !== 'test', 'DatastoreDB cannot be used in Test env, please use InMemoryDB');
|
|
56
|
+
const DS = (await this.getDatastoreLib()).Datastore;
|
|
56
57
|
this.cfg.projectId ||= this.cfg.credentials?.project_id || process.env['GOOGLE_CLOUD_PROJECT'];
|
|
57
58
|
if (this.cfg.projectId) {
|
|
58
|
-
this.cfg.logger.log(`DatastoreDB connected to ${
|
|
59
|
+
this.cfg.logger.log(`DatastoreDB connected to ${boldWhite(this.cfg.projectId)}`);
|
|
59
60
|
}
|
|
60
61
|
else if (process.env['GOOGLE_APPLICATION_CREDENTIALS']) {
|
|
61
62
|
this.cfg.logger.log(`DatastoreDB connected via GOOGLE_APPLICATION_CREDENTIALS`);
|
|
@@ -68,37 +69,46 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
68
69
|
}
|
|
69
70
|
return this.cachedDatastore;
|
|
70
71
|
}
|
|
72
|
+
async getPropertyFilter() {
|
|
73
|
+
return (await this.getDatastoreLib()).PropertyFilter;
|
|
74
|
+
}
|
|
75
|
+
async getDatastoreLib() {
|
|
76
|
+
// Lazy-loading
|
|
77
|
+
const { default: lib } = await import('@google-cloud/datastore');
|
|
78
|
+
return lib;
|
|
79
|
+
}
|
|
71
80
|
async ping() {
|
|
72
81
|
await this.getAllStats();
|
|
73
82
|
}
|
|
74
83
|
async getByIds(table, ids, opt = {}) {
|
|
75
84
|
if (!ids.length)
|
|
76
85
|
return [];
|
|
77
|
-
|
|
86
|
+
let ds = await this.ds();
|
|
87
|
+
const keys = ids.map(id => this.key(ds, table, id));
|
|
78
88
|
let rows;
|
|
79
89
|
const dsOpt = this.getRunQueryOptions(opt);
|
|
80
90
|
if (this.cfg.timeout) {
|
|
81
91
|
// First try
|
|
82
92
|
try {
|
|
83
|
-
const r = await
|
|
93
|
+
const r = await pTimeout(() => (opt.tx?.tx || ds).get(keys, dsOpt), {
|
|
84
94
|
timeout: this.cfg.timeout,
|
|
85
95
|
name: `datastore.getByIds(${table})`,
|
|
86
96
|
});
|
|
87
97
|
rows = r[0];
|
|
88
98
|
}
|
|
89
99
|
catch (err) {
|
|
90
|
-
if (!(err instanceof
|
|
100
|
+
if (!(err instanceof TimeoutError)) {
|
|
91
101
|
// Not a timeout error, re-throw
|
|
92
102
|
throw err;
|
|
93
103
|
}
|
|
94
104
|
this.cfg.logger.log('datastore recreated on error');
|
|
95
105
|
// This is to debug "GCP Datastore Timeout issue"
|
|
96
|
-
const datastoreLib =
|
|
106
|
+
const datastoreLib = await this.getDatastoreLib();
|
|
97
107
|
const DS = datastoreLib.Datastore;
|
|
98
|
-
this.cachedDatastore = new DS(this.cfg);
|
|
108
|
+
ds = this.cachedDatastore = new DS(this.cfg);
|
|
99
109
|
// Second try (will throw)
|
|
100
110
|
try {
|
|
101
|
-
const r = await
|
|
111
|
+
const r = await pRetry(() => (opt.tx?.tx || ds).get(keys, dsOpt), {
|
|
102
112
|
...this.getPRetryOptions(`datastore.getByIds(${table}) second try`),
|
|
103
113
|
maxAttempts: 3,
|
|
104
114
|
timeout: this.cfg.timeout,
|
|
@@ -106,8 +116,8 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
106
116
|
rows = r[0];
|
|
107
117
|
}
|
|
108
118
|
catch (err) {
|
|
109
|
-
if (err instanceof
|
|
110
|
-
|
|
119
|
+
if (err instanceof TimeoutError) {
|
|
120
|
+
_errorDataAppend(err, {
|
|
111
121
|
fingerprint: [DATASTORE_TIMEOUT],
|
|
112
122
|
});
|
|
113
123
|
}
|
|
@@ -116,8 +126,8 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
116
126
|
}
|
|
117
127
|
}
|
|
118
128
|
else {
|
|
119
|
-
rows = await
|
|
120
|
-
return (await
|
|
129
|
+
rows = await pRetry(async () => {
|
|
130
|
+
return (await ds.get(keys, dsOpt))[0];
|
|
121
131
|
}, this.getPRetryOptions(`datastore.getByIds(${table})`));
|
|
122
132
|
}
|
|
123
133
|
return (rows
|
|
@@ -138,69 +148,80 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
138
148
|
rows: await this.getByIds(dbQuery.table, ids, opt),
|
|
139
149
|
};
|
|
140
150
|
}
|
|
141
|
-
const
|
|
151
|
+
const ds = await this.ds();
|
|
152
|
+
const q = dbQueryToDatastoreQuery(dbQuery, ds.createQuery(dbQuery.table), await this.getPropertyFilter());
|
|
142
153
|
const dsOpt = this.getRunQueryOptions(opt);
|
|
143
154
|
const qr = await this.runDatastoreQuery(q, dsOpt);
|
|
144
155
|
// Special case when projection query didn't specify 'id'
|
|
145
156
|
if (dbQuery._selectedFieldNames && !dbQuery._selectedFieldNames.includes('id')) {
|
|
146
|
-
qr.rows = qr.rows.map(r =>
|
|
157
|
+
qr.rows = qr.rows.map(r => _omit(r, ['id']));
|
|
147
158
|
}
|
|
148
159
|
return qr;
|
|
149
160
|
}
|
|
150
161
|
async runQueryCount(dbQuery, opt = {}) {
|
|
151
|
-
const
|
|
152
|
-
const
|
|
162
|
+
const ds = await this.ds();
|
|
163
|
+
const q = dbQueryToDatastoreQuery(dbQuery.select([]), ds.createQuery(dbQuery.table), await this.getPropertyFilter());
|
|
164
|
+
const aq = ds.createAggregationQuery(q).count('count');
|
|
153
165
|
const dsOpt = this.getRunQueryOptions(opt);
|
|
154
|
-
const [entities] = await
|
|
166
|
+
const [entities] = await ds.runAggregationQuery(aq, dsOpt);
|
|
155
167
|
return entities[0]?.count;
|
|
156
168
|
}
|
|
157
169
|
async runDatastoreQuery(q, dsOpt) {
|
|
158
|
-
const
|
|
170
|
+
const ds = await this.ds();
|
|
171
|
+
const [entities, queryResult] = await ds.runQuery(q, dsOpt);
|
|
159
172
|
const rows = entities.map(e => this.mapId(e));
|
|
160
173
|
return {
|
|
161
174
|
...queryResult,
|
|
162
175
|
rows,
|
|
163
176
|
};
|
|
164
177
|
}
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
+
streamQuery(dbQuery, _opt) {
|
|
179
|
+
const transform = new Transform({
|
|
180
|
+
objectMode: true,
|
|
181
|
+
transform: (chunk, _, cb) => {
|
|
182
|
+
cb(null, this.mapId(chunk));
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
void this.ds().then(async (ds) => {
|
|
186
|
+
const q = dbQueryToDatastoreQuery(dbQuery, ds.createQuery(dbQuery.table), await this.getPropertyFilter());
|
|
187
|
+
const opt = {
|
|
188
|
+
...this.cfg.streamOptions,
|
|
189
|
+
..._opt,
|
|
190
|
+
};
|
|
191
|
+
(opt.experimentalCursorStream
|
|
192
|
+
? new DatastoreStreamReadable(q, opt, commonLoggerMinLevel(this.cfg.logger, opt.debug ? 'log' : 'warn'))
|
|
193
|
+
: ds.runQueryStream(q, this.getRunQueryOptions(opt)))
|
|
194
|
+
.on('error', err => transform.emit('error', err))
|
|
195
|
+
.pipe(transform);
|
|
196
|
+
});
|
|
197
|
+
return transform;
|
|
178
198
|
}
|
|
179
199
|
// https://github.com/GoogleCloudPlatform/nodejs-getting-started/blob/master/2-structured-data/books/model-datastore.js
|
|
180
200
|
/**
|
|
181
201
|
* Returns saved entities with generated id/updated/created (non-mutating!)
|
|
182
202
|
*/
|
|
183
203
|
async saveBatch(table, rows, opt = {}) {
|
|
184
|
-
const
|
|
204
|
+
const ds = await this.ds();
|
|
205
|
+
const entities = rows.map(obj => this.toDatastoreEntity(ds, table, obj, opt.excludeFromIndexes));
|
|
185
206
|
const method = methodMap[opt.saveMethod || 'upsert'] || 'save';
|
|
186
|
-
const save =
|
|
187
|
-
await (opt.tx?.tx ||
|
|
207
|
+
const save = pRetryFn(async (batch) => {
|
|
208
|
+
await (opt.tx?.tx || ds)[method](batch);
|
|
188
209
|
}, this.getPRetryOptions(`DatastoreLib.saveBatch(${table})`));
|
|
189
210
|
try {
|
|
190
|
-
const chunks =
|
|
211
|
+
const chunks = _chunk(entities, MAX_ITEMS);
|
|
191
212
|
if (chunks.length === 1) {
|
|
192
213
|
// Not using pMap in hope to preserve stack trace
|
|
193
214
|
await save(chunks[0]);
|
|
194
215
|
}
|
|
195
216
|
else {
|
|
196
|
-
await
|
|
217
|
+
await pMap(chunks, async (batch) => await save(batch), {
|
|
197
218
|
concurrency: DATASTORE_RECOMMENDED_CONCURRENCY,
|
|
198
219
|
});
|
|
199
220
|
}
|
|
200
221
|
}
|
|
201
222
|
catch (err) {
|
|
202
|
-
if (err instanceof
|
|
203
|
-
|
|
223
|
+
if (err instanceof TimeoutError) {
|
|
224
|
+
_errorDataAppend(err, {
|
|
204
225
|
fingerprint: [DATASTORE_TIMEOUT],
|
|
205
226
|
});
|
|
206
227
|
}
|
|
@@ -215,7 +236,8 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
215
236
|
const ids = idFilter.op === '==' ? [idFilter.val] : idFilter.val;
|
|
216
237
|
return await this.deleteByIds(q.table, ids, opt);
|
|
217
238
|
}
|
|
218
|
-
const
|
|
239
|
+
const ds = await this.ds();
|
|
240
|
+
const datastoreQuery = dbQueryToDatastoreQuery(q.select([]), ds.createQuery(q.table), await this.getPropertyFilter());
|
|
219
241
|
const dsOpt = this.getRunQueryOptions(opt);
|
|
220
242
|
const { rows } = await this.runDatastoreQuery(datastoreQuery, dsOpt);
|
|
221
243
|
return await this.deleteByIds(q.table, rows.map(obj => obj.id), opt);
|
|
@@ -225,15 +247,17 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
225
247
|
* regardless if they were actually deleted or not.
|
|
226
248
|
*/
|
|
227
249
|
async deleteByIds(table, ids, opt = {}) {
|
|
228
|
-
const
|
|
229
|
-
|
|
250
|
+
const ds = await this.ds();
|
|
251
|
+
const keys = ids.map(id => this.key(ds, table, id));
|
|
252
|
+
await pMap(_chunk(keys, MAX_ITEMS), async (batch) => await (opt.tx?.tx || ds).delete(batch), {
|
|
230
253
|
concurrency: DATASTORE_RECOMMENDED_CONCURRENCY,
|
|
231
254
|
});
|
|
232
255
|
return ids.length;
|
|
233
256
|
}
|
|
234
257
|
async runInTransaction(fn, opt = {}) {
|
|
258
|
+
const ds = await this.ds();
|
|
235
259
|
const { readOnly } = opt;
|
|
236
|
-
const datastoreTx =
|
|
260
|
+
const datastoreTx = ds.transaction({
|
|
237
261
|
readOnly,
|
|
238
262
|
});
|
|
239
263
|
try {
|
|
@@ -248,20 +272,23 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
248
272
|
}
|
|
249
273
|
}
|
|
250
274
|
async getAllStats() {
|
|
251
|
-
const
|
|
252
|
-
const
|
|
275
|
+
const ds = await this.ds();
|
|
276
|
+
const q = ds.createQuery('__Stat_Kind__');
|
|
277
|
+
const [statsArray] = await ds.runQuery(q);
|
|
253
278
|
return statsArray || [];
|
|
254
279
|
}
|
|
255
280
|
/**
|
|
256
281
|
* Returns undefined e.g when Table is non-existing
|
|
257
282
|
*/
|
|
258
283
|
async getStats(table) {
|
|
259
|
-
const
|
|
284
|
+
const ds = await this.ds();
|
|
285
|
+
const propertyFilter = await this.getPropertyFilter();
|
|
286
|
+
const q = ds
|
|
260
287
|
.createQuery('__Stat_Kind__')
|
|
261
288
|
// .filter('kind_name', table)
|
|
262
|
-
.filter(new
|
|
289
|
+
.filter(new propertyFilter('kind_name', '=', table))
|
|
263
290
|
.limit(1);
|
|
264
|
-
const [statsArray] = await
|
|
291
|
+
const [statsArray] = await ds.runQuery(q);
|
|
265
292
|
const [stats] = statsArray;
|
|
266
293
|
return stats;
|
|
267
294
|
}
|
|
@@ -270,11 +297,12 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
270
297
|
return stats?.count;
|
|
271
298
|
}
|
|
272
299
|
async getTableProperties(table) {
|
|
273
|
-
const
|
|
300
|
+
const ds = await this.ds();
|
|
301
|
+
const q = ds
|
|
274
302
|
.createQuery('__Stat_PropertyType_PropertyName_Kind__')
|
|
275
303
|
// .filter('kind_name', table)
|
|
276
|
-
.filter(new
|
|
277
|
-
const [stats] = await
|
|
304
|
+
.filter(new (await this.getPropertyFilter())('kind_name', '=', table));
|
|
305
|
+
const [stats] = await ds.runQuery(q);
|
|
278
306
|
return stats;
|
|
279
307
|
}
|
|
280
308
|
mapId(o, preserveKey = false) {
|
|
@@ -289,8 +317,8 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
289
317
|
return r;
|
|
290
318
|
}
|
|
291
319
|
// if key field exists on entity, it will be used as key (prevent to duplication of numeric keyed entities)
|
|
292
|
-
toDatastoreEntity(kind, o, excludeFromIndexes = []) {
|
|
293
|
-
const key = this.getDsKey(o) || this.key(kind, o.id);
|
|
320
|
+
toDatastoreEntity(ds, kind, o, excludeFromIndexes = []) {
|
|
321
|
+
const key = this.getDsKey(o) || this.key(ds, kind, o.id);
|
|
294
322
|
const data = Object.assign({}, o);
|
|
295
323
|
delete data.id;
|
|
296
324
|
delete data[this.KEY];
|
|
@@ -300,9 +328,9 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
300
328
|
excludeFromIndexes,
|
|
301
329
|
};
|
|
302
330
|
}
|
|
303
|
-
key(kind, id) {
|
|
304
|
-
|
|
305
|
-
return
|
|
331
|
+
key(ds, kind, id) {
|
|
332
|
+
_assert(id, `Cannot save "${kind}" entity without "id"`);
|
|
333
|
+
return ds.key([kind, id]);
|
|
306
334
|
}
|
|
307
335
|
getDsKey(o) {
|
|
308
336
|
return o?.[this.KEY];
|
|
@@ -333,17 +361,17 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
333
361
|
.forEach(stats => {
|
|
334
362
|
const { property_type: dtype } = stats;
|
|
335
363
|
const name = stats.property_name;
|
|
336
|
-
if (dtype ===
|
|
364
|
+
if (dtype === DatastoreType.Blob) {
|
|
337
365
|
s.properties[name] = {
|
|
338
366
|
instanceof: 'Buffer',
|
|
339
367
|
};
|
|
340
368
|
}
|
|
341
|
-
else if (dtype ===
|
|
369
|
+
else if (dtype === DatastoreType.Text || dtype === DatastoreType.String) {
|
|
342
370
|
s.properties[name] = {
|
|
343
371
|
type: 'string',
|
|
344
372
|
};
|
|
345
373
|
}
|
|
346
|
-
else if (dtype ===
|
|
374
|
+
else if (dtype === DatastoreType.EmbeddedEntity) {
|
|
347
375
|
s.properties[name] = {
|
|
348
376
|
type: 'object',
|
|
349
377
|
additionalProperties: true,
|
|
@@ -351,26 +379,26 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
351
379
|
required: [],
|
|
352
380
|
};
|
|
353
381
|
}
|
|
354
|
-
else if (dtype ===
|
|
382
|
+
else if (dtype === DatastoreType.Integer) {
|
|
355
383
|
s.properties[name] = {
|
|
356
384
|
type: 'integer',
|
|
357
385
|
};
|
|
358
386
|
}
|
|
359
|
-
else if (dtype ===
|
|
387
|
+
else if (dtype === DatastoreType.Float) {
|
|
360
388
|
s.properties[name] = {
|
|
361
389
|
type: 'number',
|
|
362
390
|
};
|
|
363
391
|
}
|
|
364
|
-
else if (dtype ===
|
|
392
|
+
else if (dtype === DatastoreType.Boolean) {
|
|
365
393
|
s.properties[name] = {
|
|
366
394
|
type: 'boolean',
|
|
367
395
|
};
|
|
368
396
|
}
|
|
369
|
-
else if (dtype ===
|
|
397
|
+
else if (dtype === DatastoreType.DATE_TIME) {
|
|
370
398
|
// Don't know how to map it properly
|
|
371
399
|
s.properties[name] = {};
|
|
372
400
|
}
|
|
373
|
-
else if (dtype ===
|
|
401
|
+
else if (dtype === DatastoreType.NULL) {
|
|
374
402
|
// check, maybe we can just skip this type and do nothing?
|
|
375
403
|
s.properties[name] ||= {
|
|
376
404
|
type: 'null',
|
|
@@ -422,11 +450,12 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
422
450
|
};
|
|
423
451
|
}
|
|
424
452
|
}
|
|
425
|
-
exports.DatastoreDB = DatastoreDB;
|
|
426
453
|
/**
|
|
427
454
|
* https://cloud.google.com/datastore/docs/concepts/transactions#datastore-datastore-transactional-update-nodejs
|
|
428
455
|
*/
|
|
429
|
-
class DatastoreDBTransaction {
|
|
456
|
+
export class DatastoreDBTransaction {
|
|
457
|
+
db;
|
|
458
|
+
tx;
|
|
430
459
|
constructor(db, tx) {
|
|
431
460
|
this.db = db;
|
|
432
461
|
this.tx = tx;
|
|
@@ -450,4 +479,3 @@ class DatastoreDBTransaction {
|
|
|
450
479
|
return await this.db.deleteByIds(table, ids, { ...opt, tx: this });
|
|
451
480
|
}
|
|
452
481
|
}
|
|
453
|
-
exports.DatastoreDBTransaction = DatastoreDBTransaction;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DatastoreOptions, Key } from '@google-cloud/datastore';
|
|
2
|
-
import { CommonDBOptions, CommonDBReadOptions, CommonDBSaveOptions } from '@naturalcycles/db-lib';
|
|
3
|
-
import { CommonLogger, NumberOfSeconds, ObjectWithId } from '@naturalcycles/js-lib';
|
|
2
|
+
import type { CommonDBOptions, CommonDBReadOptions, CommonDBSaveOptions } from '@naturalcycles/db-lib';
|
|
3
|
+
import type { CommonLogger, NumberOfSeconds, ObjectWithId } from '@naturalcycles/js-lib';
|
|
4
4
|
export interface DatastorePayload<T = any> {
|
|
5
5
|
key: Key;
|
|
6
6
|
data: T;
|
package/dist/datastore.model.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DatastoreType = void 0;
|
|
4
|
-
var DatastoreType;
|
|
1
|
+
export var DatastoreType;
|
|
5
2
|
(function (DatastoreType) {
|
|
6
3
|
DatastoreType["Blob"] = "Blob";
|
|
7
4
|
DatastoreType["Text"] = "Text";
|
|
@@ -12,4 +9,4 @@ var DatastoreType;
|
|
|
12
9
|
DatastoreType["DATE_TIME"] = "Date/Time";
|
|
13
10
|
DatastoreType["Boolean"] = "Boolean";
|
|
14
11
|
DatastoreType["NULL"] = "NULL";
|
|
15
|
-
})(DatastoreType || (
|
|
12
|
+
})(DatastoreType || (DatastoreType = {}));
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { CommonKeyValueDB, KeyValueDBTuple } from '@naturalcycles/db-lib';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { DatastoreDBCfg } from './datastore.model';
|
|
1
|
+
import type { CommonKeyValueDB, IncrementTuple, KeyValueDBTuple } from '@naturalcycles/db-lib';
|
|
2
|
+
import type { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
3
|
+
import { DatastoreDB } from './datastore.db.js';
|
|
4
|
+
import type { DatastoreDBCfg } from './datastore.model.js';
|
|
6
5
|
export interface DatastoreKeyValueDBCfg extends DatastoreDBCfg {
|
|
7
6
|
}
|
|
8
7
|
export declare class DatastoreKeyValueDB implements CommonKeyValueDB {
|
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const db_lib_1 = require("@naturalcycles/db-lib");
|
|
5
|
-
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
6
|
-
const datastore_db_1 = require("./datastore.db");
|
|
1
|
+
import { commonKeyValueDBFullSupport, DBQuery } from '@naturalcycles/db-lib';
|
|
2
|
+
import { AppError } from '@naturalcycles/js-lib';
|
|
3
|
+
import { DatastoreDB } from './datastore.db.js';
|
|
7
4
|
const excludeFromIndexes = ['v'];
|
|
8
|
-
class DatastoreKeyValueDB {
|
|
5
|
+
export class DatastoreKeyValueDB {
|
|
6
|
+
cfg;
|
|
9
7
|
constructor(cfg) {
|
|
10
8
|
this.cfg = cfg;
|
|
11
|
-
this.
|
|
12
|
-
...db_lib_1.commonKeyValueDBFullSupport,
|
|
13
|
-
increment: false,
|
|
14
|
-
};
|
|
15
|
-
this.db = new datastore_db_1.DatastoreDB(cfg);
|
|
9
|
+
this.db = new DatastoreDB(cfg);
|
|
16
10
|
}
|
|
11
|
+
db;
|
|
12
|
+
support = {
|
|
13
|
+
...commonKeyValueDBFullSupport,
|
|
14
|
+
increment: false,
|
|
15
|
+
};
|
|
17
16
|
async ping() {
|
|
18
17
|
await this.db.ping();
|
|
19
18
|
}
|
|
@@ -30,7 +29,7 @@ class DatastoreKeyValueDB {
|
|
|
30
29
|
});
|
|
31
30
|
}
|
|
32
31
|
streamIds(table, limit) {
|
|
33
|
-
const q =
|
|
32
|
+
const q = DBQuery.create(table)
|
|
34
33
|
.select(['id'])
|
|
35
34
|
.limit(limit || 0);
|
|
36
35
|
return (this.db
|
|
@@ -40,25 +39,24 @@ class DatastoreKeyValueDB {
|
|
|
40
39
|
}
|
|
41
40
|
streamValues(table, limit) {
|
|
42
41
|
// `select v` doesn't work for some reason
|
|
43
|
-
const q =
|
|
42
|
+
const q = DBQuery.create(table).limit(limit || 0);
|
|
44
43
|
return (this.db
|
|
45
44
|
.streamQuery(q)
|
|
46
45
|
// .on('error', err => stream.emit('error', err))
|
|
47
46
|
.map(r => r.v));
|
|
48
47
|
}
|
|
49
48
|
streamEntries(table, limit) {
|
|
50
|
-
const q =
|
|
49
|
+
const q = DBQuery.create(table).limit(limit || 0);
|
|
51
50
|
return (this.db
|
|
52
51
|
.streamQuery(q)
|
|
53
52
|
// .on('error', err => stream.emit('error', err))
|
|
54
53
|
.map(r => [r.id, r.v]));
|
|
55
54
|
}
|
|
56
55
|
async count(table) {
|
|
57
|
-
const q =
|
|
56
|
+
const q = DBQuery.create(table);
|
|
58
57
|
return await this.db.runQueryCount(q);
|
|
59
58
|
}
|
|
60
59
|
async incrementBatch(_table, _entries) {
|
|
61
|
-
throw new
|
|
60
|
+
throw new AppError('DatastoreKeyValueDB.incrementBatch() is not implemented');
|
|
62
61
|
}
|
|
63
62
|
}
|
|
64
|
-
exports.DatastoreKeyValueDB = DatastoreKeyValueDB;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from './datastore.db';
|
|
2
|
-
export * from './datastore.model';
|
|
3
|
-
export * from './datastoreKeyValueDB';
|
|
1
|
+
export * from './datastore.db.js';
|
|
2
|
+
export * from './datastore.model.js';
|
|
3
|
+
export * from './datastoreKeyValueDB.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
tslib_1.__exportStar(require("./datastore.db"), exports);
|
|
5
|
-
tslib_1.__exportStar(require("./datastore.model"), exports);
|
|
6
|
-
tslib_1.__exportStar(require("./datastoreKeyValueDB"), exports);
|
|
1
|
+
export * from './datastore.db.js';
|
|
2
|
+
export * from './datastore.model.js';
|
|
3
|
+
export * from './datastoreKeyValueDB.js';
|
package/dist/query.util.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Query } from '@google-cloud/datastore';
|
|
2
|
-
import { DBQuery } from '@naturalcycles/db-lib';
|
|
3
|
-
import { ObjectWithId } from '@naturalcycles/js-lib';
|
|
4
|
-
export declare function dbQueryToDatastoreQuery<ROW extends ObjectWithId>(dbQuery: Readonly<DBQuery<ROW>>, emptyQuery: Query): Query;
|
|
1
|
+
import type { PropertyFilter, Query } from '@google-cloud/datastore';
|
|
2
|
+
import type { DBQuery } from '@naturalcycles/db-lib';
|
|
3
|
+
import type { ObjectWithId } from '@naturalcycles/js-lib';
|
|
4
|
+
export declare function dbQueryToDatastoreQuery<ROW extends ObjectWithId>(dbQuery: Readonly<DBQuery<ROW>>, emptyQuery: Query, propertyFilterClass: typeof PropertyFilter): Query;
|
package/dist/query.util.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.dbQueryToDatastoreQuery = dbQueryToDatastoreQuery;
|
|
4
|
-
const datastore_1 = require("@google-cloud/datastore");
|
|
5
1
|
const FNAME_MAP = {
|
|
6
2
|
id: '__key__',
|
|
7
3
|
};
|
|
@@ -10,7 +6,7 @@ const OP_MAP = {
|
|
|
10
6
|
in: 'IN',
|
|
11
7
|
'not-in': 'NOT_IN',
|
|
12
8
|
};
|
|
13
|
-
function dbQueryToDatastoreQuery(dbQuery, emptyQuery) {
|
|
9
|
+
export function dbQueryToDatastoreQuery(dbQuery, emptyQuery, propertyFilterClass) {
|
|
14
10
|
let q = emptyQuery;
|
|
15
11
|
// filter
|
|
16
12
|
for (const f of dbQuery._filters) {
|
|
@@ -24,7 +20,7 @@ function dbQueryToDatastoreQuery(dbQuery, emptyQuery) {
|
|
|
24
20
|
let { op, val } = f;
|
|
25
21
|
if (val === undefined)
|
|
26
22
|
val = null;
|
|
27
|
-
q = q.filter(new
|
|
23
|
+
q = q.filter(new propertyFilterClass(f.name, OP_MAP[op] || op, val));
|
|
28
24
|
}
|
|
29
25
|
// limit
|
|
30
26
|
q = q.limit(dbQuery._limitValue || 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/datastore-lib",
|
|
3
|
-
"
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "4.0.0",
|
|
4
5
|
"description": "Opinionated library to work with Google Datastore",
|
|
5
6
|
"scripts": {
|
|
6
7
|
"prepare": "husky",
|
|
@@ -12,14 +13,16 @@
|
|
|
12
13
|
},
|
|
13
14
|
"dependencies": {
|
|
14
15
|
"@google-cloud/datastore": "^9",
|
|
15
|
-
"@naturalcycles/db-lib": "^
|
|
16
|
+
"@naturalcycles/db-lib": "^10",
|
|
16
17
|
"@naturalcycles/js-lib": "^14",
|
|
17
18
|
"@naturalcycles/nodejs-lib": "^13"
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
20
|
-
"@naturalcycles/dev-lib": "^
|
|
21
|
+
"@naturalcycles/dev-lib": "^17",
|
|
21
22
|
"@types/node": "^22",
|
|
22
|
-
"
|
|
23
|
+
"@vitest/coverage-v8": "^3",
|
|
24
|
+
"tsx": "^4.19.3",
|
|
25
|
+
"vitest": "^3"
|
|
23
26
|
},
|
|
24
27
|
"files": [
|
|
25
28
|
"dist",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Readable } from 'node:stream'
|
|
2
|
-
import { Query } from '@google-cloud/datastore'
|
|
3
|
-
import type { RunQueryInfo, RunQueryOptions } from '@google-cloud/datastore/build/src/query'
|
|
4
|
-
import {
|
|
2
|
+
import type { Query } from '@google-cloud/datastore'
|
|
3
|
+
import type { RunQueryInfo, RunQueryOptions } from '@google-cloud/datastore/build/src/query.js'
|
|
4
|
+
import type { CommonLogger, UnixTimestampMillis } from '@naturalcycles/js-lib'
|
|
5
|
+
import { _ms, pRetry } from '@naturalcycles/js-lib'
|
|
5
6
|
import type { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
6
|
-
import type { DatastoreDBStreamOptions } from './datastore.model'
|
|
7
|
+
import type { DatastoreDBStreamOptions } from './datastore.model.js'
|
|
7
8
|
|
|
8
9
|
export class DatastoreStreamReadable<T = any> extends Readable implements ReadableTyped<T> {
|
|
9
10
|
private readonly originalLimit: number
|
package/src/datastore.db.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { PropertyFilter, Transaction } from '@google-cloud/datastore'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
BaseCommonDB,
|
|
1
|
+
import { Transform } from 'node:stream'
|
|
2
|
+
import type { Datastore, Key, PropertyFilter, Query, Transaction } from '@google-cloud/datastore'
|
|
3
|
+
import type { RunQueryOptions } from '@google-cloud/datastore/build/src/query.js'
|
|
4
|
+
import type {
|
|
6
5
|
CommonDB,
|
|
7
|
-
commonDBFullSupport,
|
|
8
6
|
CommonDBOptions,
|
|
9
7
|
CommonDBReadOptions,
|
|
10
8
|
CommonDBSaveMethod,
|
|
@@ -16,13 +14,9 @@ import {
|
|
|
16
14
|
DBTransactionFn,
|
|
17
15
|
RunQueryResult,
|
|
18
16
|
} from '@naturalcycles/db-lib'
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
_chunk,
|
|
22
|
-
_errorDataAppend,
|
|
23
|
-
_omit,
|
|
17
|
+
import { BaseCommonDB, commonDBFullSupport } from '@naturalcycles/db-lib'
|
|
18
|
+
import type {
|
|
24
19
|
CommonLogger,
|
|
25
|
-
commonLoggerMinLevel,
|
|
26
20
|
JsonSchemaAny,
|
|
27
21
|
JsonSchemaBoolean,
|
|
28
22
|
JsonSchemaNull,
|
|
@@ -31,15 +25,23 @@ import {
|
|
|
31
25
|
JsonSchemaRootObject,
|
|
32
26
|
JsonSchemaString,
|
|
33
27
|
ObjectWithId,
|
|
28
|
+
PRetryOptions,
|
|
29
|
+
} from '@naturalcycles/js-lib'
|
|
30
|
+
import {
|
|
31
|
+
_assert,
|
|
32
|
+
_chunk,
|
|
33
|
+
_errorDataAppend,
|
|
34
|
+
_omit,
|
|
35
|
+
commonLoggerMinLevel,
|
|
34
36
|
pMap,
|
|
35
37
|
pRetry,
|
|
36
38
|
pRetryFn,
|
|
37
|
-
PRetryOptions,
|
|
38
39
|
pTimeout,
|
|
39
40
|
TimeoutError,
|
|
40
41
|
} from '@naturalcycles/js-lib'
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
42
|
+
import type { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
43
|
+
import { boldWhite } from '@naturalcycles/nodejs-lib'
|
|
44
|
+
import type {
|
|
43
45
|
DatastoreDBCfg,
|
|
44
46
|
DatastoreDBOptions,
|
|
45
47
|
DatastoreDBReadOptions,
|
|
@@ -48,10 +50,10 @@ import {
|
|
|
48
50
|
DatastorePayload,
|
|
49
51
|
DatastorePropertyStats,
|
|
50
52
|
DatastoreStats,
|
|
51
|
-
|
|
52
|
-
} from './datastore.model'
|
|
53
|
-
import { DatastoreStreamReadable } from './DatastoreStreamReadable'
|
|
54
|
-
import { dbQueryToDatastoreQuery } from './query.util'
|
|
53
|
+
} from './datastore.model.js'
|
|
54
|
+
import { DatastoreType } from './datastore.model.js'
|
|
55
|
+
import { DatastoreStreamReadable } from './DatastoreStreamReadable.js'
|
|
56
|
+
import { dbQueryToDatastoreQuery } from './query.util.js'
|
|
55
57
|
|
|
56
58
|
// Datastore (also Firestore and other Google APIs) supports max 500 of items when saving/deleting, etc.
|
|
57
59
|
const MAX_ITEMS = 500
|
|
@@ -107,16 +109,14 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
107
109
|
protected KEY!: symbol
|
|
108
110
|
|
|
109
111
|
// @memo() // not used to be able to connect to many DBs in the same server instance
|
|
110
|
-
ds(): Datastore {
|
|
112
|
+
async ds(): Promise<Datastore> {
|
|
111
113
|
if (!this.cachedDatastore) {
|
|
112
114
|
_assert(
|
|
113
115
|
process.env['APP_ENV'] !== 'test',
|
|
114
116
|
'DatastoreDB cannot be used in Test env, please use InMemoryDB',
|
|
115
117
|
)
|
|
116
118
|
|
|
117
|
-
|
|
118
|
-
const datastoreLib = require('@google-cloud/datastore')
|
|
119
|
-
const DS = datastoreLib.Datastore as typeof Datastore
|
|
119
|
+
const DS = (await this.getDatastoreLib()).Datastore as typeof Datastore
|
|
120
120
|
this.cfg.projectId ||= this.cfg.credentials?.project_id || process.env['GOOGLE_CLOUD_PROJECT']
|
|
121
121
|
|
|
122
122
|
if (this.cfg.projectId) {
|
|
@@ -136,6 +136,16 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
136
136
|
return this.cachedDatastore
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
private async getPropertyFilter(): Promise<typeof PropertyFilter> {
|
|
140
|
+
return (await this.getDatastoreLib()).PropertyFilter
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private async getDatastoreLib(): Promise<any> {
|
|
144
|
+
// Lazy-loading
|
|
145
|
+
const { default: lib } = await import('@google-cloud/datastore')
|
|
146
|
+
return lib
|
|
147
|
+
}
|
|
148
|
+
|
|
139
149
|
override async ping(): Promise<void> {
|
|
140
150
|
await this.getAllStats()
|
|
141
151
|
}
|
|
@@ -146,7 +156,8 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
146
156
|
opt: DatastoreDBReadOptions = {},
|
|
147
157
|
): Promise<ROW[]> {
|
|
148
158
|
if (!ids.length) return []
|
|
149
|
-
|
|
159
|
+
let ds = await this.ds()
|
|
160
|
+
const keys = ids.map(id => this.key(ds, table, id))
|
|
150
161
|
let rows: any[]
|
|
151
162
|
|
|
152
163
|
const dsOpt = this.getRunQueryOptions(opt)
|
|
@@ -155,7 +166,7 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
155
166
|
// First try
|
|
156
167
|
try {
|
|
157
168
|
const r = await pTimeout(
|
|
158
|
-
() => ((opt.tx as DatastoreDBTransaction)?.tx ||
|
|
169
|
+
() => ((opt.tx as DatastoreDBTransaction)?.tx || ds).get(keys, dsOpt),
|
|
159
170
|
{
|
|
160
171
|
timeout: this.cfg.timeout,
|
|
161
172
|
name: `datastore.getByIds(${table})`,
|
|
@@ -171,14 +182,14 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
171
182
|
this.cfg.logger.log('datastore recreated on error')
|
|
172
183
|
|
|
173
184
|
// This is to debug "GCP Datastore Timeout issue"
|
|
174
|
-
const datastoreLib =
|
|
185
|
+
const datastoreLib = await this.getDatastoreLib()
|
|
175
186
|
const DS = datastoreLib.Datastore as typeof Datastore
|
|
176
|
-
this.cachedDatastore = new DS(this.cfg)
|
|
187
|
+
ds = this.cachedDatastore = new DS(this.cfg)
|
|
177
188
|
|
|
178
189
|
// Second try (will throw)
|
|
179
190
|
try {
|
|
180
191
|
const r = await pRetry(
|
|
181
|
-
() => ((opt.tx as DatastoreDBTransaction)?.tx ||
|
|
192
|
+
() => ((opt.tx as DatastoreDBTransaction)?.tx || ds).get(keys, dsOpt),
|
|
182
193
|
{
|
|
183
194
|
...this.getPRetryOptions(`datastore.getByIds(${table}) second try`),
|
|
184
195
|
maxAttempts: 3,
|
|
@@ -198,7 +209,7 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
198
209
|
} else {
|
|
199
210
|
rows = await pRetry(
|
|
200
211
|
async () => {
|
|
201
|
-
return (await
|
|
212
|
+
return (await ds.get(keys, dsOpt))[0]
|
|
202
213
|
},
|
|
203
214
|
this.getPRetryOptions(`datastore.getByIds(${table})`),
|
|
204
215
|
)
|
|
@@ -231,7 +242,12 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
231
242
|
}
|
|
232
243
|
}
|
|
233
244
|
|
|
234
|
-
const
|
|
245
|
+
const ds = await this.ds()
|
|
246
|
+
const q = dbQueryToDatastoreQuery(
|
|
247
|
+
dbQuery,
|
|
248
|
+
ds.createQuery(dbQuery.table),
|
|
249
|
+
await this.getPropertyFilter(),
|
|
250
|
+
)
|
|
235
251
|
const dsOpt = this.getRunQueryOptions(opt)
|
|
236
252
|
const qr = await this.runDatastoreQuery<ROW>(q, dsOpt)
|
|
237
253
|
|
|
@@ -247,10 +263,15 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
247
263
|
dbQuery: DBQuery<ROW>,
|
|
248
264
|
opt: DatastoreDBReadOptions = {},
|
|
249
265
|
): Promise<number> {
|
|
250
|
-
const
|
|
251
|
-
const
|
|
266
|
+
const ds = await this.ds()
|
|
267
|
+
const q = dbQueryToDatastoreQuery(
|
|
268
|
+
dbQuery.select([]),
|
|
269
|
+
ds.createQuery(dbQuery.table),
|
|
270
|
+
await this.getPropertyFilter(),
|
|
271
|
+
)
|
|
272
|
+
const aq = ds.createAggregationQuery(q).count('count')
|
|
252
273
|
const dsOpt = this.getRunQueryOptions(opt)
|
|
253
|
-
const [entities] = await
|
|
274
|
+
const [entities] = await ds.runAggregationQuery(aq, dsOpt)
|
|
254
275
|
return entities[0]?.count
|
|
255
276
|
}
|
|
256
277
|
|
|
@@ -258,7 +279,8 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
258
279
|
q: Query,
|
|
259
280
|
dsOpt: RunQueryOptions,
|
|
260
281
|
): Promise<RunQueryResult<ROW>> {
|
|
261
|
-
const
|
|
282
|
+
const ds = await this.ds()
|
|
283
|
+
const [entities, queryResult] = await ds.runQuery(q, dsOpt)
|
|
262
284
|
|
|
263
285
|
const rows = entities.map(e => this.mapId<ROW>(e))
|
|
264
286
|
|
|
@@ -268,33 +290,42 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
268
290
|
}
|
|
269
291
|
}
|
|
270
292
|
|
|
271
|
-
|
|
272
|
-
|
|
293
|
+
override streamQuery<ROW extends ObjectWithId>(
|
|
294
|
+
dbQuery: DBQuery<ROW>,
|
|
273
295
|
_opt?: DatastoreDBStreamOptions,
|
|
274
296
|
): ReadableTyped<ROW> {
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
297
|
+
const transform = new Transform({
|
|
298
|
+
objectMode: true,
|
|
299
|
+
transform: (chunk, _, cb) => {
|
|
300
|
+
cb(null, this.mapId(chunk))
|
|
301
|
+
},
|
|
302
|
+
})
|
|
280
303
|
|
|
281
|
-
|
|
282
|
-
|
|
304
|
+
void this.ds().then(async ds => {
|
|
305
|
+
const q = dbQueryToDatastoreQuery(
|
|
306
|
+
dbQuery,
|
|
307
|
+
ds.createQuery(dbQuery.table),
|
|
308
|
+
await this.getPropertyFilter(),
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
const opt = {
|
|
312
|
+
...this.cfg.streamOptions,
|
|
313
|
+
..._opt,
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
;(opt.experimentalCursorStream
|
|
283
317
|
? new DatastoreStreamReadable<ROW>(
|
|
284
318
|
q,
|
|
285
319
|
opt,
|
|
286
320
|
commonLoggerMinLevel(this.cfg.logger, opt.debug ? 'log' : 'warn'),
|
|
287
321
|
)
|
|
288
|
-
: (
|
|
289
|
-
|
|
290
|
-
|
|
322
|
+
: (ds.runQueryStream(q, this.getRunQueryOptions(opt)) as ReadableTyped<ROW>)
|
|
323
|
+
)
|
|
324
|
+
.on('error', err => transform.emit('error', err))
|
|
325
|
+
.pipe(transform)
|
|
326
|
+
})
|
|
291
327
|
|
|
292
|
-
|
|
293
|
-
dbQuery: DBQuery<ROW>,
|
|
294
|
-
opt?: DatastoreDBStreamOptions,
|
|
295
|
-
): ReadableTyped<ROW> {
|
|
296
|
-
const q = dbQueryToDatastoreQuery(dbQuery, this.ds().createQuery(dbQuery.table))
|
|
297
|
-
return this.runQueryStream(q, opt)
|
|
328
|
+
return transform
|
|
298
329
|
}
|
|
299
330
|
|
|
300
331
|
// https://github.com/GoogleCloudPlatform/nodejs-getting-started/blob/master/2-structured-data/books/model-datastore.js
|
|
@@ -307,15 +338,16 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
307
338
|
rows: ROW[],
|
|
308
339
|
opt: DatastoreDBSaveOptions<ROW> = {},
|
|
309
340
|
): Promise<void> {
|
|
341
|
+
const ds = await this.ds()
|
|
310
342
|
const entities = rows.map(obj =>
|
|
311
|
-
this.toDatastoreEntity(table, obj, opt.excludeFromIndexes as string[]),
|
|
343
|
+
this.toDatastoreEntity(ds, table, obj, opt.excludeFromIndexes as string[]),
|
|
312
344
|
)
|
|
313
345
|
|
|
314
346
|
const method = methodMap[opt.saveMethod || 'upsert'] || 'save'
|
|
315
347
|
|
|
316
348
|
const save = pRetryFn(
|
|
317
349
|
async (batch: DatastorePayload<ROW>[]) => {
|
|
318
|
-
await ((opt.tx as DatastoreDBTransaction)?.tx ||
|
|
350
|
+
await ((opt.tx as DatastoreDBTransaction)?.tx || ds)[method](batch)
|
|
319
351
|
},
|
|
320
352
|
this.getPRetryOptions(`DatastoreLib.saveBatch(${table})`),
|
|
321
353
|
)
|
|
@@ -357,7 +389,12 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
357
389
|
return await this.deleteByIds(q.table, ids, opt)
|
|
358
390
|
}
|
|
359
391
|
|
|
360
|
-
const
|
|
392
|
+
const ds = await this.ds()
|
|
393
|
+
const datastoreQuery = dbQueryToDatastoreQuery(
|
|
394
|
+
q.select([]),
|
|
395
|
+
ds.createQuery(q.table),
|
|
396
|
+
await this.getPropertyFilter(),
|
|
397
|
+
)
|
|
361
398
|
const dsOpt = this.getRunQueryOptions(opt)
|
|
362
399
|
const { rows } = await this.runDatastoreQuery<ROW>(datastoreQuery, dsOpt)
|
|
363
400
|
return await this.deleteByIds(
|
|
@@ -376,12 +413,13 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
376
413
|
ids: string[],
|
|
377
414
|
opt: DatastoreDBOptions = {},
|
|
378
415
|
): Promise<number> {
|
|
379
|
-
const
|
|
416
|
+
const ds = await this.ds()
|
|
417
|
+
const keys = ids.map(id => this.key(ds, table, id))
|
|
380
418
|
|
|
381
419
|
await pMap(
|
|
382
420
|
_chunk(keys, MAX_ITEMS),
|
|
383
421
|
|
|
384
|
-
async batch => await ((opt.tx as DatastoreDBTransaction)?.tx ||
|
|
422
|
+
async batch => await ((opt.tx as DatastoreDBTransaction)?.tx || ds).delete(batch),
|
|
385
423
|
{
|
|
386
424
|
concurrency: DATASTORE_RECOMMENDED_CONCURRENCY,
|
|
387
425
|
},
|
|
@@ -393,8 +431,9 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
393
431
|
fn: DBTransactionFn,
|
|
394
432
|
opt: CommonDBTransactionOptions = {},
|
|
395
433
|
): Promise<void> {
|
|
434
|
+
const ds = await this.ds()
|
|
396
435
|
const { readOnly } = opt
|
|
397
|
-
const datastoreTx =
|
|
436
|
+
const datastoreTx = ds.transaction({
|
|
398
437
|
readOnly,
|
|
399
438
|
})
|
|
400
439
|
|
|
@@ -410,8 +449,9 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
410
449
|
}
|
|
411
450
|
|
|
412
451
|
async getAllStats(): Promise<DatastoreStats[]> {
|
|
413
|
-
const
|
|
414
|
-
const
|
|
452
|
+
const ds = await this.ds()
|
|
453
|
+
const q = ds.createQuery('__Stat_Kind__')
|
|
454
|
+
const [statsArray] = await ds.runQuery(q)
|
|
415
455
|
return statsArray || []
|
|
416
456
|
}
|
|
417
457
|
|
|
@@ -419,12 +459,15 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
419
459
|
* Returns undefined e.g when Table is non-existing
|
|
420
460
|
*/
|
|
421
461
|
async getStats(table: string): Promise<DatastoreStats | undefined> {
|
|
422
|
-
const
|
|
462
|
+
const ds = await this.ds()
|
|
463
|
+
const propertyFilter = await this.getPropertyFilter()
|
|
464
|
+
|
|
465
|
+
const q = ds
|
|
423
466
|
.createQuery('__Stat_Kind__')
|
|
424
467
|
// .filter('kind_name', table)
|
|
425
|
-
.filter(new
|
|
468
|
+
.filter(new propertyFilter('kind_name', '=', table))
|
|
426
469
|
.limit(1)
|
|
427
|
-
const [statsArray] = await
|
|
470
|
+
const [statsArray] = await ds.runQuery(q)
|
|
428
471
|
const [stats] = statsArray
|
|
429
472
|
return stats
|
|
430
473
|
}
|
|
@@ -435,11 +478,12 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
435
478
|
}
|
|
436
479
|
|
|
437
480
|
async getTableProperties(table: string): Promise<DatastorePropertyStats[]> {
|
|
438
|
-
const
|
|
481
|
+
const ds = await this.ds()
|
|
482
|
+
const q = ds
|
|
439
483
|
.createQuery('__Stat_PropertyType_PropertyName_Kind__')
|
|
440
484
|
// .filter('kind_name', table)
|
|
441
|
-
.filter(new
|
|
442
|
-
const [stats] = await
|
|
485
|
+
.filter(new (await this.getPropertyFilter())('kind_name', '=', table))
|
|
486
|
+
const [stats] = await ds.runQuery(q)
|
|
443
487
|
return stats
|
|
444
488
|
}
|
|
445
489
|
|
|
@@ -455,11 +499,12 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
455
499
|
|
|
456
500
|
// if key field exists on entity, it will be used as key (prevent to duplication of numeric keyed entities)
|
|
457
501
|
private toDatastoreEntity<T extends ObjectWithId>(
|
|
502
|
+
ds: Datastore,
|
|
458
503
|
kind: string,
|
|
459
504
|
o: T,
|
|
460
505
|
excludeFromIndexes: string[] = [],
|
|
461
506
|
): DatastorePayload<T> {
|
|
462
|
-
const key = this.getDsKey(o) || this.key(kind, o.id)
|
|
507
|
+
const key = this.getDsKey(o) || this.key(ds, kind, o.id)
|
|
463
508
|
const data = Object.assign({}, o) as any
|
|
464
509
|
delete data.id
|
|
465
510
|
delete data[this.KEY]
|
|
@@ -471,9 +516,9 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
471
516
|
}
|
|
472
517
|
}
|
|
473
518
|
|
|
474
|
-
key(kind: string, id: string): Key {
|
|
519
|
+
key(ds: Datastore, kind: string, id: string): Key {
|
|
475
520
|
_assert(id, `Cannot save "${kind}" entity without "id"`)
|
|
476
|
-
return
|
|
521
|
+
return ds.key([kind, id])
|
|
477
522
|
}
|
|
478
523
|
|
|
479
524
|
private getDsKey(o: any): Key | undefined {
|
package/src/datastore.model.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { DatastoreOptions, Key } from '@google-cloud/datastore'
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import type {
|
|
3
|
+
CommonDBOptions,
|
|
4
|
+
CommonDBReadOptions,
|
|
5
|
+
CommonDBSaveOptions,
|
|
6
|
+
} from '@naturalcycles/db-lib'
|
|
7
|
+
import type { CommonLogger, NumberOfSeconds, ObjectWithId } from '@naturalcycles/js-lib'
|
|
4
8
|
|
|
5
9
|
export interface DatastorePayload<T = any> {
|
|
6
10
|
key: Key
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from '
|
|
7
|
-
import {
|
|
8
|
-
import { AppError, ObjectWithId } from '@naturalcycles/js-lib'
|
|
9
|
-
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
10
|
-
import { DatastoreDB } from './datastore.db'
|
|
11
|
-
import { DatastoreDBCfg } from './datastore.model'
|
|
1
|
+
import type { CommonKeyValueDB, IncrementTuple, KeyValueDBTuple } from '@naturalcycles/db-lib'
|
|
2
|
+
import { commonKeyValueDBFullSupport, DBQuery } from '@naturalcycles/db-lib'
|
|
3
|
+
import type { ObjectWithId } from '@naturalcycles/js-lib'
|
|
4
|
+
import { AppError } from '@naturalcycles/js-lib'
|
|
5
|
+
import type { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
6
|
+
import { DatastoreDB } from './datastore.db.js'
|
|
7
|
+
import type { DatastoreDBCfg } from './datastore.model.js'
|
|
12
8
|
|
|
13
9
|
interface KVObject {
|
|
14
10
|
id: string
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from './datastore.db'
|
|
2
|
-
export * from './datastore.model'
|
|
3
|
-
export * from './datastoreKeyValueDB'
|
|
1
|
+
export * from './datastore.db.js'
|
|
2
|
+
export * from './datastore.model.js'
|
|
3
|
+
export * from './datastoreKeyValueDB.js'
|
package/src/query.util.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { PropertyFilter, Query } from '@google-cloud/datastore'
|
|
2
|
-
import { DBQuery, DBQueryFilterOperator } from '@naturalcycles/db-lib'
|
|
3
|
-
import { ObjectWithId, StringMap } from '@naturalcycles/js-lib'
|
|
1
|
+
import type { PropertyFilter, Query } from '@google-cloud/datastore'
|
|
2
|
+
import type { DBQuery, DBQueryFilterOperator } from '@naturalcycles/db-lib'
|
|
3
|
+
import type { ObjectWithId, StringMap } from '@naturalcycles/js-lib'
|
|
4
4
|
|
|
5
5
|
const FNAME_MAP: StringMap = {
|
|
6
6
|
id: '__key__',
|
|
@@ -15,6 +15,7 @@ const OP_MAP: Partial<Record<DBQueryFilterOperator, string>> = {
|
|
|
15
15
|
export function dbQueryToDatastoreQuery<ROW extends ObjectWithId>(
|
|
16
16
|
dbQuery: Readonly<DBQuery<ROW>>,
|
|
17
17
|
emptyQuery: Query,
|
|
18
|
+
propertyFilterClass: typeof PropertyFilter,
|
|
18
19
|
): Query {
|
|
19
20
|
let q = emptyQuery
|
|
20
21
|
|
|
@@ -30,7 +31,7 @@ export function dbQueryToDatastoreQuery<ROW extends ObjectWithId>(
|
|
|
30
31
|
// `a == null` will return just that - rows with null values
|
|
31
32
|
let { op, val } = f
|
|
32
33
|
if (val === undefined) val = null
|
|
33
|
-
q = q.filter(new
|
|
34
|
+
q = q.filter(new propertyFilterClass(f.name as string, OP_MAP[op] || (op as any), val))
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
// limit
|