datastore-api 6.1.0 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,22 +1,22 @@
1
1
  /*
2
2
  * dstore.ts - Datastore Compatibility layer
3
- * Try to get a smoother api for transactions and such.
3
+ * Try to get a smoother API for transactions and such.
4
4
  * A little bit inspired by the Python2 ndb interface.
5
5
  * But without the ORM bits.
6
6
  *
7
7
  * In future https://github.com/graphql/dataloader might be used for batching.
8
8
  *
9
9
  * Created by Dr. Maximillian Dornseif 2021-12-05 in huwawi3backend 11.10.0
10
- * Copyright (c) 2021, 2022, 2023 Dr. Maximillian Dornseif
10
+ * Copyright (c) 2021, 2022, 2023, 2025 Dr. Maximillian Dornseif
11
11
  */
12
12
 
13
- import { AsyncLocalStorage } from 'async_hooks';
14
- import { setImmediate } from 'timers/promises';
13
+ import { AsyncLocalStorage } from 'async_hooks'
14
+ import { setImmediate } from 'timers/promises'
15
15
 
16
- import { Datastore, Key, PathType, Query, Transaction, PropertyFilter } from '@google-cloud/datastore';
17
- import { Entity, entity } from '@google-cloud/datastore/build/src/entity';
18
- import { Operator, RunQueryInfo, RunQueryResponse } from '@google-cloud/datastore/build/src/query';
19
- import { CommitResponse } from '@google-cloud/datastore/build/src/request';
16
+ import { Datastore, Key, PathType, Query, Transaction, PropertyFilter } from '@google-cloud/datastore'
17
+ import { Entity, entity } from '@google-cloud/datastore/build/src/entity'
18
+ import { Operator, RunQueryInfo, RunQueryResponse } from '@google-cloud/datastore/build/src/query'
19
+ import { CommitResponse } from '@google-cloud/datastore/build/src/request'
20
20
  import {
21
21
  assert,
22
22
  assertIsArray,
@@ -24,19 +24,20 @@ import {
24
24
  assertIsNumber,
25
25
  assertIsObject,
26
26
  assertIsString,
27
- } from 'assertate-debug';
28
- import Debug from 'debug';
29
- import promClient from 'prom-client';
30
- import { Writable } from 'ts-essentials';
27
+ } from 'assertate-debug'
28
+ import Debug from 'debug'
29
+ import promClient from 'prom-client'
30
+ import { Writable } from 'ts-essentials'
31
+ import { assertIsKey } from './assert'
31
32
 
32
33
  /** @ignore */
33
- export { Datastore, Key, PathType, Query, Transaction } from '@google-cloud/datastore';
34
+ export { Datastore, Key, PathType, Query, Transaction } from '@google-cloud/datastore'
34
35
 
35
36
  /** @ignore */
36
- const debug = Debug('ds:api');
37
+ const debug = Debug('ds:api')
37
38
 
38
39
  /** @ignore */
39
- const transactionAsyncLocalStorage = new AsyncLocalStorage();
40
+ const transactionAsyncLocalStorage = new AsyncLocalStorage()
40
41
 
41
42
  // for HMR
42
43
  promClient.register.removeSingleMetric('dstore_requests_seconds')
@@ -46,25 +47,25 @@ const metricHistogram = new promClient.Histogram({
46
47
  name: 'dstore_requests_seconds',
47
48
  help: 'How long did Datastore operations take?',
48
49
  labelNames: ['operation'],
49
- });
50
+ })
50
51
  const metricFailureCounter = new promClient.Counter({
51
52
  name: 'dstore_failures_total',
52
53
  help: 'How many Datastore operations failed?',
53
54
  labelNames: ['operation'],
54
- });
55
+ })
55
56
 
56
57
  /** Use instead of Datastore.KEY
57
58
  *
58
59
  * Even better: use `_key` instead.
59
60
  */
60
- export const KEYSYM = Datastore.KEY;
61
+ export const KEYSYM = Datastore.KEY
61
62
 
62
- export type IGqlFilterTypes = boolean | string | number;
63
+ export type IGqlFilterTypes = boolean | string | number
63
64
 
64
65
  export type IGqlFilterSpec = {
65
- readonly eq: IGqlFilterTypes;
66
- };
67
- export type TGqlFilterList = Array<[string, Operator, DstorePropertyValues]>;
66
+ readonly eq: IGqlFilterTypes
67
+ }
68
+ export type TGqlFilterList = Array<[string, Operator, DstorePropertyValues]>
68
69
 
69
70
  /** Define what can be written into the Datastore */
70
71
  export type DstorePropertyValues =
@@ -77,11 +78,11 @@ export type DstorePropertyValues =
77
78
  | Buffer
78
79
  | Key
79
80
  | DstorePropertyValues[]
80
- | { [key: string]: DstorePropertyValues };
81
+ | { [key: string]: DstorePropertyValues }
81
82
 
82
83
  export interface IDstoreEntryWithoutKey {
83
84
  /** All User Data stored in the Datastore */
84
- [key: string]: DstorePropertyValues;
85
+ [key: string]: DstorePropertyValues
85
86
  }
86
87
 
87
88
  /** Represents what is actually stored inside the Datastore, called "Entity" by Google
@@ -89,39 +90,56 @@ export interface IDstoreEntryWithoutKey {
89
90
  */
90
91
  export interface IDstoreEntry extends IDstoreEntryWithoutKey {
91
92
  /* Datastore Key provided by [@google-cloud/datastore](https://github.com/googleapis/nodejs-datastore#readme) */
92
- readonly [Datastore.KEY]?: Key;
93
+ readonly [Datastore.KEY]?: Key
94
+ /** [Datastore.KEY] key */
95
+ _keyStr: string
96
+ }
97
+
98
+ /** Represents what is actually stored inside the Datastore, called "Entity" by Google
99
+ */
100
+ export interface IDstoreEntryWithKey extends IDstoreEntry {
101
+ /* Datastore Key provided by [@google-cloud/datastore](https://github.com/googleapis/nodejs-datastore#readme) */
102
+ readonly [Datastore.KEY]: Key
93
103
  /** [Datastore.KEY] key */
94
- _keyStr: string;
104
+ _keyStr: string
95
105
  }
96
106
 
97
107
  /** Represents the thing you pass to the save method. Also called "Entity" by Google */
98
108
  export type DstoreSaveEntity = {
99
- key: Key;
109
+ key: Key
100
110
  data: Omit<IDstoreEntry, '_keyStr' | Datastore['KEY']> &
101
- Partial<{
102
- _keyStr: string|undefined;
103
- [Datastore.KEY]: Key;
104
- }>;
105
- method?: 'insert' | 'update' | 'upsert';
106
- excludeLargeProperties?: boolean;
107
- excludeFromIndexes?: readonly string[];
108
- };
111
+ Partial<{
112
+ _keyStr: string | undefined;
113
+ [Datastore.KEY]: Key
114
+ }>
115
+ method?: 'insert' | 'update' | 'upsert'
116
+ excludeLargeProperties?: boolean
117
+ excludeFromIndexes?: readonly string[]
118
+ }
109
119
 
120
+ export interface IIterateParams {
121
+ kindName: string,
122
+ filters?: TGqlFilterList,
123
+ limit?: number,
124
+ ordering?: readonly string[],
125
+ selection?: readonly string[],
126
+ }
110
127
  type IDstore = {
111
128
  /** Accessible by Users of the library. Keep in mind that you will access outside transactions created by [[runInTransaction]]. */
112
- readonly datastore: Datastore;
113
- key: (path: ReadonlyArray<PathType>) => Key;
114
- keyFromSerialized: (text: string) => Key;
115
- keySerialize: (key: Key) => string;
116
- readKey: (entry: IDstoreEntry) => Key;
117
- get: (key: Key) => Promise<IDstoreEntry | null>;
118
- getMulti: (keys: ReadonlyArray<Key>) => Promise<ReadonlyArray<IDstoreEntry | null>>;
119
- set: (key: Key, entry: IDstoreEntry) => Promise<Key>;
120
- save: (entities: readonly DstoreSaveEntity[]) => Promise<CommitResponse | undefined>;
121
- insert: (entities: readonly DstoreSaveEntity[]) => Promise<CommitResponse | undefined>;
122
- update: (entities: readonly DstoreSaveEntity[]) => Promise<CommitResponse | undefined>;
123
- delete: (keys: readonly Key[]) => Promise<CommitResponse | undefined>;
124
- createQuery: (kind: string) => Query;
129
+ readonly datastore: Datastore
130
+ key: (path: ReadonlyArray<PathType>) => Key
131
+ keyFromSerialized: (text: string) => Key
132
+ keySerialize: (key: Key) => string
133
+ readKey: (entry: IDstoreEntry) => Key
134
+ get: (key: Key) => Promise<IDstoreEntry | null>
135
+ getMulti: (keys: ReadonlyArray<Key>) => Promise<ReadonlyArray<IDstoreEntry | null>>
136
+ set: (key: Key, entry: IDstoreEntry) => Promise<Key>
137
+ save: (entities: readonly DstoreSaveEntity[]) => Promise<CommitResponse | undefined>
138
+ insert: (entities: readonly DstoreSaveEntity[]) => Promise<CommitResponse | undefined>
139
+ update: (entities: readonly DstoreSaveEntity[]) => Promise<CommitResponse | undefined>
140
+ delete: (keys: readonly Key[]) => Promise<CommitResponse | undefined>
141
+ createQuery: (kind: string) => Query
142
+ runQuery: (query: Query | Omit<Query, 'run'>) => Promise<RunQueryResponse>
125
143
  query: (
126
144
  kind: string,
127
145
  filters?: TGqlFilterList,
@@ -129,11 +147,11 @@ type IDstore = {
129
147
  ordering?: readonly string[],
130
148
  selection?: readonly string[],
131
149
  cursor?: string
132
- ) => Promise<RunQueryResponse>;
133
- runQuery: (query: Query | Omit<Query, 'run'>) => Promise<RunQueryResponse>;
134
- allocateOneId: (kindName: string) => Promise<string>;
135
- runInTransaction: <T>(func: { (): Promise<T>; (): T }) => Promise<T>;
136
- };
150
+ ) => Promise<RunQueryResponse>
151
+ iterate: (options: IIterateParams) => AsyncIterable<IDstoreEntry>
152
+ allocateOneId: (kindName: string) => Promise<string>
153
+ runInTransaction: <T>(func: { (): Promise<T>; (): T }) => Promise<T>
154
+ }
137
155
 
138
156
  /** Dstore implements a slightly more accessible version of the [Google Cloud Datastore: Node.js Client](https://cloud.google.com/nodejs/docs/reference/datastore/latest)
139
157
 
@@ -179,13 +197,13 @@ export class Dstore implements IDstore {
179
197
  ```
180
198
  */
181
199
  constructor(readonly datastore: Datastore, readonly projectId?: string, readonly logger?: string) {
182
- assertIsObject(datastore);
183
- this.engines.push(this.engine);
200
+ assertIsObject(datastore)
201
+ this.engines.push(this.engine)
184
202
  }
185
203
 
186
204
  /** Gets the Datastore or the current Transaction. */
187
205
  private getDoT(): Transaction | Datastore {
188
- return (transactionAsyncLocalStorage.getStore() as Transaction) || this.datastore;
206
+ return (transactionAsyncLocalStorage.getStore() as Transaction) || this.datastore
189
207
  }
190
208
 
191
209
  /** `key()` creates a [[Key]] Object from a path.
@@ -197,7 +215,7 @@ export class Dstore implements IDstore {
197
215
  * @category Datastore Drop-In
198
216
  */
199
217
  key(path: readonly PathType[]): Key {
200
- return this.datastore.key(path as Writable<typeof path>);
218
+ return this.datastore.key(path as Writable<typeof path>)
201
219
  }
202
220
 
203
221
  /** `keyFromSerialized()` serializes [[Key]] to a string.
@@ -209,7 +227,7 @@ export class Dstore implements IDstore {
209
227
  * @category Datastore Drop-In
210
228
  */
211
229
  keySerialize(key: Key): string {
212
- return key ? this.urlSaveKey.legacyEncode(this.projectId ?? '', key) : '';
230
+ return key ? this.urlSaveKey.legacyEncode(this.projectId ?? '', key) : ''
213
231
  }
214
232
 
215
233
  /** `keyFromSerialized()` deserializes a string created with [[keySerialize]] to a [[Key]].
@@ -219,7 +237,7 @@ export class Dstore implements IDstore {
219
237
  * @category Datastore Drop-In
220
238
  */
221
239
  keyFromSerialized(text: string): Key {
222
- return this.urlSaveKey.legacyDecode(text);
240
+ return this.urlSaveKey.legacyDecode(text)
223
241
  }
224
242
 
225
243
  /** `readKey()` extracts the [[Key]] from an [[IDstoreEntry]].
@@ -230,20 +248,20 @@ export class Dstore implements IDstore {
230
248
  * @category Additional
231
249
  */
232
250
  readKey(ent: IDstoreEntry): Key {
233
- assertIsObject(ent);
234
- let ret = ent[Datastore.KEY];
251
+ assertIsObject(ent)
252
+ let ret = ent[Datastore.KEY]
235
253
  if (ent._keyStr && !ret) {
236
- ret = this.keyFromSerialized(ent._keyStr);
254
+ ret = this.keyFromSerialized(ent._keyStr)
237
255
  }
238
256
  assertIsObject(
239
257
  ret,
240
258
  'entity[Datastore.KEY]/entity._keyStr',
241
259
  `Entity is missing the datastore Key: ${JSON.stringify(ent)}`
242
- );
243
- return ret;
260
+ )
261
+ return ret
244
262
  }
245
263
 
246
- /** `fixKeys()` is called for all [[IDstoreEntry]]sa returned from [[Dstore]].
264
+ /** `fixKeys()` is called for all [[IDstoreEntry]] returned from [[Dstore]].
247
265
  *
248
266
  * Is ensures that besides `entity[Datastore.KEY]` there is `_keyStr` to be leveraged by [[readKey]].
249
267
  *
@@ -254,13 +272,36 @@ export class Dstore implements IDstore {
254
272
  ): Array<IDstoreEntry | undefined> {
255
273
  entities.forEach((x) => {
256
274
  if (!!x?.[Datastore.KEY] && x[Datastore.KEY]) {
257
- assertIsDefined(x[Datastore.KEY]);
258
- assertIsObject(x[Datastore.KEY]);
275
+ assertIsDefined(x[Datastore.KEY])
276
+ assertIsObject(x[Datastore.KEY])
259
277
  // Old TypesScript has problems with symbols as a property
260
- x._keyStr = this.keySerialize(x[Datastore.KEY] as Key);
278
+ x._keyStr = this.keySerialize(x[Datastore.KEY] as Key)
261
279
  }
262
- });
263
- return entities as Array<IDstoreEntry | undefined>;
280
+ })
281
+ return entities as Array<IDstoreEntry | undefined>
282
+ }
283
+
284
+ /** this is for save, insert, update and upsert and ensures _kkeyStr() handling.
285
+ *
286
+ */
287
+ private prepareEntitiesForDatastore(entities: readonly DstoreSaveEntity[]) {
288
+ for (const e of entities) {
289
+ assertIsObject(e.key)
290
+ assertIsObject(e.data)
291
+ this.fixKeys([e.data])
292
+ e.excludeLargeProperties = e.excludeLargeProperties === undefined ? true : e.excludeLargeProperties
293
+ e.data = { ...e.data, _keyStr: undefined }
294
+ }
295
+ }
296
+
297
+ /** this is for save, insert, update and upsert and ensures _kkeyStr() handling.
298
+ *
299
+ */
300
+ private prepareEntitiesFromDatastore(entities: readonly DstoreSaveEntity[] & unknown[]) {
301
+ for (const e of entities) {
302
+ e.data[Datastore.KEY] = e.key
303
+ this.fixKeys([e.data])
304
+ }
264
305
  }
265
306
 
266
307
  /** `get()` reads a [[IDstoreEntry]] from the Datastore.
@@ -279,11 +320,11 @@ export class Dstore implements IDstore {
279
320
  * @category Datastore Drop-In
280
321
  */
281
322
  async get(key: Key): Promise<IDstoreEntry | null> {
282
- assertIsObject(key);
283
- assert(!Array.isArray(key));
284
- assert(key.path.length % 2 == 0, `key.path must be complete: ${JSON.stringify(key.path)}`);
285
- const result = await this.getMulti([key]);
286
- return result?.[0] || null;
323
+ assertIsObject(key)
324
+ assert(!Array.isArray(key))
325
+ assert(key.path.length % 2 == 0, `key.path must be complete: ${JSON.stringify(key.path)}`)
326
+ const result = await this.getMulti([key])
327
+ return result?.[0] || null
287
328
  }
288
329
 
289
330
  /** `getMulti()` reads several [[IDstoreEntry]]s from the Datastore.
@@ -305,28 +346,28 @@ export class Dstore implements IDstore {
305
346
  */
306
347
  async getMulti(keys: readonly Key[]): Promise<Array<IDstoreEntry | null>> {
307
348
  // assertIsArray(keys);
308
- let results: (IDstoreEntry | null | undefined)[];
309
- const metricEnd = metricHistogram.startTimer();
349
+ let results: (IDstoreEntry | null | undefined)[]
350
+ const metricEnd = metricHistogram.startTimer()
310
351
  try {
311
352
  results = this.fixKeys(
312
353
  keys.length > 0 ? (await this.getDoT().get(keys as Writable<typeof keys>))?.[0] : []
313
- );
354
+ )
314
355
  } catch (error) {
315
- metricFailureCounter.inc({ operation: 'get' });
316
- await setImmediate();
317
- throw new DstoreError('datastore.getMulti error', error as Error, { keys });
356
+ metricFailureCounter.inc({ operation: 'get' })
357
+ await setImmediate()
358
+ throw new DstoreError('datastore.getMulti error', error as Error, { keys })
318
359
  } finally {
319
- metricEnd({ operation: 'get' });
360
+ metricEnd({ operation: 'get' })
320
361
  }
321
362
 
322
363
  // Sort resulting entities by the keys they were requested with.
323
- assertIsArray(results);
324
- const entities = results as IDstoreEntry[];
325
- const entitiesByKey: Record<string, IDstoreEntry> = {};
364
+ assertIsArray(results)
365
+ const entities = results as IDstoreEntry[]
366
+ const entitiesByKey: Record<string, IDstoreEntry> = {}
326
367
  entities.forEach((entity) => {
327
- entitiesByKey[JSON.stringify(entity[Datastore.KEY])] = entity;
328
- });
329
- return keys.map((key) => entitiesByKey[JSON.stringify(key)] || null);
368
+ entitiesByKey[JSON.stringify(entity[Datastore.KEY])] = entity
369
+ })
370
+ return keys.map((key) => entitiesByKey[JSON.stringify(key)] || null)
330
371
  }
331
372
 
332
373
  /** `set()` is addition to [[Datastore]]. It provides a classic Key-value Interface.
@@ -346,11 +387,11 @@ export class Dstore implements IDstore {
346
387
  * @category Additional
347
388
  */
348
389
  async set(key: Key, data: IDstoreEntryWithoutKey): Promise<Key> {
349
- assertIsObject(key);
350
- assertIsObject(data);
351
- const saveEntity = { key, data: { ...data, _keyStr: undefined } };
352
- await this.save([saveEntity]);
353
- return saveEntity.key;
390
+ assertIsObject(key)
391
+ assertIsObject(data)
392
+ const saveEntity = { key, data: { ...data, _keyStr: undefined } }
393
+ await this.save([saveEntity])
394
+ return saveEntity.key
354
395
  }
355
396
 
356
397
  /** `save()` is compatible to [Datastore.save()](https://cloud.google.com/nodejs/docs/reference/datastore/latest/datastore/datastore#_google_cloud_datastore_Datastore_save_member_1_).
@@ -387,34 +428,27 @@ export class Dstore implements IDstore {
387
428
  * @category Datastore Drop-In
388
429
  */
389
430
  async save(entities: readonly DstoreSaveEntity[]): Promise<CommitResponse | undefined> {
390
- assertIsArray(entities);
391
- let ret: CommitResponse | undefined;
392
- const metricEnd = metricHistogram.startTimer();
431
+ assertIsArray(entities)
432
+ let ret: CommitResponse | undefined
433
+ const metricEnd = metricHistogram.startTimer()
393
434
  try {
435
+ this.prepareEntitiesForDatastore(entities)
394
436
  // Within Transaction we don't get any answer here!
395
437
  // [ { mutationResults: [ [Object], [Object] ], indexUpdates: 51 } ]
396
- for (const e of entities) {
397
- assertIsObject(e.key);
398
- assertIsObject(e.data);
399
- this.fixKeys([e.data]);
400
- e.excludeLargeProperties = e.excludeLargeProperties === undefined ? true : e.excludeLargeProperties;
401
- e.data = { ...e.data, _keyStr: undefined };
402
- }
403
- ret = (await this.getDoT().save(entities)) || undefined;
404
- for (const e of entities) {
405
- e.data[Datastore.KEY] = e.key;
406
- this.fixKeys([e.data]);
407
- }
438
+ ret = (await this.getDoT().save(entities)) || undefined
439
+ this.prepareEntitiesFromDatastore(entities)
408
440
  } catch (error) {
409
- metricFailureCounter.inc({ operation: 'save' });
410
- await setImmediate();
411
- throw new DstoreError('datastore.save error', error as Error);
441
+ metricFailureCounter.inc({ operation: 'save' })
442
+ await setImmediate()
443
+ throw new DstoreError('datastore.save error', error as Error)
412
444
  } finally {
413
- metricEnd({ operation: 'save' });
445
+ metricEnd({ operation: 'save' })
414
446
  }
415
- return ret;
447
+ return ret
416
448
  }
417
449
 
450
+
451
+
418
452
  /** `insert()` is compatible to [Datastore.insert()](https://cloud.google.com/nodejs/docs/reference/datastore/latest/datastore/datastore#_google_cloud_datastore_Datastore_insert_member_1_).
419
453
  *
420
454
  * The single Parameter is a list of [[DstoreSaveEntity]]s.
@@ -434,20 +468,22 @@ export class Dstore implements IDstore {
434
468
  * @category Datastore Drop-In
435
469
  */
436
470
  async insert(entities: readonly DstoreSaveEntity[]): Promise<CommitResponse | undefined> {
437
- assertIsArray(entities);
438
- let ret: CommitResponse | undefined;
439
- const metricEnd = metricHistogram.startTimer();
471
+ assertIsArray(entities)
472
+ let ret: CommitResponse | undefined
473
+ const metricEnd = metricHistogram.startTimer()
440
474
  try {
441
- ret = (await this.getDoT().insert(entities)) || undefined;
475
+ this.prepareEntitiesForDatastore(entities)
476
+ ret = (await this.getDoT().insert(entities)) || undefined
477
+ this.prepareEntitiesFromDatastore(entities)
442
478
  } catch (error) {
443
479
  // console.error(error)
444
- metricFailureCounter.inc({ operation: 'insert' });
445
- await setImmediate();
446
- throw new DstoreError('datastore.insert error', error as Error);
480
+ metricFailureCounter.inc({ operation: 'insert' })
481
+ await setImmediate()
482
+ throw new DstoreError('datastore.insert error', error as Error)
447
483
  } finally {
448
- metricEnd({ operation: 'insert' });
484
+ metricEnd({ operation: 'insert' })
449
485
  }
450
- return ret;
486
+ return ret
451
487
  }
452
488
 
453
489
  /** `update()` is compatible to [Datastore.update()](https://cloud.google.com/nodejs/docs/reference/datastore/latest/datastore/datastore#_google_cloud_datastore_Datastore_update_member_1_).
@@ -469,29 +505,31 @@ export class Dstore implements IDstore {
469
505
  * @category Datastore Drop-In
470
506
  */
471
507
  async update(entities: readonly DstoreSaveEntity[]): Promise<CommitResponse | undefined> {
472
- assertIsArray(entities);
508
+ assertIsArray(entities)
473
509
 
474
- entities.forEach((entity) => assertIsObject(entity.key));
510
+ entities.forEach((entity) => assertIsObject(entity.key))
475
511
  entities.forEach((entity) =>
476
512
  assert(
477
513
  entity.key.path.length % 2 == 0,
478
514
  `entity.key.path must be complete: ${JSON.stringify([entity.key.path, entity])}`
479
515
  )
480
- );
481
- let ret: CommitResponse | undefined;
482
- const metricEnd = metricHistogram.startTimer();
516
+ )
517
+ let ret: CommitResponse | undefined
518
+ const metricEnd = metricHistogram.startTimer()
483
519
 
484
520
  try {
485
- ret = (await this.getDoT().update(entities)) || undefined;
521
+ this.prepareEntitiesForDatastore(entities)
522
+ ret = (await this.getDoT().update(entities)) || undefined
523
+ this.prepareEntitiesFromDatastore(entities)
486
524
  } catch (error) {
487
525
  // console.error(error)
488
- metricFailureCounter.inc({ operation: 'update' });
489
- await setImmediate();
490
- throw new DstoreError('datastore.update error', error as Error);
526
+ metricFailureCounter.inc({ operation: 'update' })
527
+ await setImmediate()
528
+ throw new DstoreError('datastore.update error', error as Error)
491
529
  } finally {
492
- metricEnd({ operation: 'update' });
530
+ metricEnd({ operation: 'update' })
493
531
  }
494
- return ret;
532
+ return ret
495
533
  }
496
534
 
497
535
  /** `delete()` is compatible to [Datastore.delete()].
@@ -508,23 +546,23 @@ export class Dstore implements IDstore {
508
546
  * @category Datastore Drop-In
509
547
  */
510
548
  async delete(keys: readonly Key[]): Promise<CommitResponse | undefined> {
511
- assertIsArray(keys);
512
- keys.forEach((key) => assertIsObject(key));
549
+ assertIsArray(keys)
550
+ keys.forEach((key) => assertIsObject(key))
513
551
  keys.forEach((key) =>
514
552
  assert(key.path.length % 2 == 0, `key.path must be complete: ${JSON.stringify(key.path)}`)
515
- );
516
- let ret;
517
- const metricEnd = metricHistogram.startTimer();
553
+ )
554
+ let ret
555
+ const metricEnd = metricHistogram.startTimer()
518
556
  try {
519
- ret = (await this.getDoT().delete(keys)) || undefined;
557
+ ret = (await this.getDoT().delete(keys)) || undefined
520
558
  } catch (error) {
521
- metricFailureCounter.inc({ operation: 'delete' });
522
- await setImmediate();
523
- throw new DstoreError('datastore.delete error', error as Error);
559
+ metricFailureCounter.inc({ operation: 'delete' })
560
+ await setImmediate()
561
+ throw new DstoreError('datastore.delete error', error as Error)
524
562
  } finally {
525
- metricEnd({ operation: 'delete' });
563
+ metricEnd({ operation: 'delete' })
526
564
  }
527
- return ret;
565
+ return ret
528
566
  }
529
567
 
530
568
  /** `createQuery()` creates an "empty" [[Query]] Object.
@@ -537,25 +575,25 @@ export class Dstore implements IDstore {
537
575
  */
538
576
  createQuery(kindName: string): Query {
539
577
  try {
540
- return this.getDoT().createQuery(kindName);
578
+ return this.getDoT().createQuery(kindName)
541
579
  } catch (error) {
542
- throw new DstoreError('datastore.createQuery error', error as Error);
580
+ throw new DstoreError('datastore.createQuery error', error as Error)
543
581
  }
544
582
  }
545
583
 
546
584
  async runQuery(query: Query | Omit<Query, 'run'>): Promise<RunQueryResponse> {
547
- let ret;
548
- const metricEnd = metricHistogram.startTimer();
585
+ let ret
586
+ const metricEnd = metricHistogram.startTimer()
549
587
  try {
550
- const [entities, info]: [Entity[], RunQueryInfo] = await this.getDoT().runQuery(query as Query);
551
- ret = [this.fixKeys(entities), info];
588
+ const [entities, info]: [Entity[], RunQueryInfo] = await this.getDoT().runQuery(query as Query)
589
+ ret = [this.fixKeys(entities), info]
552
590
  } catch (error) {
553
- await setImmediate();
554
- throw new DstoreError('datastore.runQuery error', error as Error);
591
+ await setImmediate()
592
+ throw new DstoreError('datastore.runQuery error', error as Error)
555
593
  } finally {
556
- metricEnd({ operation: 'query' });
594
+ metricEnd({ operation: 'query' })
557
595
  }
558
- return ret as RunQueryResponse;
596
+ return ret as RunQueryResponse
559
597
  }
560
598
 
561
599
  /** `query()` combined [[createQuery]] and [[runQuery]] in a single call.
@@ -568,6 +606,7 @@ export class Dstore implements IDstore {
568
606
  * @param selection selectionList of [[Query]] select() calls.
569
607
  * @param cursor unsupported so far.
570
608
  *
609
+ * @throws [[DstoreError]]
571
610
  * @category Datastore Drop-In
572
611
  */
573
612
  async query(
@@ -578,34 +617,101 @@ export class Dstore implements IDstore {
578
617
  selection: readonly string[] = [],
579
618
  cursor?: string
580
619
  ): Promise<RunQueryResponse> {
581
- assertIsString(kindName);
582
- assertIsArray(filters);
583
- assertIsNumber(limit);
620
+ assertIsString(kindName)
621
+ assertIsArray(filters)
622
+ assertIsNumber(limit)
584
623
  try {
585
- const q = this.createQuery(kindName);
624
+ const q = this.createQuery(kindName)
586
625
  for (const filterSpec of filters) {
587
- assertIsObject(filterSpec);
626
+ assertIsObject(filterSpec)
588
627
  // @ts-ignore
589
- q.filter(new PropertyFilter(...filterSpec));
628
+ q.filter(new PropertyFilter(...filterSpec))
590
629
  }
591
630
  for (const orderField of ordering) {
592
- q.order(orderField);
631
+ q.order(orderField)
593
632
  }
594
633
  if (limit > 0) {
595
- q.limit(limit);
634
+ q.limit(limit)
596
635
  }
597
636
  if (selection.length > 0) {
598
- q.select(selection as any);
637
+ q.select(selection as any)
638
+ }
639
+ const ret = await this.getDoT().runQuery(q)
640
+ return [this.fixKeys(ret[0]), ret[1]]
641
+ } catch (error) {
642
+ await setImmediate()
643
+ throw new DstoreError('datastore.query error', error as Error, {
644
+ kindName,
645
+ filters,
646
+ limit,
647
+ ordering,
648
+ })
649
+ }
650
+ }
651
+
652
+
653
+ /** `iterate()` is a modernized version of `query()`.
654
+ *
655
+ * It takes a Parameter object and returns an AsyncIterable.
656
+ * Entities returned have been processed by `fixKeys()`.
657
+ *
658
+ * Can be used with `for await` loops like this:
659
+ *
660
+ * ```typescript
661
+ * for await (const entity of dstore.iterate({ kindName: 'p_ReservierungsAbruf', filters: [['verbucht', '=', true]]})) {
662
+ * console.log(entity)
663
+ * }
664
+ * ```
665
+ *
666
+ * @param kindName Name of the [[Datastore]][Kind](https://cloud.google.com/datastore/docs/concepts/entities#kinds_and_identifiers) ("Table") which should be searched.
667
+ * @param filters List of [[Query]] filter() calls.
668
+ * @param limit Maximum Number of Results to return.
669
+ * @param ordering List of [[Query]] order() calls.
670
+ * @param selection selectionList of [[Query]] select() calls.
671
+ *
672
+ * @throws [[DstoreError]]
673
+ * @category Additional
674
+ */
675
+ async * iterate({
676
+ kindName,
677
+ filters = [],
678
+ limit = 2500,
679
+ ordering = [],
680
+ selection = [],
681
+ }: IIterateParams): AsyncIterable<IDstoreEntry> {
682
+ assertIsString(kindName)
683
+ assertIsArray(filters)
684
+ assertIsNumber(limit)
685
+ try {
686
+ const q = this.getDoT().createQuery(kindName)
687
+ for (const filterSpec of filters) {
688
+ assertIsObject(filterSpec)
689
+ // @ts-ignore
690
+ q.filter(new PropertyFilter(...filterSpec))
691
+ }
692
+ for (const orderField of ordering) {
693
+ q.order(orderField)
694
+ }
695
+ if (limit > 0) {
696
+ q.limit(limit)
697
+ }
698
+ if (selection.length > 0) {
699
+ q.select(selection as any)
700
+ }
701
+ for await (const entity of q.runStream()) {
702
+ const ret = this.fixKeys([entity])[0]
703
+ assertIsDefined(ret, 'datastore.iterate: entity is undefined')
704
+ assertIsKey(ret[Datastore.KEY])
705
+ yield ret as IDstoreEntryWithKey
599
706
  }
600
- return await this.runQuery(q);
601
707
  } catch (error) {
602
- await setImmediate();
708
+ await setImmediate()
603
709
  throw new DstoreError('datastore.query error', error as Error, {
604
710
  kindName,
605
711
  filters,
606
712
  limit,
607
713
  ordering,
608
- });
714
+ })
609
715
  }
610
716
  }
611
717
 
@@ -621,10 +727,10 @@ export class Dstore implements IDstore {
621
727
  * In fact the generated ID is namespaced via an incomplete [[Key]] of the given Kind.
622
728
  */
623
729
  async allocateOneId(kindName = 'Numbering'): Promise<string> {
624
- assertIsString(kindName);
625
- const ret = (await this.datastore.allocateIds(this.key([kindName]), 1))[0][0].id;
626
- assertIsString(ret);
627
- return ret;
730
+ assertIsString(kindName)
731
+ const ret = (await this.datastore.allocateIds(this.key([kindName]), 1))[0][0].id
732
+ assertIsString(ret)
733
+ return ret
628
734
  }
629
735
 
630
736
  /** This tries to give high level access to transactions.
@@ -642,27 +748,27 @@ export class Dstore implements IDstore {
642
748
  Most Applications today are running on "Firestore in Datastore Mode". Beware that the Datastore-Emulator fails with `error: 3 INVALID_ARGUMENT: Only ancestor queries are allowed inside transactions.` during [[runQuery]] while the Datastore on Google Infrastructure does not have such an restriction anymore as of 2022.
643
749
  */
644
750
  async runInTransaction<T>(func: () => Promise<T>): Promise<T> {
645
- let ret;
646
- const transaction: Transaction = this.datastore.transaction();
751
+ let ret
752
+ const transaction: Transaction = this.datastore.transaction()
647
753
  await transactionAsyncLocalStorage.run(transaction, async () => {
648
- const [transactionInfo, transactionRunApiResponse] = await transaction.run();
649
- let commitApiResponse;
754
+ const [transactionInfo, transactionRunApiResponse] = await transaction.run()
755
+ let commitApiResponse
650
756
  try {
651
- ret = await func();
757
+ ret = await func()
652
758
  } catch (error) {
653
- const rollbackInfo = await transaction.rollback();
759
+ const rollbackInfo = await transaction.rollback()
654
760
  debug(
655
761
  'Transaction failed, rollback initiated: %O %O %O %O',
656
762
  transactionInfo,
657
763
  transactionRunApiResponse,
658
764
  rollbackInfo,
659
765
  error
660
- );
661
- await setImmediate();
662
- throw new DstoreError('datastore.transaction execution error', error as Error);
766
+ )
767
+ await setImmediate()
768
+ throw new DstoreError('datastore.transaction execution error', error as Error)
663
769
  }
664
770
  try {
665
- commitApiResponse = (await transaction.commit())[0];
771
+ commitApiResponse = (await transaction.commit())[0]
666
772
  } catch (error) {
667
773
  debug(
668
774
  'Transaction commit failed: %O %O %O %O ret: %O',
@@ -671,36 +777,36 @@ export class Dstore implements IDstore {
671
777
  commitApiResponse,
672
778
  error,
673
779
  ret
674
- );
675
- await setImmediate();
676
- throw new DstoreError('datastore.transaction execution error', error as Error);
780
+ )
781
+ await setImmediate()
782
+ throw new DstoreError('datastore.transaction execution error', error as Error)
677
783
  }
678
- });
679
- return ret as T;
784
+ })
785
+ return ret as T
680
786
  }
681
787
  }
682
788
 
683
789
  export class DstoreError extends Error {
684
- public readonly extensions: Record<string, unknown>;
685
- public readonly originalError: Error | undefined;
686
- readonly [key: string]: unknown;
790
+ public readonly extensions: Record<string, unknown>
791
+ public readonly originalError: Error | undefined
792
+ readonly [key: string]: unknown
687
793
 
688
794
  constructor(message: string, originalError: Error | undefined, extensions?: Record<string, unknown>) {
689
- super(`${message}: ${originalError?.message}`);
795
+ super(`${message}: ${originalError?.message}`)
690
796
 
691
797
  // if no name provided, use the default. defineProperty ensures that it stays non-enumerable
692
798
  if (!this.name) {
693
- Object.defineProperty(this, 'name', { value: 'DstoreError' });
799
+ Object.defineProperty(this, 'name', { value: 'DstoreError' })
694
800
  }
695
801
  // metadata: Metadata { internalRepr: Map(0) {}, options: {} },
696
- this.originalError = originalError;
697
- this.extensions = { ...extensions };
802
+ this.originalError = originalError
803
+ this.extensions = { ...extensions }
698
804
  this.stack =
699
805
  (this.stack?.split('\n')[0] || '') +
700
806
  '\n' +
701
807
  (originalError?.stack?.split('\n')?.slice(1)?.join('\n') || '') +
702
808
  '\n' +
703
- (this.stack?.split('\n')?.slice(1)?.join('\n') || '');
809
+ (this.stack?.split('\n')?.slice(1)?.join('\n') || '')
704
810
 
705
811
  // These are usually present on Datastore Errors
706
812
  // logger.error({ err: originalError, extensions }, message);