datastore-api 6.0.2 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/datastore-api.cjs.development.js +654 -419
- package/dist/datastore-api.cjs.development.js.map +1 -1
- package/dist/datastore-api.cjs.production.min.js +1 -1
- package/dist/datastore-api.cjs.production.min.js.map +1 -1
- package/dist/datastore-api.esm.js +655 -421
- package/dist/datastore-api.esm.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/lib/assert.d.ts +10 -0
- package/dist/lib/dstore-api.d.ts +25 -2
- package/package.json +100 -102
- package/src/index.ts +2 -1
- package/src/lib/assert.ts +52 -0
- package/src/lib/dstore-api.ts +254 -189
package/src/lib/dstore-api.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* dstore.ts - Datastore Compatibility layer
|
|
3
|
-
* Try to get a smoother
|
|
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,19 @@ 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
31
|
|
|
32
32
|
/** @ignore */
|
|
33
|
-
export { Datastore, Key, PathType, Query, Transaction } from '@google-cloud/datastore'
|
|
33
|
+
export { Datastore, Key, PathType, Query, Transaction } from '@google-cloud/datastore'
|
|
34
34
|
|
|
35
35
|
/** @ignore */
|
|
36
|
-
const debug = Debug('ds:api')
|
|
36
|
+
const debug = Debug('ds:api')
|
|
37
37
|
|
|
38
38
|
/** @ignore */
|
|
39
|
-
const transactionAsyncLocalStorage = new AsyncLocalStorage()
|
|
39
|
+
const transactionAsyncLocalStorage = new AsyncLocalStorage()
|
|
40
40
|
|
|
41
41
|
// for HMR
|
|
42
42
|
promClient.register.removeSingleMetric('dstore_requests_seconds')
|
|
@@ -46,25 +46,25 @@ const metricHistogram = new promClient.Histogram({
|
|
|
46
46
|
name: 'dstore_requests_seconds',
|
|
47
47
|
help: 'How long did Datastore operations take?',
|
|
48
48
|
labelNames: ['operation'],
|
|
49
|
-
})
|
|
49
|
+
})
|
|
50
50
|
const metricFailureCounter = new promClient.Counter({
|
|
51
51
|
name: 'dstore_failures_total',
|
|
52
52
|
help: 'How many Datastore operations failed?',
|
|
53
53
|
labelNames: ['operation'],
|
|
54
|
-
})
|
|
54
|
+
})
|
|
55
55
|
|
|
56
56
|
/** Use instead of Datastore.KEY
|
|
57
57
|
*
|
|
58
58
|
* Even better: use `_key` instead.
|
|
59
59
|
*/
|
|
60
|
-
export const KEYSYM = Datastore.KEY
|
|
60
|
+
export const KEYSYM = Datastore.KEY
|
|
61
61
|
|
|
62
|
-
export type IGqlFilterTypes = boolean | string | number
|
|
62
|
+
export type IGqlFilterTypes = boolean | string | number
|
|
63
63
|
|
|
64
64
|
export type IGqlFilterSpec = {
|
|
65
|
-
readonly eq: IGqlFilterTypes
|
|
66
|
-
}
|
|
67
|
-
export type TGqlFilterList = Array<[string, Operator, DstorePropertyValues]
|
|
65
|
+
readonly eq: IGqlFilterTypes
|
|
66
|
+
}
|
|
67
|
+
export type TGqlFilterList = Array<[string, Operator, DstorePropertyValues]>
|
|
68
68
|
|
|
69
69
|
/** Define what can be written into the Datastore */
|
|
70
70
|
export type DstorePropertyValues =
|
|
@@ -77,11 +77,11 @@ export type DstorePropertyValues =
|
|
|
77
77
|
| Buffer
|
|
78
78
|
| Key
|
|
79
79
|
| DstorePropertyValues[]
|
|
80
|
-
| { [key: string]: DstorePropertyValues }
|
|
80
|
+
| { [key: string]: DstorePropertyValues }
|
|
81
81
|
|
|
82
82
|
export interface IDstoreEntryWithoutKey {
|
|
83
83
|
/** All User Data stored in the Datastore */
|
|
84
|
-
[key: string]: DstorePropertyValues
|
|
84
|
+
[key: string]: DstorePropertyValues
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/** Represents what is actually stored inside the Datastore, called "Entity" by Google
|
|
@@ -89,39 +89,47 @@ export interface IDstoreEntryWithoutKey {
|
|
|
89
89
|
*/
|
|
90
90
|
export interface IDstoreEntry extends IDstoreEntryWithoutKey {
|
|
91
91
|
/* Datastore Key provided by [@google-cloud/datastore](https://github.com/googleapis/nodejs-datastore#readme) */
|
|
92
|
-
readonly [Datastore.KEY]?: Key
|
|
92
|
+
readonly [Datastore.KEY]?: Key
|
|
93
93
|
/** [Datastore.KEY] key */
|
|
94
|
-
_keyStr: string
|
|
94
|
+
_keyStr: string
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
/** Represents the thing you pass to the save method. Also called "Entity" by Google */
|
|
98
98
|
export type DstoreSaveEntity = {
|
|
99
|
-
key: Key
|
|
99
|
+
key: Key
|
|
100
100
|
data: Omit<IDstoreEntry, '_keyStr' | Datastore['KEY']> &
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
method?: 'insert' | 'update' | 'upsert'
|
|
106
|
-
excludeLargeProperties?: boolean
|
|
107
|
-
excludeFromIndexes?: readonly string[]
|
|
108
|
-
}
|
|
101
|
+
Partial<{
|
|
102
|
+
_keyStr: string | undefined;
|
|
103
|
+
[Datastore.KEY]: Key
|
|
104
|
+
}>
|
|
105
|
+
method?: 'insert' | 'update' | 'upsert'
|
|
106
|
+
excludeLargeProperties?: boolean
|
|
107
|
+
excludeFromIndexes?: readonly string[]
|
|
108
|
+
}
|
|
109
109
|
|
|
110
|
+
export interface IIterateParams {
|
|
111
|
+
kindName: string,
|
|
112
|
+
filters?: TGqlFilterList,
|
|
113
|
+
limit?: number,
|
|
114
|
+
ordering?: readonly string[],
|
|
115
|
+
selection?: readonly string[],
|
|
116
|
+
}
|
|
110
117
|
type IDstore = {
|
|
111
118
|
/** 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
|
|
119
|
+
readonly datastore: Datastore
|
|
120
|
+
key: (path: ReadonlyArray<PathType>) => Key
|
|
121
|
+
keyFromSerialized: (text: string) => Key
|
|
122
|
+
keySerialize: (key: Key) => string
|
|
123
|
+
readKey: (entry: IDstoreEntry) => Key
|
|
124
|
+
get: (key: Key) => Promise<IDstoreEntry | null>
|
|
125
|
+
getMulti: (keys: ReadonlyArray<Key>) => Promise<ReadonlyArray<IDstoreEntry | null>>
|
|
126
|
+
set: (key: Key, entry: IDstoreEntry) => Promise<Key>
|
|
127
|
+
save: (entities: readonly DstoreSaveEntity[]) => Promise<CommitResponse | undefined>
|
|
128
|
+
insert: (entities: readonly DstoreSaveEntity[]) => Promise<CommitResponse | undefined>
|
|
129
|
+
update: (entities: readonly DstoreSaveEntity[]) => Promise<CommitResponse | undefined>
|
|
130
|
+
delete: (keys: readonly Key[]) => Promise<CommitResponse | undefined>
|
|
131
|
+
createQuery: (kind: string) => Query
|
|
132
|
+
runQuery: (query: Query | Omit<Query, 'run'>) => Promise<RunQueryResponse>
|
|
125
133
|
query: (
|
|
126
134
|
kind: string,
|
|
127
135
|
filters?: TGqlFilterList,
|
|
@@ -129,11 +137,11 @@ type IDstore = {
|
|
|
129
137
|
ordering?: readonly string[],
|
|
130
138
|
selection?: readonly string[],
|
|
131
139
|
cursor?: string
|
|
132
|
-
) => Promise<RunQueryResponse
|
|
133
|
-
|
|
134
|
-
allocateOneId: (kindName: string) => Promise<string
|
|
135
|
-
runInTransaction: <T>(func: { (): Promise<T>; (): T }) => Promise<T
|
|
136
|
-
}
|
|
140
|
+
) => Promise<RunQueryResponse>
|
|
141
|
+
iterate: (options: IIterateParams) => AsyncIterable<IDstoreEntry>
|
|
142
|
+
allocateOneId: (kindName: string) => Promise<string>
|
|
143
|
+
runInTransaction: <T>(func: { (): Promise<T>; (): T }) => Promise<T>
|
|
144
|
+
}
|
|
137
145
|
|
|
138
146
|
/** 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
147
|
|
|
@@ -179,13 +187,13 @@ export class Dstore implements IDstore {
|
|
|
179
187
|
```
|
|
180
188
|
*/
|
|
181
189
|
constructor(readonly datastore: Datastore, readonly projectId?: string, readonly logger?: string) {
|
|
182
|
-
assertIsObject(datastore)
|
|
183
|
-
this.engines.push(this.engine)
|
|
190
|
+
assertIsObject(datastore)
|
|
191
|
+
this.engines.push(this.engine)
|
|
184
192
|
}
|
|
185
193
|
|
|
186
194
|
/** Gets the Datastore or the current Transaction. */
|
|
187
195
|
private getDoT(): Transaction | Datastore {
|
|
188
|
-
return (transactionAsyncLocalStorage.getStore() as Transaction) || this.datastore
|
|
196
|
+
return (transactionAsyncLocalStorage.getStore() as Transaction) || this.datastore
|
|
189
197
|
}
|
|
190
198
|
|
|
191
199
|
/** `key()` creates a [[Key]] Object from a path.
|
|
@@ -197,7 +205,7 @@ export class Dstore implements IDstore {
|
|
|
197
205
|
* @category Datastore Drop-In
|
|
198
206
|
*/
|
|
199
207
|
key(path: readonly PathType[]): Key {
|
|
200
|
-
return this.datastore.key(path as Writable<typeof path>)
|
|
208
|
+
return this.datastore.key(path as Writable<typeof path>)
|
|
201
209
|
}
|
|
202
210
|
|
|
203
211
|
/** `keyFromSerialized()` serializes [[Key]] to a string.
|
|
@@ -209,7 +217,7 @@ export class Dstore implements IDstore {
|
|
|
209
217
|
* @category Datastore Drop-In
|
|
210
218
|
*/
|
|
211
219
|
keySerialize(key: Key): string {
|
|
212
|
-
return key ? this.urlSaveKey.legacyEncode(this.projectId ?? '', key) : ''
|
|
220
|
+
return key ? this.urlSaveKey.legacyEncode(this.projectId ?? '', key) : ''
|
|
213
221
|
}
|
|
214
222
|
|
|
215
223
|
/** `keyFromSerialized()` deserializes a string created with [[keySerialize]] to a [[Key]].
|
|
@@ -219,7 +227,7 @@ export class Dstore implements IDstore {
|
|
|
219
227
|
* @category Datastore Drop-In
|
|
220
228
|
*/
|
|
221
229
|
keyFromSerialized(text: string): Key {
|
|
222
|
-
return this.urlSaveKey.legacyDecode(text)
|
|
230
|
+
return this.urlSaveKey.legacyDecode(text)
|
|
223
231
|
}
|
|
224
232
|
|
|
225
233
|
/** `readKey()` extracts the [[Key]] from an [[IDstoreEntry]].
|
|
@@ -230,20 +238,20 @@ export class Dstore implements IDstore {
|
|
|
230
238
|
* @category Additional
|
|
231
239
|
*/
|
|
232
240
|
readKey(ent: IDstoreEntry): Key {
|
|
233
|
-
assertIsObject(ent)
|
|
234
|
-
let ret = ent[Datastore.KEY]
|
|
241
|
+
assertIsObject(ent)
|
|
242
|
+
let ret = ent[Datastore.KEY]
|
|
235
243
|
if (ent._keyStr && !ret) {
|
|
236
|
-
ret = this.keyFromSerialized(ent._keyStr)
|
|
244
|
+
ret = this.keyFromSerialized(ent._keyStr)
|
|
237
245
|
}
|
|
238
246
|
assertIsObject(
|
|
239
247
|
ret,
|
|
240
248
|
'entity[Datastore.KEY]/entity._keyStr',
|
|
241
249
|
`Entity is missing the datastore Key: ${JSON.stringify(ent)}`
|
|
242
|
-
)
|
|
243
|
-
return ret
|
|
250
|
+
)
|
|
251
|
+
return ret
|
|
244
252
|
}
|
|
245
253
|
|
|
246
|
-
/** `fixKeys()` is called for all [[IDstoreEntry]]
|
|
254
|
+
/** `fixKeys()` is called for all [[IDstoreEntry]] returned from [[Dstore]].
|
|
247
255
|
*
|
|
248
256
|
* Is ensures that besides `entity[Datastore.KEY]` there is `_keyStr` to be leveraged by [[readKey]].
|
|
249
257
|
*
|
|
@@ -254,13 +262,13 @@ export class Dstore implements IDstore {
|
|
|
254
262
|
): Array<IDstoreEntry | undefined> {
|
|
255
263
|
entities.forEach((x) => {
|
|
256
264
|
if (!!x?.[Datastore.KEY] && x[Datastore.KEY]) {
|
|
257
|
-
assertIsDefined(x[Datastore.KEY])
|
|
258
|
-
assertIsObject(x[Datastore.KEY])
|
|
265
|
+
assertIsDefined(x[Datastore.KEY])
|
|
266
|
+
assertIsObject(x[Datastore.KEY])
|
|
259
267
|
// Old TypesScript has problems with symbols as a property
|
|
260
|
-
x._keyStr = this.keySerialize(x[Datastore.KEY] as Key)
|
|
268
|
+
x._keyStr = this.keySerialize(x[Datastore.KEY] as Key)
|
|
261
269
|
}
|
|
262
|
-
})
|
|
263
|
-
return entities as Array<IDstoreEntry | undefined
|
|
270
|
+
})
|
|
271
|
+
return entities as Array<IDstoreEntry | undefined>
|
|
264
272
|
}
|
|
265
273
|
|
|
266
274
|
/** `get()` reads a [[IDstoreEntry]] from the Datastore.
|
|
@@ -279,11 +287,11 @@ export class Dstore implements IDstore {
|
|
|
279
287
|
* @category Datastore Drop-In
|
|
280
288
|
*/
|
|
281
289
|
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
|
|
290
|
+
assertIsObject(key)
|
|
291
|
+
assert(!Array.isArray(key))
|
|
292
|
+
assert(key.path.length % 2 == 0, `key.path must be complete: ${JSON.stringify(key.path)}`)
|
|
293
|
+
const result = await this.getMulti([key])
|
|
294
|
+
return result?.[0] || null
|
|
287
295
|
}
|
|
288
296
|
|
|
289
297
|
/** `getMulti()` reads several [[IDstoreEntry]]s from the Datastore.
|
|
@@ -305,28 +313,28 @@ export class Dstore implements IDstore {
|
|
|
305
313
|
*/
|
|
306
314
|
async getMulti(keys: readonly Key[]): Promise<Array<IDstoreEntry | null>> {
|
|
307
315
|
// assertIsArray(keys);
|
|
308
|
-
let results: (IDstoreEntry | null | undefined)[]
|
|
309
|
-
const metricEnd = metricHistogram.startTimer()
|
|
316
|
+
let results: (IDstoreEntry | null | undefined)[]
|
|
317
|
+
const metricEnd = metricHistogram.startTimer()
|
|
310
318
|
try {
|
|
311
319
|
results = this.fixKeys(
|
|
312
320
|
keys.length > 0 ? (await this.getDoT().get(keys as Writable<typeof keys>))?.[0] : []
|
|
313
|
-
)
|
|
321
|
+
)
|
|
314
322
|
} catch (error) {
|
|
315
|
-
metricFailureCounter.inc({ operation: 'get' })
|
|
316
|
-
await setImmediate()
|
|
317
|
-
throw new DstoreError('datastore.getMulti error', error as Error, { keys })
|
|
323
|
+
metricFailureCounter.inc({ operation: 'get' })
|
|
324
|
+
await setImmediate()
|
|
325
|
+
throw new DstoreError('datastore.getMulti error', error as Error, { keys })
|
|
318
326
|
} finally {
|
|
319
|
-
metricEnd({ operation: 'get' })
|
|
327
|
+
metricEnd({ operation: 'get' })
|
|
320
328
|
}
|
|
321
329
|
|
|
322
330
|
// 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> = {}
|
|
331
|
+
assertIsArray(results)
|
|
332
|
+
const entities = results as IDstoreEntry[]
|
|
333
|
+
const entitiesByKey: Record<string, IDstoreEntry> = {}
|
|
326
334
|
entities.forEach((entity) => {
|
|
327
|
-
entitiesByKey[JSON.stringify(entity[Datastore.KEY])] = entity
|
|
328
|
-
})
|
|
329
|
-
return keys.map((key) => entitiesByKey[JSON.stringify(key)] || null)
|
|
335
|
+
entitiesByKey[JSON.stringify(entity[Datastore.KEY])] = entity
|
|
336
|
+
})
|
|
337
|
+
return keys.map((key) => entitiesByKey[JSON.stringify(key)] || null)
|
|
330
338
|
}
|
|
331
339
|
|
|
332
340
|
/** `set()` is addition to [[Datastore]]. It provides a classic Key-value Interface.
|
|
@@ -346,11 +354,11 @@ export class Dstore implements IDstore {
|
|
|
346
354
|
* @category Additional
|
|
347
355
|
*/
|
|
348
356
|
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
|
|
357
|
+
assertIsObject(key)
|
|
358
|
+
assertIsObject(data)
|
|
359
|
+
const saveEntity = { key, data: { ...data, _keyStr: undefined } }
|
|
360
|
+
await this.save([saveEntity])
|
|
361
|
+
return saveEntity.key
|
|
354
362
|
}
|
|
355
363
|
|
|
356
364
|
/** `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,32 +395,32 @@ export class Dstore implements IDstore {
|
|
|
387
395
|
* @category Datastore Drop-In
|
|
388
396
|
*/
|
|
389
397
|
async save(entities: readonly DstoreSaveEntity[]): Promise<CommitResponse | undefined> {
|
|
390
|
-
assertIsArray(entities)
|
|
391
|
-
let ret: CommitResponse | undefined
|
|
392
|
-
const metricEnd = metricHistogram.startTimer()
|
|
398
|
+
assertIsArray(entities)
|
|
399
|
+
let ret: CommitResponse | undefined
|
|
400
|
+
const metricEnd = metricHistogram.startTimer()
|
|
393
401
|
try {
|
|
394
402
|
// Within Transaction we don't get any answer here!
|
|
395
403
|
// [ { mutationResults: [ [Object], [Object] ], indexUpdates: 51 } ]
|
|
396
404
|
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 }
|
|
405
|
+
assertIsObject(e.key)
|
|
406
|
+
assertIsObject(e.data)
|
|
407
|
+
this.fixKeys([e.data])
|
|
408
|
+
e.excludeLargeProperties = e.excludeLargeProperties === undefined ? true : e.excludeLargeProperties
|
|
409
|
+
e.data = { ...e.data, _keyStr: undefined }
|
|
402
410
|
}
|
|
403
|
-
ret = (await this.getDoT().save(entities)) || undefined
|
|
411
|
+
ret = (await this.getDoT().save(entities)) || undefined
|
|
404
412
|
for (const e of entities) {
|
|
405
|
-
e.data[Datastore.KEY] = e.key
|
|
406
|
-
this.fixKeys([e.data])
|
|
413
|
+
e.data[Datastore.KEY] = e.key
|
|
414
|
+
this.fixKeys([e.data])
|
|
407
415
|
}
|
|
408
416
|
} catch (error) {
|
|
409
|
-
metricFailureCounter.inc({ operation: 'save' })
|
|
410
|
-
await setImmediate()
|
|
411
|
-
throw new DstoreError('datastore.save error', error as Error)
|
|
417
|
+
metricFailureCounter.inc({ operation: 'save' })
|
|
418
|
+
await setImmediate()
|
|
419
|
+
throw new DstoreError('datastore.save error', error as Error)
|
|
412
420
|
} finally {
|
|
413
|
-
metricEnd({ operation: 'save' })
|
|
421
|
+
metricEnd({ operation: 'save' })
|
|
414
422
|
}
|
|
415
|
-
return ret
|
|
423
|
+
return ret
|
|
416
424
|
}
|
|
417
425
|
|
|
418
426
|
/** `insert()` is compatible to [Datastore.insert()](https://cloud.google.com/nodejs/docs/reference/datastore/latest/datastore/datastore#_google_cloud_datastore_Datastore_insert_member_1_).
|
|
@@ -434,20 +442,20 @@ export class Dstore implements IDstore {
|
|
|
434
442
|
* @category Datastore Drop-In
|
|
435
443
|
*/
|
|
436
444
|
async insert(entities: readonly DstoreSaveEntity[]): Promise<CommitResponse | undefined> {
|
|
437
|
-
assertIsArray(entities)
|
|
438
|
-
let ret: CommitResponse | undefined
|
|
439
|
-
const metricEnd = metricHistogram.startTimer()
|
|
445
|
+
assertIsArray(entities)
|
|
446
|
+
let ret: CommitResponse | undefined
|
|
447
|
+
const metricEnd = metricHistogram.startTimer()
|
|
440
448
|
try {
|
|
441
|
-
ret = (await this.getDoT().insert(entities)) || undefined
|
|
449
|
+
ret = (await this.getDoT().insert(entities)) || undefined
|
|
442
450
|
} catch (error) {
|
|
443
451
|
// console.error(error)
|
|
444
|
-
metricFailureCounter.inc({ operation: 'insert' })
|
|
445
|
-
await setImmediate()
|
|
446
|
-
throw new DstoreError('datastore.insert error', error as Error)
|
|
452
|
+
metricFailureCounter.inc({ operation: 'insert' })
|
|
453
|
+
await setImmediate()
|
|
454
|
+
throw new DstoreError('datastore.insert error', error as Error)
|
|
447
455
|
} finally {
|
|
448
|
-
metricEnd({ operation: 'insert' })
|
|
456
|
+
metricEnd({ operation: 'insert' })
|
|
449
457
|
}
|
|
450
|
-
return ret
|
|
458
|
+
return ret
|
|
451
459
|
}
|
|
452
460
|
|
|
453
461
|
/** `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 +477,29 @@ export class Dstore implements IDstore {
|
|
|
469
477
|
* @category Datastore Drop-In
|
|
470
478
|
*/
|
|
471
479
|
async update(entities: readonly DstoreSaveEntity[]): Promise<CommitResponse | undefined> {
|
|
472
|
-
assertIsArray(entities)
|
|
480
|
+
assertIsArray(entities)
|
|
473
481
|
|
|
474
|
-
entities.forEach((entity) => assertIsObject(entity.key))
|
|
482
|
+
entities.forEach((entity) => assertIsObject(entity.key))
|
|
475
483
|
entities.forEach((entity) =>
|
|
476
484
|
assert(
|
|
477
485
|
entity.key.path.length % 2 == 0,
|
|
478
486
|
`entity.key.path must be complete: ${JSON.stringify([entity.key.path, entity])}`
|
|
479
487
|
)
|
|
480
|
-
)
|
|
481
|
-
let ret: CommitResponse | undefined
|
|
482
|
-
const metricEnd = metricHistogram.startTimer()
|
|
488
|
+
)
|
|
489
|
+
let ret: CommitResponse | undefined
|
|
490
|
+
const metricEnd = metricHistogram.startTimer()
|
|
483
491
|
|
|
484
492
|
try {
|
|
485
|
-
ret = (await this.getDoT().update(entities)) || undefined
|
|
493
|
+
ret = (await this.getDoT().update(entities)) || undefined
|
|
486
494
|
} catch (error) {
|
|
487
495
|
// console.error(error)
|
|
488
|
-
metricFailureCounter.inc({ operation: 'update' })
|
|
489
|
-
await setImmediate()
|
|
490
|
-
throw new DstoreError('datastore.update error', error as Error)
|
|
496
|
+
metricFailureCounter.inc({ operation: 'update' })
|
|
497
|
+
await setImmediate()
|
|
498
|
+
throw new DstoreError('datastore.update error', error as Error)
|
|
491
499
|
} finally {
|
|
492
|
-
metricEnd({ operation: 'update' })
|
|
500
|
+
metricEnd({ operation: 'update' })
|
|
493
501
|
}
|
|
494
|
-
return ret
|
|
502
|
+
return ret
|
|
495
503
|
}
|
|
496
504
|
|
|
497
505
|
/** `delete()` is compatible to [Datastore.delete()].
|
|
@@ -508,23 +516,23 @@ export class Dstore implements IDstore {
|
|
|
508
516
|
* @category Datastore Drop-In
|
|
509
517
|
*/
|
|
510
518
|
async delete(keys: readonly Key[]): Promise<CommitResponse | undefined> {
|
|
511
|
-
assertIsArray(keys)
|
|
512
|
-
keys.forEach((key) => assertIsObject(key))
|
|
519
|
+
assertIsArray(keys)
|
|
520
|
+
keys.forEach((key) => assertIsObject(key))
|
|
513
521
|
keys.forEach((key) =>
|
|
514
522
|
assert(key.path.length % 2 == 0, `key.path must be complete: ${JSON.stringify(key.path)}`)
|
|
515
|
-
)
|
|
516
|
-
let ret
|
|
517
|
-
const metricEnd = metricHistogram.startTimer()
|
|
523
|
+
)
|
|
524
|
+
let ret
|
|
525
|
+
const metricEnd = metricHistogram.startTimer()
|
|
518
526
|
try {
|
|
519
|
-
ret = (await this.getDoT().delete(keys)) || undefined
|
|
527
|
+
ret = (await this.getDoT().delete(keys)) || undefined
|
|
520
528
|
} catch (error) {
|
|
521
|
-
metricFailureCounter.inc({ operation: 'delete' })
|
|
522
|
-
await setImmediate()
|
|
523
|
-
throw new DstoreError('datastore.delete error', error as Error)
|
|
529
|
+
metricFailureCounter.inc({ operation: 'delete' })
|
|
530
|
+
await setImmediate()
|
|
531
|
+
throw new DstoreError('datastore.delete error', error as Error)
|
|
524
532
|
} finally {
|
|
525
|
-
metricEnd({ operation: 'delete' })
|
|
533
|
+
metricEnd({ operation: 'delete' })
|
|
526
534
|
}
|
|
527
|
-
return ret
|
|
535
|
+
return ret
|
|
528
536
|
}
|
|
529
537
|
|
|
530
538
|
/** `createQuery()` creates an "empty" [[Query]] Object.
|
|
@@ -537,25 +545,25 @@ export class Dstore implements IDstore {
|
|
|
537
545
|
*/
|
|
538
546
|
createQuery(kindName: string): Query {
|
|
539
547
|
try {
|
|
540
|
-
return this.getDoT().createQuery(kindName)
|
|
548
|
+
return this.getDoT().createQuery(kindName)
|
|
541
549
|
} catch (error) {
|
|
542
|
-
throw new DstoreError('datastore.createQuery error', error as Error)
|
|
550
|
+
throw new DstoreError('datastore.createQuery error', error as Error)
|
|
543
551
|
}
|
|
544
552
|
}
|
|
545
553
|
|
|
546
554
|
async runQuery(query: Query | Omit<Query, 'run'>): Promise<RunQueryResponse> {
|
|
547
|
-
let ret
|
|
548
|
-
const metricEnd = metricHistogram.startTimer()
|
|
555
|
+
let ret
|
|
556
|
+
const metricEnd = metricHistogram.startTimer()
|
|
549
557
|
try {
|
|
550
|
-
const [entities, info]: [Entity[], RunQueryInfo] = await this.getDoT().runQuery(query as Query)
|
|
551
|
-
ret = [this.fixKeys(entities), info]
|
|
558
|
+
const [entities, info]: [Entity[], RunQueryInfo] = await this.getDoT().runQuery(query as Query)
|
|
559
|
+
ret = [this.fixKeys(entities), info]
|
|
552
560
|
} catch (error) {
|
|
553
|
-
await setImmediate()
|
|
554
|
-
throw new DstoreError('datastore.runQuery error', error as Error)
|
|
561
|
+
await setImmediate()
|
|
562
|
+
throw new DstoreError('datastore.runQuery error', error as Error)
|
|
555
563
|
} finally {
|
|
556
|
-
metricEnd({ operation: 'query' })
|
|
564
|
+
metricEnd({ operation: 'query' })
|
|
557
565
|
}
|
|
558
|
-
return ret as RunQueryResponse
|
|
566
|
+
return ret as RunQueryResponse
|
|
559
567
|
}
|
|
560
568
|
|
|
561
569
|
/** `query()` combined [[createQuery]] and [[runQuery]] in a single call.
|
|
@@ -568,6 +576,7 @@ export class Dstore implements IDstore {
|
|
|
568
576
|
* @param selection selectionList of [[Query]] select() calls.
|
|
569
577
|
* @param cursor unsupported so far.
|
|
570
578
|
*
|
|
579
|
+
* @throws [[DstoreError]]
|
|
571
580
|
* @category Datastore Drop-In
|
|
572
581
|
*/
|
|
573
582
|
async query(
|
|
@@ -578,34 +587,90 @@ export class Dstore implements IDstore {
|
|
|
578
587
|
selection: readonly string[] = [],
|
|
579
588
|
cursor?: string
|
|
580
589
|
): Promise<RunQueryResponse> {
|
|
581
|
-
assertIsString(kindName)
|
|
582
|
-
assertIsArray(filters)
|
|
583
|
-
assertIsNumber(limit)
|
|
590
|
+
assertIsString(kindName)
|
|
591
|
+
assertIsArray(filters)
|
|
592
|
+
assertIsNumber(limit)
|
|
584
593
|
try {
|
|
585
|
-
const q = this.createQuery(kindName)
|
|
594
|
+
const q = this.createQuery(kindName)
|
|
586
595
|
for (const filterSpec of filters) {
|
|
587
|
-
assertIsObject(filterSpec)
|
|
596
|
+
assertIsObject(filterSpec)
|
|
588
597
|
// @ts-ignore
|
|
589
|
-
q.filter(new PropertyFilter(...filterSpec))
|
|
598
|
+
q.filter(new PropertyFilter(...filterSpec))
|
|
590
599
|
}
|
|
591
600
|
for (const orderField of ordering) {
|
|
592
|
-
q.order(orderField)
|
|
601
|
+
q.order(orderField)
|
|
593
602
|
}
|
|
594
603
|
if (limit > 0) {
|
|
595
|
-
q.limit(limit)
|
|
604
|
+
q.limit(limit)
|
|
596
605
|
}
|
|
597
606
|
if (selection.length > 0) {
|
|
598
|
-
q.select(selection as any)
|
|
607
|
+
q.select(selection as any)
|
|
608
|
+
}
|
|
609
|
+
return await this.runQuery(q)
|
|
610
|
+
} catch (error) {
|
|
611
|
+
await setImmediate()
|
|
612
|
+
throw new DstoreError('datastore.query error', error as Error, {
|
|
613
|
+
kindName,
|
|
614
|
+
filters,
|
|
615
|
+
limit,
|
|
616
|
+
ordering,
|
|
617
|
+
})
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
/** `iterate()` is a modernized version of `query()`.
|
|
623
|
+
*
|
|
624
|
+
* It takes a Parameter object and returns an AsyncIterable.
|
|
625
|
+
* Entities returned have been processed by `fixKeys()`.
|
|
626
|
+
*
|
|
627
|
+
* @param kindName Name of the [[Datastore]][Kind](https://cloud.google.com/datastore/docs/concepts/entities#kinds_and_identifiers) ("Table") which should be searched.
|
|
628
|
+
* @param filters List of [[Query]] filter() calls.
|
|
629
|
+
* @param limit Maximum Number of Results to return.
|
|
630
|
+
* @param ordering List of [[Query]] order() calls.
|
|
631
|
+
* @param selection selectionList of [[Query]] select() calls.
|
|
632
|
+
*
|
|
633
|
+
* @category Additional
|
|
634
|
+
*/
|
|
635
|
+
async * iterate({
|
|
636
|
+
kindName,
|
|
637
|
+
filters = [],
|
|
638
|
+
limit = 2500,
|
|
639
|
+
ordering = [],
|
|
640
|
+
selection = [],
|
|
641
|
+
}: IIterateParams): AsyncIterable<IDstoreEntry> {
|
|
642
|
+
assertIsString(kindName)
|
|
643
|
+
assertIsArray(filters)
|
|
644
|
+
assertIsNumber(limit)
|
|
645
|
+
try {
|
|
646
|
+
const q = this.getDoT().createQuery(kindName)
|
|
647
|
+
for (const filterSpec of filters) {
|
|
648
|
+
assertIsObject(filterSpec)
|
|
649
|
+
// @ts-ignore
|
|
650
|
+
q.filter(new PropertyFilter(...filterSpec))
|
|
651
|
+
}
|
|
652
|
+
for (const orderField of ordering) {
|
|
653
|
+
q.order(orderField)
|
|
654
|
+
}
|
|
655
|
+
if (limit > 0) {
|
|
656
|
+
q.limit(limit)
|
|
657
|
+
}
|
|
658
|
+
if (selection.length > 0) {
|
|
659
|
+
q.select(selection as any)
|
|
660
|
+
}
|
|
661
|
+
for await (const entity of this.getDoT().runStream(q)) {
|
|
662
|
+
const ret = this.fixKeys([entity])[0]
|
|
663
|
+
assertIsDefined(ret, 'datastore.iterate: entity is undefined')
|
|
664
|
+
yield ret
|
|
599
665
|
}
|
|
600
|
-
return await this.runQuery(q);
|
|
601
666
|
} catch (error) {
|
|
602
|
-
await setImmediate()
|
|
667
|
+
await setImmediate()
|
|
603
668
|
throw new DstoreError('datastore.query error', error as Error, {
|
|
604
669
|
kindName,
|
|
605
670
|
filters,
|
|
606
671
|
limit,
|
|
607
672
|
ordering,
|
|
608
|
-
})
|
|
673
|
+
})
|
|
609
674
|
}
|
|
610
675
|
}
|
|
611
676
|
|
|
@@ -621,10 +686,10 @@ export class Dstore implements IDstore {
|
|
|
621
686
|
* In fact the generated ID is namespaced via an incomplete [[Key]] of the given Kind.
|
|
622
687
|
*/
|
|
623
688
|
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
|
|
689
|
+
assertIsString(kindName)
|
|
690
|
+
const ret = (await this.datastore.allocateIds(this.key([kindName]), 1))[0][0].id
|
|
691
|
+
assertIsString(ret)
|
|
692
|
+
return ret
|
|
628
693
|
}
|
|
629
694
|
|
|
630
695
|
/** This tries to give high level access to transactions.
|
|
@@ -642,27 +707,27 @@ export class Dstore implements IDstore {
|
|
|
642
707
|
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
708
|
*/
|
|
644
709
|
async runInTransaction<T>(func: () => Promise<T>): Promise<T> {
|
|
645
|
-
let ret
|
|
646
|
-
const transaction: Transaction = this.datastore.transaction()
|
|
710
|
+
let ret
|
|
711
|
+
const transaction: Transaction = this.datastore.transaction()
|
|
647
712
|
await transactionAsyncLocalStorage.run(transaction, async () => {
|
|
648
|
-
const [transactionInfo, transactionRunApiResponse] = await transaction.run()
|
|
649
|
-
let commitApiResponse
|
|
713
|
+
const [transactionInfo, transactionRunApiResponse] = await transaction.run()
|
|
714
|
+
let commitApiResponse
|
|
650
715
|
try {
|
|
651
|
-
ret = await func()
|
|
716
|
+
ret = await func()
|
|
652
717
|
} catch (error) {
|
|
653
|
-
const rollbackInfo = await transaction.rollback()
|
|
718
|
+
const rollbackInfo = await transaction.rollback()
|
|
654
719
|
debug(
|
|
655
720
|
'Transaction failed, rollback initiated: %O %O %O %O',
|
|
656
721
|
transactionInfo,
|
|
657
722
|
transactionRunApiResponse,
|
|
658
723
|
rollbackInfo,
|
|
659
724
|
error
|
|
660
|
-
)
|
|
661
|
-
await setImmediate()
|
|
662
|
-
throw new DstoreError('datastore.transaction execution error', error as Error)
|
|
725
|
+
)
|
|
726
|
+
await setImmediate()
|
|
727
|
+
throw new DstoreError('datastore.transaction execution error', error as Error)
|
|
663
728
|
}
|
|
664
729
|
try {
|
|
665
|
-
commitApiResponse = (await transaction.commit())[0]
|
|
730
|
+
commitApiResponse = (await transaction.commit())[0]
|
|
666
731
|
} catch (error) {
|
|
667
732
|
debug(
|
|
668
733
|
'Transaction commit failed: %O %O %O %O ret: %O',
|
|
@@ -671,36 +736,36 @@ export class Dstore implements IDstore {
|
|
|
671
736
|
commitApiResponse,
|
|
672
737
|
error,
|
|
673
738
|
ret
|
|
674
|
-
)
|
|
675
|
-
await setImmediate()
|
|
676
|
-
throw new DstoreError('datastore.transaction execution error', error as Error)
|
|
739
|
+
)
|
|
740
|
+
await setImmediate()
|
|
741
|
+
throw new DstoreError('datastore.transaction execution error', error as Error)
|
|
677
742
|
}
|
|
678
|
-
})
|
|
679
|
-
return ret as T
|
|
743
|
+
})
|
|
744
|
+
return ret as T
|
|
680
745
|
}
|
|
681
746
|
}
|
|
682
747
|
|
|
683
748
|
export class DstoreError extends Error {
|
|
684
|
-
public readonly extensions: Record<string, unknown
|
|
685
|
-
public readonly originalError: Error | undefined
|
|
686
|
-
readonly [key: string]: unknown
|
|
749
|
+
public readonly extensions: Record<string, unknown>
|
|
750
|
+
public readonly originalError: Error | undefined
|
|
751
|
+
readonly [key: string]: unknown
|
|
687
752
|
|
|
688
753
|
constructor(message: string, originalError: Error | undefined, extensions?: Record<string, unknown>) {
|
|
689
|
-
super(`${message}: ${originalError?.message}`)
|
|
754
|
+
super(`${message}: ${originalError?.message}`)
|
|
690
755
|
|
|
691
756
|
// if no name provided, use the default. defineProperty ensures that it stays non-enumerable
|
|
692
757
|
if (!this.name) {
|
|
693
|
-
Object.defineProperty(this, 'name', { value: 'DstoreError' })
|
|
758
|
+
Object.defineProperty(this, 'name', { value: 'DstoreError' })
|
|
694
759
|
}
|
|
695
760
|
// metadata: Metadata { internalRepr: Map(0) {}, options: {} },
|
|
696
|
-
this.originalError = originalError
|
|
697
|
-
this.extensions = { ...extensions }
|
|
761
|
+
this.originalError = originalError
|
|
762
|
+
this.extensions = { ...extensions }
|
|
698
763
|
this.stack =
|
|
699
764
|
(this.stack?.split('\n')[0] || '') +
|
|
700
765
|
'\n' +
|
|
701
766
|
(originalError?.stack?.split('\n')?.slice(1)?.join('\n') || '') +
|
|
702
767
|
'\n' +
|
|
703
|
-
(this.stack?.split('\n')?.slice(1)?.join('\n') || '')
|
|
768
|
+
(this.stack?.split('\n')?.slice(1)?.join('\n') || '')
|
|
704
769
|
|
|
705
770
|
// These are usually present on Datastore Errors
|
|
706
771
|
// logger.error({ err: originalError, extensions }, message);
|