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