@naturalcycles/datastore-lib 4.11.0 → 4.13.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.
@@ -3,7 +3,7 @@ import type { CommonDB, CommonDBOptions, CommonDBReadOptions, CommonDBSaveOption
3
3
  import { BaseCommonDB } from '@naturalcycles/db-lib';
4
4
  import type { JsonSchemaObject, JsonSchemaRootObject } from '@naturalcycles/js-lib/json-schema';
5
5
  import type { CommonLogger } from '@naturalcycles/js-lib/log';
6
- import type { ObjectWithId } from '@naturalcycles/js-lib/types';
6
+ import { type ObjectWithId, type StringMap } from '@naturalcycles/js-lib/types';
7
7
  import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream';
8
8
  import type { DatastoreDBCfg, DatastoreDBOptions, DatastoreDBReadOptions, DatastoreDBSaveOptions, DatastoreDBStreamOptions, DatastorePropertyStats, DatastoreStats } from './datastore.model.js';
9
9
  /**
@@ -27,6 +27,7 @@ export declare class DatastoreDB extends BaseCommonDB implements CommonDB {
27
27
  private getDatastoreLib;
28
28
  ping(): Promise<void>;
29
29
  getByIds<ROW extends ObjectWithId>(table: string, ids: string[], opt?: DatastoreDBReadOptions): Promise<ROW[]>;
30
+ multiGet<ROW extends ObjectWithId>(map: StringMap<string[]>, opt?: DatastoreDBReadOptions): Promise<StringMap<ROW[]>>;
30
31
  runQuery<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, opt?: DatastoreDBReadOptions): Promise<RunQueryResult<ROW>>;
31
32
  runQueryCount<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, opt?: DatastoreDBReadOptions): Promise<number>;
32
33
  private runDatastoreQuery;
@@ -41,6 +42,7 @@ export declare class DatastoreDB extends BaseCommonDB implements CommonDB {
41
42
  * regardless if they were actually deleted or not.
42
43
  */
43
44
  deleteByIds(table: string, ids: string[], opt?: DatastoreDBOptions): Promise<number>;
45
+ multiDelete(map: StringMap<string[]>, opt?: DatastoreDBOptions): Promise<number>;
44
46
  createTransaction(opt?: CommonDBTransactionOptions): Promise<DatastoreDBTransaction>;
45
47
  runInTransaction(fn: DBTransactionFn, opt?: CommonDBTransactionOptions): Promise<void>;
46
48
  getAllStats(): Promise<DatastoreStats[]>;
@@ -51,10 +53,11 @@ export declare class DatastoreDB extends BaseCommonDB implements CommonDB {
51
53
  getStatsCount(table: string): Promise<number | undefined>;
52
54
  getTableProperties(table: string): Promise<DatastorePropertyStats[]>;
53
55
  private mapId;
56
+ private parseDatastoreEntity;
54
57
  private toDatastoreEntity;
55
58
  key(ds: Datastore, kind: string, id: string): Key;
56
59
  private getDsKey;
57
- getKey(key: Key): string | undefined;
60
+ private getIdFromKey;
58
61
  createTable<ROW extends ObjectWithId>(_table: string, _schema: JsonSchemaObject<ROW>): Promise<void>;
59
62
  getTables(): Promise<string[]>;
60
63
  getTableSchema<ROW extends ObjectWithId>(table: string): Promise<JsonSchemaRootObject<ROW>>;
@@ -9,6 +9,7 @@ import { _omit } from '@naturalcycles/js-lib/object/object.util.js';
9
9
  import { pMap } from '@naturalcycles/js-lib/promise/pMap.js';
10
10
  import { pRetry, pRetryFn } from '@naturalcycles/js-lib/promise/pRetry.js';
11
11
  import { pTimeout } from '@naturalcycles/js-lib/promise/pTimeout.js';
12
+ import { _stringMapEntries, _stringMapValues, } from '@naturalcycles/js-lib/types';
12
13
  import { boldWhite } from '@naturalcycles/nodejs-lib/colors';
13
14
  import { DatastoreType } from './datastore.model.js';
14
15
  import { DatastoreStreamReadable } from './DatastoreStreamReadable.js';
@@ -44,6 +45,7 @@ export class DatastoreDB extends BaseCommonDB {
44
45
  support = {
45
46
  ...commonDBFullSupport,
46
47
  patchByQuery: false,
48
+ patchById: false, // use Firestore for that
47
49
  increment: false,
48
50
  };
49
51
  constructor(cfg = {}) {
@@ -144,7 +146,29 @@ export class DatastoreDB extends BaseCommonDB {
144
146
  .map(r => this.mapId(r))
145
147
  // Seems like datastore .get() method doesn't return items properly sorted by input ids, so we gonna sort them here
146
148
  // same ids are not expected here
147
- .sort((a, b) => (a.id > b.id ? 1 : -1)));
149
+ .sort(idComparator));
150
+ }
151
+ async multiGet(map, opt = {}) {
152
+ const result = {};
153
+ const ds = await this.ds();
154
+ const dsOpt = this.getRunQueryOptions(opt);
155
+ const keys = [];
156
+ for (const [table, ids] of _stringMapEntries(map)) {
157
+ result[table] = [];
158
+ keys.push(...ids.map(id => this.key(ds, table, id)));
159
+ }
160
+ const r = await ds.get(keys, dsOpt);
161
+ const rows = r[0];
162
+ rows.forEach(entity => {
163
+ const [kind, row] = this.parseDatastoreEntity(entity);
164
+ result[kind].push(row);
165
+ });
166
+ // Seems like datastore .get() method doesn't return items properly sorted by input ids, so we gonna sort them here
167
+ // same ids are not expected here
168
+ for (const tableRows of _stringMapValues(result)) {
169
+ tableRows.sort(idComparator);
170
+ }
171
+ return result;
148
172
  }
149
173
  // getQueryKind(q: Query): string {
150
174
  // if (!q?.kinds?.length) return '' // should never be the case, but
@@ -240,6 +264,7 @@ export class DatastoreDB extends BaseCommonDB {
240
264
  throw err;
241
265
  }
242
266
  }
267
+ // not implementing multiSaveBatch, since the API does not support passing excludeFromIndexes for multiple tables
243
268
  async deleteByQuery(q, opt = {}) {
244
269
  const idFilter = q._filters.find(f => f.name === 'id');
245
270
  if (idFilter) {
@@ -271,6 +296,24 @@ export class DatastoreDB extends BaseCommonDB {
271
296
  });
272
297
  return ids.length;
273
298
  }
299
+ async multiDelete(map, opt = {}) {
300
+ const ds = await this.ds();
301
+ const keys = [];
302
+ for (const [table, ids] of _stringMapEntries(map)) {
303
+ keys.push(...ids.map(id => this.key(ds, table, id)));
304
+ }
305
+ const retryOptions = this.getPRetryOptions(`DatastoreLib.multiDeleteByIds`);
306
+ await pMap(_chunk(keys, MAX_ITEMS),
307
+ // async batch => await doDelete(batch),
308
+ async (batchOfKeys) => {
309
+ await pRetry(async () => {
310
+ await (opt.tx?.tx || ds).delete(batchOfKeys);
311
+ }, retryOptions);
312
+ }, {
313
+ concurrency: DATASTORE_RECOMMENDED_CONCURRENCY,
314
+ });
315
+ return keys.length;
316
+ }
274
317
  async createTransaction(opt = {}) {
275
318
  const ds = await this.ds();
276
319
  const { readOnly } = opt;
@@ -336,11 +379,21 @@ export class DatastoreDB extends BaseCommonDB {
336
379
  return o;
337
380
  const r = {
338
381
  ...o,
339
- id: this.getKey(this.getDsKey(o)),
382
+ id: this.getIdFromKey(this.getDsKey(o)),
340
383
  };
341
384
  delete r[this.KEY];
342
385
  return r;
343
386
  }
387
+ parseDatastoreEntity(entity) {
388
+ const key = this.getDsKey(entity);
389
+ const { name, kind } = key;
390
+ const row = {
391
+ ...entity,
392
+ id: name,
393
+ };
394
+ delete row[this.KEY];
395
+ return [kind, row];
396
+ }
344
397
  // if key field exists on entity, it will be used as key (prevent to duplication of numeric keyed entities)
345
398
  toDatastoreEntity(ds, kind, o, excludeFromIndexes = []) {
346
399
  const key = this.getDsKey(o) || this.key(ds, kind, o.id);
@@ -360,7 +413,7 @@ export class DatastoreDB extends BaseCommonDB {
360
413
  getDsKey(o) {
361
414
  return o?.[this.KEY];
362
415
  }
363
- getKey(key) {
416
+ getIdFromKey(key) {
364
417
  const id = key.id || key.name;
365
418
  return id?.toString();
366
419
  }
@@ -508,3 +561,6 @@ export class DatastoreDBTransaction {
508
561
  return await this.db.deleteByIds(table, ids, { ...opt, tx: this });
509
562
  }
510
563
  }
564
+ function idComparator(a, b) {
565
+ return a.id > b.id ? 1 : a.id < b.id ? -1 : 0;
566
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/datastore-lib",
3
3
  "type": "module",
4
- "version": "4.11.0",
4
+ "version": "4.13.0",
5
5
  "description": "Opinionated library to work with Google Datastore, implements CommonDB",
6
6
  "dependencies": {
7
7
  "@google-cloud/datastore": "^10",
@@ -35,7 +35,12 @@ import type { PRetryOptions } from '@naturalcycles/js-lib/promise'
35
35
  import { pMap } from '@naturalcycles/js-lib/promise/pMap.js'
36
36
  import { pRetry, pRetryFn } from '@naturalcycles/js-lib/promise/pRetry.js'
37
37
  import { pTimeout } from '@naturalcycles/js-lib/promise/pTimeout.js'
38
- import type { ObjectWithId } from '@naturalcycles/js-lib/types'
38
+ import {
39
+ _stringMapEntries,
40
+ _stringMapValues,
41
+ type ObjectWithId,
42
+ type StringMap,
43
+ } from '@naturalcycles/js-lib/types'
39
44
  import { boldWhite } from '@naturalcycles/nodejs-lib/colors'
40
45
  import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream'
41
46
  import type {
@@ -87,6 +92,7 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
87
92
  override support: CommonDBSupport = {
88
93
  ...commonDBFullSupport,
89
94
  patchByQuery: false,
95
+ patchById: false, // use Firestore for that
90
96
  increment: false,
91
97
  }
92
98
 
@@ -219,10 +225,40 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
219
225
  .map(r => this.mapId<ROW>(r))
220
226
  // Seems like datastore .get() method doesn't return items properly sorted by input ids, so we gonna sort them here
221
227
  // same ids are not expected here
222
- .sort((a, b) => (a.id > b.id ? 1 : -1))
228
+ .sort(idComparator)
223
229
  )
224
230
  }
225
231
 
232
+ override async multiGet<ROW extends ObjectWithId>(
233
+ map: StringMap<string[]>,
234
+ opt: DatastoreDBReadOptions = {},
235
+ ): Promise<StringMap<ROW[]>> {
236
+ const result: StringMap<ROW[]> = {}
237
+ const ds = await this.ds()
238
+ const dsOpt = this.getRunQueryOptions(opt)
239
+ const keys: Key[] = []
240
+ for (const [table, ids] of _stringMapEntries(map)) {
241
+ result[table] = []
242
+ keys.push(...ids.map(id => this.key(ds, table, id)))
243
+ }
244
+
245
+ const r = await ds.get(keys, dsOpt)
246
+ const rows: any[] = r[0]
247
+
248
+ rows.forEach(entity => {
249
+ const [kind, row] = this.parseDatastoreEntity<ROW>(entity)
250
+ result[kind]!.push(row)
251
+ })
252
+
253
+ // Seems like datastore .get() method doesn't return items properly sorted by input ids, so we gonna sort them here
254
+ // same ids are not expected here
255
+ for (const tableRows of _stringMapValues(result)) {
256
+ tableRows.sort(idComparator)
257
+ }
258
+
259
+ return result
260
+ }
261
+
226
262
  // getQueryKind(q: Query): string {
227
263
  // if (!q?.kinds?.length) return '' // should never be the case, but
228
264
  // return q.kinds[0]!
@@ -378,6 +414,8 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
378
414
  }
379
415
  }
380
416
 
417
+ // not implementing multiSaveBatch, since the API does not support passing excludeFromIndexes for multiple tables
418
+
381
419
  override async deleteByQuery<ROW extends ObjectWithId>(
382
420
  q: DBQuery<ROW>,
383
421
  opt: DatastoreDBReadOptions = {},
@@ -432,6 +470,34 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
432
470
  return ids.length
433
471
  }
434
472
 
473
+ override async multiDelete(
474
+ map: StringMap<string[]>,
475
+ opt: DatastoreDBOptions = {},
476
+ ): Promise<number> {
477
+ const ds = await this.ds()
478
+ const keys: Key[] = []
479
+ for (const [table, ids] of _stringMapEntries(map)) {
480
+ keys.push(...ids.map(id => this.key(ds, table, id)))
481
+ }
482
+
483
+ const retryOptions = this.getPRetryOptions(`DatastoreLib.multiDeleteByIds`)
484
+
485
+ await pMap(
486
+ _chunk(keys, MAX_ITEMS),
487
+ // async batch => await doDelete(batch),
488
+ async batchOfKeys => {
489
+ await pRetry(async () => {
490
+ await ((opt.tx as DatastoreDBTransaction)?.tx || ds).delete(batchOfKeys)
491
+ }, retryOptions)
492
+ },
493
+ {
494
+ concurrency: DATASTORE_RECOMMENDED_CONCURRENCY,
495
+ },
496
+ )
497
+
498
+ return keys.length
499
+ }
500
+
435
501
  override async createTransaction(
436
502
  opt: CommonDBTransactionOptions = {},
437
503
  ): Promise<DatastoreDBTransaction> {
@@ -508,12 +574,23 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
508
574
  if (!o) return o
509
575
  const r = {
510
576
  ...o,
511
- id: this.getKey(this.getDsKey(o)!),
577
+ id: this.getIdFromKey(this.getDsKey(o)!),
512
578
  }
513
579
  delete r[this.KEY]
514
580
  return r
515
581
  }
516
582
 
583
+ private parseDatastoreEntity<T extends ObjectWithId>(entity: any): [kind: string, row: T] {
584
+ const key = this.getDsKey(entity)!
585
+ const { name, kind } = key
586
+ const row: any = {
587
+ ...entity,
588
+ id: name,
589
+ }
590
+ delete row[this.KEY]
591
+ return [kind, row]
592
+ }
593
+
517
594
  // if key field exists on entity, it will be used as key (prevent to duplication of numeric keyed entities)
518
595
  private toDatastoreEntity<T extends ObjectWithId>(
519
596
  ds: Datastore,
@@ -542,7 +619,7 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
542
619
  return o?.[this.KEY]
543
620
  }
544
621
 
545
- getKey(key: Key): string | undefined {
622
+ private getIdFromKey(key: Key): string | undefined {
546
623
  const id = key.id || key.name
547
624
  return id?.toString()
548
625
  }
@@ -709,3 +786,7 @@ export class DatastoreDBTransaction implements DBTransaction {
709
786
  return await this.db.deleteByIds(table, ids, { ...opt, tx: this })
710
787
  }
711
788
  }
789
+
790
+ function idComparator<T extends ObjectWithId>(a: T, b: T): number {
791
+ return a.id > b.id ? 1 : a.id < b.id ? -1 : 0
792
+ }