datastore-api 4.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/datastore-api.cjs.development.js +1 -1
- 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 +2 -2
- package/dist/datastore-api.esm.js.map +1 -1
- package/dist/lib/dstore-api.d.ts +1 -3
- package/package.json +21 -22
- package/src/index.ts +1 -1
- package/src/lib/dstore-api-cloud.spec.ts +123 -123
- package/src/lib/dstore-api-emulator.spec.ts +131 -131
- package/src/lib/dstore-api-simulator.spec.ts +132 -132
- package/src/lib/dstore-api.ts +189 -190
- package/src/mock/index.ts +228 -228
- package/src/mock/operators.ts +1 -1
package/src/lib/dstore-api.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* dstore.ts - Datastore Compatibility layer
|
|
3
3
|
* Try to get a smoother api for transactions and such.
|
|
4
4
|
* A little bit inspired by the Python2 ndb interface.
|
|
5
|
+
* But without the ORM bits.
|
|
5
6
|
*
|
|
6
7
|
* In future https://github.com/graphql/dataloader might be used for batching.
|
|
7
8
|
*
|
|
@@ -9,13 +10,13 @@
|
|
|
9
10
|
* Copyright (c) 2021, 2022, 2023 Dr. Maximillian Dornseif
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
|
-
import { AsyncLocalStorage } from 'async_hooks'
|
|
13
|
-
import { setImmediate } from 'timers/promises'
|
|
13
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
14
|
+
import { setImmediate } from 'timers/promises';
|
|
14
15
|
|
|
15
|
-
import { Datastore, Key, PathType, Query, Transaction } from '@google-cloud/datastore'
|
|
16
|
-
import { Entity, entity } from '@google-cloud/datastore/build/src/entity'
|
|
17
|
-
import { Operator, RunQueryInfo, RunQueryResponse } from '@google-cloud/datastore/build/src/query'
|
|
18
|
-
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';
|
|
19
20
|
import {
|
|
20
21
|
assert,
|
|
21
22
|
assertIsArray,
|
|
@@ -23,44 +24,44 @@ import {
|
|
|
23
24
|
assertIsNumber,
|
|
24
25
|
assertIsObject,
|
|
25
26
|
assertIsString,
|
|
26
|
-
} from 'assertate-debug'
|
|
27
|
-
import Debug from 'debug'
|
|
28
|
-
import promClient from 'prom-client'
|
|
29
|
-
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';
|
|
30
31
|
|
|
31
32
|
/** @ignore */
|
|
32
|
-
export { Datastore, Key, PathType, Query, Transaction } from '@google-cloud/datastore'
|
|
33
|
+
export { Datastore, Key, PathType, Query, Transaction } from '@google-cloud/datastore';
|
|
33
34
|
|
|
34
35
|
/** @ignore */
|
|
35
|
-
const debug = Debug('ds:api')
|
|
36
|
+
const debug = Debug('ds:api');
|
|
36
37
|
|
|
37
38
|
/** @ignore */
|
|
38
|
-
const transactionAsyncLocalStorage = new AsyncLocalStorage()
|
|
39
|
+
const transactionAsyncLocalStorage = new AsyncLocalStorage();
|
|
39
40
|
|
|
40
41
|
/** @ignore */
|
|
41
42
|
const metricHistogram = new promClient.Histogram({
|
|
42
43
|
name: 'dstore_requests_seconds',
|
|
43
44
|
help: 'How long did Datastore operations take?',
|
|
44
45
|
labelNames: ['operation'],
|
|
45
|
-
})
|
|
46
|
+
});
|
|
46
47
|
const metricFailureCounter = new promClient.Counter({
|
|
47
48
|
name: 'dstore_failures_total',
|
|
48
49
|
help: 'How many Datastore operations failed?',
|
|
49
50
|
labelNames: ['operation'],
|
|
50
|
-
})
|
|
51
|
+
});
|
|
51
52
|
|
|
52
53
|
/** Use instead of Datastore.KEY
|
|
53
54
|
*
|
|
54
55
|
* Even better: use `_key` instead.
|
|
55
56
|
*/
|
|
56
|
-
export const KEYSYM = Datastore.KEY
|
|
57
|
+
export const KEYSYM = Datastore.KEY;
|
|
57
58
|
|
|
58
|
-
export type IGqlFilterTypes = boolean | string | number
|
|
59
|
+
export type IGqlFilterTypes = boolean | string | number;
|
|
59
60
|
|
|
60
61
|
export type IGqlFilterSpec = {
|
|
61
|
-
readonly eq: IGqlFilterTypes
|
|
62
|
-
}
|
|
63
|
-
export type TGqlFilterList = Array<[string, Operator, DstorePropertyValues]
|
|
62
|
+
readonly eq: IGqlFilterTypes;
|
|
63
|
+
};
|
|
64
|
+
export type TGqlFilterList = Array<[string, Operator, DstorePropertyValues]>;
|
|
64
65
|
|
|
65
66
|
/** Define what can be written into the Datastore */
|
|
66
67
|
export type DstorePropertyValues =
|
|
@@ -73,53 +74,51 @@ export type DstorePropertyValues =
|
|
|
73
74
|
| Buffer
|
|
74
75
|
| Key
|
|
75
76
|
| DstorePropertyValues[]
|
|
76
|
-
| { [key: string]: DstorePropertyValues }
|
|
77
|
+
| { [key: string]: DstorePropertyValues };
|
|
77
78
|
|
|
78
79
|
export interface IDstoreEntryWithoutKey {
|
|
79
80
|
/** All User Data stored in the Datastore */
|
|
80
|
-
[key: string]: DstorePropertyValues
|
|
81
|
+
[key: string]: DstorePropertyValues;
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
/** Represents what is actually stored inside the Datastore, called "Entity" by Google
|
|
84
85
|
[@google-cloud/datastore](https://github.com/googleapis/nodejs-datastore#readme) adds `[Datastore.KEY]`. Using ES6 Symbols presents all kinds of hurdles, especially when you try to serialize into a cache. So we add the property _keyStr which contains the encoded key. It is automatically used to reconstruct `[Datastore.KEY]`, if you use [[Dstore.readKey]].
|
|
85
86
|
*/
|
|
86
87
|
export interface IDstoreEntry extends IDstoreEntryWithoutKey {
|
|
87
|
-
/* Datastore
|
|
88
|
-
readonly [Datastore.KEY]?: Key
|
|
88
|
+
/* Datastore Key provided by [@google-cloud/datastore](https://github.com/googleapis/nodejs-datastore#readme) */
|
|
89
|
+
readonly [Datastore.KEY]?: Key;
|
|
89
90
|
/** [Datastore.KEY] key */
|
|
90
|
-
_keyStr: string
|
|
91
|
-
/** All User Data stored in the Datastore */
|
|
92
|
-
[key: string]: DstorePropertyValues
|
|
91
|
+
_keyStr: string;
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
/** Represents the thing you pass to the save method. Also called "Entity" by Google */
|
|
96
95
|
export type DstoreSaveEntity = {
|
|
97
|
-
key: Key
|
|
96
|
+
key: Key;
|
|
98
97
|
data: Omit<IDstoreEntry, '_keyStr' | Datastore['KEY']> &
|
|
99
98
|
Partial<{
|
|
100
|
-
_keyStr: string
|
|
101
|
-
[Datastore.KEY]: Key
|
|
102
|
-
}
|
|
103
|
-
method?: 'insert' | 'update' | 'upsert'
|
|
104
|
-
excludeLargeProperties?: boolean
|
|
105
|
-
excludeFromIndexes?: readonly string[]
|
|
106
|
-
}
|
|
99
|
+
_keyStr: string|undefined;
|
|
100
|
+
[Datastore.KEY]: Key;
|
|
101
|
+
}>;
|
|
102
|
+
method?: 'insert' | 'update' | 'upsert';
|
|
103
|
+
excludeLargeProperties?: boolean;
|
|
104
|
+
excludeFromIndexes?: readonly string[];
|
|
105
|
+
};
|
|
107
106
|
|
|
108
107
|
type IDstore = {
|
|
109
108
|
/** Accessible by Users of the library. Keep in mind that you will access outside transactions created by [[runInTransaction]]. */
|
|
110
|
-
readonly datastore: Datastore
|
|
111
|
-
key: (path: ReadonlyArray<PathType>) => Key
|
|
112
|
-
keyFromSerialized: (text: string) => Key
|
|
113
|
-
keySerialize: (key: Key) => string
|
|
114
|
-
readKey: (entry: IDstoreEntry) => Key
|
|
115
|
-
get: (key: Key) => Promise<IDstoreEntry | null
|
|
116
|
-
getMulti: (keys: ReadonlyArray<Key>) => Promise<ReadonlyArray<IDstoreEntry | null
|
|
117
|
-
set: (key: Key, entry: IDstoreEntry) => Promise<Key
|
|
118
|
-
save: (entities: readonly DstoreSaveEntity[]) => Promise<CommitResponse | undefined
|
|
119
|
-
insert: (entities: readonly DstoreSaveEntity[]) => Promise<CommitResponse | undefined
|
|
120
|
-
update: (entities: readonly DstoreSaveEntity[]) => Promise<CommitResponse | undefined
|
|
121
|
-
delete: (keys: readonly Key[]) => Promise<CommitResponse | undefined
|
|
122
|
-
createQuery: (kind: string) => Query
|
|
109
|
+
readonly datastore: Datastore;
|
|
110
|
+
key: (path: ReadonlyArray<PathType>) => Key;
|
|
111
|
+
keyFromSerialized: (text: string) => Key;
|
|
112
|
+
keySerialize: (key: Key) => string;
|
|
113
|
+
readKey: (entry: IDstoreEntry) => Key;
|
|
114
|
+
get: (key: Key) => Promise<IDstoreEntry | null>;
|
|
115
|
+
getMulti: (keys: ReadonlyArray<Key>) => Promise<ReadonlyArray<IDstoreEntry | null>>;
|
|
116
|
+
set: (key: Key, entry: IDstoreEntry) => Promise<Key>;
|
|
117
|
+
save: (entities: readonly DstoreSaveEntity[]) => Promise<CommitResponse | undefined>;
|
|
118
|
+
insert: (entities: readonly DstoreSaveEntity[]) => Promise<CommitResponse | undefined>;
|
|
119
|
+
update: (entities: readonly DstoreSaveEntity[]) => Promise<CommitResponse | undefined>;
|
|
120
|
+
delete: (keys: readonly Key[]) => Promise<CommitResponse | undefined>;
|
|
121
|
+
createQuery: (kind: string) => Query;
|
|
123
122
|
query: (
|
|
124
123
|
kind: string,
|
|
125
124
|
filters?: TGqlFilterList,
|
|
@@ -127,11 +126,11 @@ type IDstore = {
|
|
|
127
126
|
ordering?: readonly string[],
|
|
128
127
|
selection?: readonly string[],
|
|
129
128
|
cursor?: string
|
|
130
|
-
) => Promise<RunQueryResponse
|
|
131
|
-
runQuery: (query: Query | Omit<Query, 'run'>) => Promise<RunQueryResponse
|
|
132
|
-
allocateOneId: (kindName: string) => Promise<string
|
|
133
|
-
runInTransaction: <T>(func: { (): Promise<T>; (): T }) => Promise<T
|
|
134
|
-
}
|
|
129
|
+
) => Promise<RunQueryResponse>;
|
|
130
|
+
runQuery: (query: Query | Omit<Query, 'run'>) => Promise<RunQueryResponse>;
|
|
131
|
+
allocateOneId: (kindName: string) => Promise<string>;
|
|
132
|
+
runInTransaction: <T>(func: { (): Promise<T>; (): T }) => Promise<T>;
|
|
133
|
+
};
|
|
135
134
|
|
|
136
135
|
/** Dstore implements a slightly more accessible version of the [Google Cloud Datastore: Node.js Client](https://cloud.google.com/nodejs/docs/reference/datastore/latest)
|
|
137
136
|
|
|
@@ -157,9 +156,9 @@ Main differences:
|
|
|
157
156
|
This documentation also tries to document the little known idiosyncrasies of the [@google-cloud/datastore](https://github.com/googleapis/nodejs-datastore) library. See the corresponding functions.
|
|
158
157
|
*/
|
|
159
158
|
export class Dstore implements IDstore {
|
|
160
|
-
engine = 'Dstore'
|
|
161
|
-
engines: string[] = []
|
|
162
|
-
private readonly urlSaveKey = new entity.URLSafeKey()
|
|
159
|
+
engine = 'Dstore';
|
|
160
|
+
engines: string[] = [];
|
|
161
|
+
private readonly urlSaveKey = new entity.URLSafeKey();
|
|
163
162
|
|
|
164
163
|
/** Generate a Dstore instance for a specific [[Datastore]] instance.
|
|
165
164
|
|
|
@@ -177,13 +176,13 @@ export class Dstore implements IDstore {
|
|
|
177
176
|
```
|
|
178
177
|
*/
|
|
179
178
|
constructor(readonly datastore: Datastore, readonly projectId?: string, readonly logger?: string) {
|
|
180
|
-
assertIsObject(datastore)
|
|
181
|
-
this.engines.push(this.engine)
|
|
179
|
+
assertIsObject(datastore);
|
|
180
|
+
this.engines.push(this.engine);
|
|
182
181
|
}
|
|
183
182
|
|
|
184
183
|
/** Gets the Datastore or the current Transaction. */
|
|
185
184
|
private getDoT(): Transaction | Datastore {
|
|
186
|
-
return (transactionAsyncLocalStorage.getStore() as Transaction) || this.datastore
|
|
185
|
+
return (transactionAsyncLocalStorage.getStore() as Transaction) || this.datastore;
|
|
187
186
|
}
|
|
188
187
|
|
|
189
188
|
/** `key()` creates a [[Key]] Object from a path.
|
|
@@ -195,7 +194,7 @@ export class Dstore implements IDstore {
|
|
|
195
194
|
* @category Datastore Drop-In
|
|
196
195
|
*/
|
|
197
196
|
key(path: readonly PathType[]): Key {
|
|
198
|
-
return this.datastore.key(path as Writable<typeof path>)
|
|
197
|
+
return this.datastore.key(path as Writable<typeof path>);
|
|
199
198
|
}
|
|
200
199
|
|
|
201
200
|
/** `keyFromSerialized()` serializes [[Key]] to a string.
|
|
@@ -207,7 +206,7 @@ export class Dstore implements IDstore {
|
|
|
207
206
|
* @category Datastore Drop-In
|
|
208
207
|
*/
|
|
209
208
|
keySerialize(key: Key): string {
|
|
210
|
-
return key ? this.urlSaveKey.legacyEncode(this.projectId ?? '', key) : ''
|
|
209
|
+
return key ? this.urlSaveKey.legacyEncode(this.projectId ?? '', key) : '';
|
|
211
210
|
}
|
|
212
211
|
|
|
213
212
|
/** `keyFromSerialized()` deserializes a string created with [[keySerialize]] to a [[Key]].
|
|
@@ -217,7 +216,7 @@ export class Dstore implements IDstore {
|
|
|
217
216
|
* @category Datastore Drop-In
|
|
218
217
|
*/
|
|
219
218
|
keyFromSerialized(text: string): Key {
|
|
220
|
-
return this.urlSaveKey.legacyDecode(text)
|
|
219
|
+
return this.urlSaveKey.legacyDecode(text);
|
|
221
220
|
}
|
|
222
221
|
|
|
223
222
|
/** `readKey()` extracts the [[Key]] from an [[IDstoreEntry]].
|
|
@@ -228,17 +227,17 @@ export class Dstore implements IDstore {
|
|
|
228
227
|
* @category Additional
|
|
229
228
|
*/
|
|
230
229
|
readKey(ent: IDstoreEntry): Key {
|
|
231
|
-
assertIsObject(ent)
|
|
232
|
-
let ret = ent[Datastore.KEY]
|
|
230
|
+
assertIsObject(ent);
|
|
231
|
+
let ret = ent[Datastore.KEY];
|
|
233
232
|
if (ent._keyStr && !ret) {
|
|
234
|
-
ret = this.keyFromSerialized(ent._keyStr)
|
|
233
|
+
ret = this.keyFromSerialized(ent._keyStr);
|
|
235
234
|
}
|
|
236
235
|
assertIsObject(
|
|
237
236
|
ret,
|
|
238
237
|
'entity[Datastore.KEY]/entity._keyStr',
|
|
239
238
|
`Entity is missing the datastore Key: ${JSON.stringify(ent)}`
|
|
240
|
-
)
|
|
241
|
-
return ret
|
|
239
|
+
);
|
|
240
|
+
return ret;
|
|
242
241
|
}
|
|
243
242
|
|
|
244
243
|
/** `fixKeys()` is called for all [[IDstoreEntry]]sa returned from [[Dstore]].
|
|
@@ -252,13 +251,13 @@ export class Dstore implements IDstore {
|
|
|
252
251
|
): Array<IDstoreEntry | undefined> {
|
|
253
252
|
entities.forEach((x) => {
|
|
254
253
|
if (!!x?.[Datastore.KEY] && x[Datastore.KEY]) {
|
|
255
|
-
assertIsDefined(x[Datastore.KEY])
|
|
256
|
-
assertIsObject(x[Datastore.KEY])
|
|
254
|
+
assertIsDefined(x[Datastore.KEY]);
|
|
255
|
+
assertIsObject(x[Datastore.KEY]);
|
|
257
256
|
// Old TypesScript has problems with symbols as a property
|
|
258
|
-
x._keyStr = this.keySerialize(x[Datastore.KEY] as Key)
|
|
257
|
+
x._keyStr = this.keySerialize(x[Datastore.KEY] as Key);
|
|
259
258
|
}
|
|
260
|
-
})
|
|
261
|
-
return entities as Array<IDstoreEntry | undefined
|
|
259
|
+
});
|
|
260
|
+
return entities as Array<IDstoreEntry | undefined>;
|
|
262
261
|
}
|
|
263
262
|
|
|
264
263
|
/** `get()` reads a [[IDstoreEntry]] from the Datastore.
|
|
@@ -277,11 +276,11 @@ export class Dstore implements IDstore {
|
|
|
277
276
|
* @category Datastore Drop-In
|
|
278
277
|
*/
|
|
279
278
|
async get(key: Key): Promise<IDstoreEntry | null> {
|
|
280
|
-
assertIsObject(key)
|
|
281
|
-
assert(!Array.isArray(key))
|
|
282
|
-
assert(key.path.length % 2 == 0, `key.path must be complete: ${JSON.stringify(key.path)}`)
|
|
283
|
-
const result = await this.getMulti([key])
|
|
284
|
-
return result?.[0] || null
|
|
279
|
+
assertIsObject(key);
|
|
280
|
+
assert(!Array.isArray(key));
|
|
281
|
+
assert(key.path.length % 2 == 0, `key.path must be complete: ${JSON.stringify(key.path)}`);
|
|
282
|
+
const result = await this.getMulti([key]);
|
|
283
|
+
return result?.[0] || null;
|
|
285
284
|
}
|
|
286
285
|
|
|
287
286
|
/** `getMulti()` reads several [[IDstoreEntry]]s from the Datastore.
|
|
@@ -303,28 +302,28 @@ export class Dstore implements IDstore {
|
|
|
303
302
|
*/
|
|
304
303
|
async getMulti(keys: readonly Key[]): Promise<Array<IDstoreEntry | null>> {
|
|
305
304
|
// assertIsArray(keys);
|
|
306
|
-
let results: (IDstoreEntry | null | undefined)[]
|
|
307
|
-
const metricEnd = metricHistogram.startTimer()
|
|
305
|
+
let results: (IDstoreEntry | null | undefined)[];
|
|
306
|
+
const metricEnd = metricHistogram.startTimer();
|
|
308
307
|
try {
|
|
309
308
|
results = this.fixKeys(
|
|
310
309
|
keys.length > 0 ? (await this.getDoT().get(keys as Writable<typeof keys>))?.[0] : []
|
|
311
|
-
)
|
|
310
|
+
);
|
|
312
311
|
} catch (error) {
|
|
313
|
-
metricFailureCounter.inc({ operation: 'get' })
|
|
314
|
-
await setImmediate()
|
|
315
|
-
throw new DstoreError('datastore.getMulti error', error as Error, { keys })
|
|
312
|
+
metricFailureCounter.inc({ operation: 'get' });
|
|
313
|
+
await setImmediate();
|
|
314
|
+
throw new DstoreError('datastore.getMulti error', error as Error, { keys });
|
|
316
315
|
} finally {
|
|
317
|
-
metricEnd({ operation: 'get' })
|
|
316
|
+
metricEnd({ operation: 'get' });
|
|
318
317
|
}
|
|
319
318
|
|
|
320
319
|
// Sort resulting entities by the keys they were requested with.
|
|
321
|
-
assertIsArray(results)
|
|
322
|
-
const entities = results as IDstoreEntry[]
|
|
323
|
-
const entitiesByKey: Record<string, IDstoreEntry> = {}
|
|
320
|
+
assertIsArray(results);
|
|
321
|
+
const entities = results as IDstoreEntry[];
|
|
322
|
+
const entitiesByKey: Record<string, IDstoreEntry> = {};
|
|
324
323
|
entities.forEach((entity) => {
|
|
325
|
-
entitiesByKey[JSON.stringify(entity[Datastore.KEY])] = entity
|
|
326
|
-
})
|
|
327
|
-
return keys.map((key) => entitiesByKey[JSON.stringify(key)] || null)
|
|
324
|
+
entitiesByKey[JSON.stringify(entity[Datastore.KEY])] = entity;
|
|
325
|
+
});
|
|
326
|
+
return keys.map((key) => entitiesByKey[JSON.stringify(key)] || null);
|
|
328
327
|
}
|
|
329
328
|
|
|
330
329
|
/** `set()` is addition to [[Datastore]]. It provides a classic Key-value Interface.
|
|
@@ -344,11 +343,11 @@ export class Dstore implements IDstore {
|
|
|
344
343
|
* @category Additional
|
|
345
344
|
*/
|
|
346
345
|
async set(key: Key, data: IDstoreEntryWithoutKey): Promise<Key> {
|
|
347
|
-
assertIsObject(key)
|
|
348
|
-
assertIsObject(data)
|
|
349
|
-
const saveEntity = { key, data }
|
|
350
|
-
await this.save([saveEntity])
|
|
351
|
-
return saveEntity.key
|
|
346
|
+
assertIsObject(key);
|
|
347
|
+
assertIsObject(data);
|
|
348
|
+
const saveEntity = { key, data };
|
|
349
|
+
await this.save([saveEntity]);
|
|
350
|
+
return saveEntity.key;
|
|
352
351
|
}
|
|
353
352
|
|
|
354
353
|
/** `save()` is compatible to [Datastore.save()](https://cloud.google.com/nodejs/docs/reference/datastore/latest/datastore/datastore#_google_cloud_datastore_Datastore_save_member_1_).
|
|
@@ -385,32 +384,32 @@ export class Dstore implements IDstore {
|
|
|
385
384
|
* @category Datastore Drop-In
|
|
386
385
|
*/
|
|
387
386
|
async save(entities: readonly DstoreSaveEntity[]): Promise<CommitResponse | undefined> {
|
|
388
|
-
assertIsArray(entities)
|
|
389
|
-
let ret: CommitResponse | undefined
|
|
390
|
-
const metricEnd = metricHistogram.startTimer()
|
|
387
|
+
assertIsArray(entities);
|
|
388
|
+
let ret: CommitResponse | undefined;
|
|
389
|
+
const metricEnd = metricHistogram.startTimer();
|
|
391
390
|
try {
|
|
392
391
|
// Within Transaction we don't get any answer here!
|
|
393
392
|
// [ { mutationResults: [ [Object], [Object] ], indexUpdates: 51 } ]
|
|
394
393
|
for (const e of entities) {
|
|
395
|
-
assertIsObject(e.key)
|
|
396
|
-
assertIsObject(e.data)
|
|
397
|
-
this.fixKeys([e.data])
|
|
398
|
-
e.excludeLargeProperties = e.excludeLargeProperties === undefined ? true : e.excludeLargeProperties
|
|
399
|
-
e.data = { ...e.data, _keyStr: undefined }
|
|
394
|
+
assertIsObject(e.key);
|
|
395
|
+
assertIsObject(e.data);
|
|
396
|
+
this.fixKeys([e.data]);
|
|
397
|
+
e.excludeLargeProperties = e.excludeLargeProperties === undefined ? true : e.excludeLargeProperties;
|
|
398
|
+
e.data = { ...e.data, _keyStr: undefined };
|
|
400
399
|
}
|
|
401
|
-
ret = (await this.getDoT().save(entities)) || undefined
|
|
400
|
+
ret = (await this.getDoT().save(entities)) || undefined;
|
|
402
401
|
for (const e of entities) {
|
|
403
|
-
e.data[Datastore.KEY] = e.key
|
|
404
|
-
this.fixKeys([e.data])
|
|
402
|
+
e.data[Datastore.KEY] = e.key;
|
|
403
|
+
this.fixKeys([e.data]);
|
|
405
404
|
}
|
|
406
405
|
} catch (error) {
|
|
407
|
-
metricFailureCounter.inc({ operation: 'save' })
|
|
408
|
-
await setImmediate()
|
|
409
|
-
throw new DstoreError('datastore.save error', error as Error)
|
|
406
|
+
metricFailureCounter.inc({ operation: 'save' });
|
|
407
|
+
await setImmediate();
|
|
408
|
+
throw new DstoreError('datastore.save error', error as Error);
|
|
410
409
|
} finally {
|
|
411
|
-
metricEnd({ operation: 'save' })
|
|
410
|
+
metricEnd({ operation: 'save' });
|
|
412
411
|
}
|
|
413
|
-
return ret
|
|
412
|
+
return ret;
|
|
414
413
|
}
|
|
415
414
|
|
|
416
415
|
/** `insert()` is compatible to [Datastore.insert()](https://cloud.google.com/nodejs/docs/reference/datastore/latest/datastore/datastore#_google_cloud_datastore_Datastore_insert_member_1_).
|
|
@@ -432,20 +431,20 @@ export class Dstore implements IDstore {
|
|
|
432
431
|
* @category Datastore Drop-In
|
|
433
432
|
*/
|
|
434
433
|
async insert(entities: readonly DstoreSaveEntity[]): Promise<CommitResponse | undefined> {
|
|
435
|
-
assertIsArray(entities)
|
|
436
|
-
let ret: CommitResponse | undefined
|
|
437
|
-
const metricEnd = metricHistogram.startTimer()
|
|
434
|
+
assertIsArray(entities);
|
|
435
|
+
let ret: CommitResponse | undefined;
|
|
436
|
+
const metricEnd = metricHistogram.startTimer();
|
|
438
437
|
try {
|
|
439
|
-
ret = (await this.getDoT().insert(entities)) || undefined
|
|
438
|
+
ret = (await this.getDoT().insert(entities)) || undefined;
|
|
440
439
|
} catch (error) {
|
|
441
440
|
// console.error(error)
|
|
442
|
-
metricFailureCounter.inc({ operation: 'insert' })
|
|
443
|
-
await setImmediate()
|
|
444
|
-
throw new DstoreError('datastore.insert error', error as Error)
|
|
441
|
+
metricFailureCounter.inc({ operation: 'insert' });
|
|
442
|
+
await setImmediate();
|
|
443
|
+
throw new DstoreError('datastore.insert error', error as Error);
|
|
445
444
|
} finally {
|
|
446
|
-
metricEnd({ operation: 'insert' })
|
|
445
|
+
metricEnd({ operation: 'insert' });
|
|
447
446
|
}
|
|
448
|
-
return ret
|
|
447
|
+
return ret;
|
|
449
448
|
}
|
|
450
449
|
|
|
451
450
|
/** `update()` is compatible to [Datastore.update()](https://cloud.google.com/nodejs/docs/reference/datastore/latest/datastore/datastore#_google_cloud_datastore_Datastore_update_member_1_).
|
|
@@ -467,29 +466,29 @@ export class Dstore implements IDstore {
|
|
|
467
466
|
* @category Datastore Drop-In
|
|
468
467
|
*/
|
|
469
468
|
async update(entities: readonly DstoreSaveEntity[]): Promise<CommitResponse | undefined> {
|
|
470
|
-
assertIsArray(entities)
|
|
469
|
+
assertIsArray(entities);
|
|
471
470
|
|
|
472
|
-
entities.forEach((entity) => assertIsObject(entity.key))
|
|
471
|
+
entities.forEach((entity) => assertIsObject(entity.key));
|
|
473
472
|
entities.forEach((entity) =>
|
|
474
473
|
assert(
|
|
475
474
|
entity.key.path.length % 2 == 0,
|
|
476
475
|
`entity.key.path must be complete: ${JSON.stringify([entity.key.path, entity])}`
|
|
477
476
|
)
|
|
478
|
-
)
|
|
479
|
-
let ret: CommitResponse | undefined
|
|
480
|
-
const metricEnd = metricHistogram.startTimer()
|
|
477
|
+
);
|
|
478
|
+
let ret: CommitResponse | undefined;
|
|
479
|
+
const metricEnd = metricHistogram.startTimer();
|
|
481
480
|
|
|
482
481
|
try {
|
|
483
|
-
ret = (await this.getDoT().update(entities)) || undefined
|
|
482
|
+
ret = (await this.getDoT().update(entities)) || undefined;
|
|
484
483
|
} catch (error) {
|
|
485
484
|
// console.error(error)
|
|
486
|
-
metricFailureCounter.inc({ operation: 'update' })
|
|
487
|
-
await setImmediate()
|
|
488
|
-
throw new DstoreError('datastore.update error', error as Error)
|
|
485
|
+
metricFailureCounter.inc({ operation: 'update' });
|
|
486
|
+
await setImmediate();
|
|
487
|
+
throw new DstoreError('datastore.update error', error as Error);
|
|
489
488
|
} finally {
|
|
490
|
-
metricEnd({ operation: 'update' })
|
|
489
|
+
metricEnd({ operation: 'update' });
|
|
491
490
|
}
|
|
492
|
-
return ret
|
|
491
|
+
return ret;
|
|
493
492
|
}
|
|
494
493
|
|
|
495
494
|
/** `delete()` is compatible to [Datastore.delete()].
|
|
@@ -506,23 +505,23 @@ export class Dstore implements IDstore {
|
|
|
506
505
|
* @category Datastore Drop-In
|
|
507
506
|
*/
|
|
508
507
|
async delete(keys: readonly Key[]): Promise<CommitResponse | undefined> {
|
|
509
|
-
assertIsArray(keys)
|
|
510
|
-
keys.forEach((key) => assertIsObject(key))
|
|
508
|
+
assertIsArray(keys);
|
|
509
|
+
keys.forEach((key) => assertIsObject(key));
|
|
511
510
|
keys.forEach((key) =>
|
|
512
511
|
assert(key.path.length % 2 == 0, `key.path must be complete: ${JSON.stringify(key.path)}`)
|
|
513
|
-
)
|
|
514
|
-
let ret
|
|
515
|
-
const metricEnd = metricHistogram.startTimer()
|
|
512
|
+
);
|
|
513
|
+
let ret;
|
|
514
|
+
const metricEnd = metricHistogram.startTimer();
|
|
516
515
|
try {
|
|
517
|
-
ret = (await this.getDoT().delete(keys)) || undefined
|
|
516
|
+
ret = (await this.getDoT().delete(keys)) || undefined;
|
|
518
517
|
} catch (error) {
|
|
519
|
-
metricFailureCounter.inc({ operation: 'delete' })
|
|
520
|
-
await setImmediate()
|
|
521
|
-
throw new DstoreError('datastore.delete error', error as Error)
|
|
518
|
+
metricFailureCounter.inc({ operation: 'delete' });
|
|
519
|
+
await setImmediate();
|
|
520
|
+
throw new DstoreError('datastore.delete error', error as Error);
|
|
522
521
|
} finally {
|
|
523
|
-
metricEnd({ operation: 'delete' })
|
|
522
|
+
metricEnd({ operation: 'delete' });
|
|
524
523
|
}
|
|
525
|
-
return ret
|
|
524
|
+
return ret;
|
|
526
525
|
}
|
|
527
526
|
|
|
528
527
|
/** `createQuery()` creates an "empty" [[Query]] Object.
|
|
@@ -535,25 +534,25 @@ export class Dstore implements IDstore {
|
|
|
535
534
|
*/
|
|
536
535
|
createQuery(kindName: string): Query {
|
|
537
536
|
try {
|
|
538
|
-
return this.getDoT().createQuery(kindName)
|
|
537
|
+
return this.getDoT().createQuery(kindName);
|
|
539
538
|
} catch (error) {
|
|
540
|
-
throw new DstoreError('datastore.createQuery error', error as Error)
|
|
539
|
+
throw new DstoreError('datastore.createQuery error', error as Error);
|
|
541
540
|
}
|
|
542
541
|
}
|
|
543
542
|
|
|
544
543
|
async runQuery(query: Query | Omit<Query, 'run'>): Promise<RunQueryResponse> {
|
|
545
|
-
let ret
|
|
546
|
-
const metricEnd = metricHistogram.startTimer()
|
|
544
|
+
let ret;
|
|
545
|
+
const metricEnd = metricHistogram.startTimer();
|
|
547
546
|
try {
|
|
548
|
-
const [entities, info]: [Entity[], RunQueryInfo] = await this.getDoT().runQuery(query as Query)
|
|
549
|
-
ret = [this.fixKeys(entities), info]
|
|
547
|
+
const [entities, info]: [Entity[], RunQueryInfo] = await this.getDoT().runQuery(query as Query);
|
|
548
|
+
ret = [this.fixKeys(entities), info];
|
|
550
549
|
} catch (error) {
|
|
551
|
-
await setImmediate()
|
|
552
|
-
throw new DstoreError('datastore.runQuery error', error as Error)
|
|
550
|
+
await setImmediate();
|
|
551
|
+
throw new DstoreError('datastore.runQuery error', error as Error);
|
|
553
552
|
} finally {
|
|
554
|
-
metricEnd({ operation: 'query' })
|
|
553
|
+
metricEnd({ operation: 'query' });
|
|
555
554
|
}
|
|
556
|
-
return ret as RunQueryResponse
|
|
555
|
+
return ret as RunQueryResponse;
|
|
557
556
|
}
|
|
558
557
|
|
|
559
558
|
/** `query()` combined [[createQuery]] and [[runQuery]] in a single call.
|
|
@@ -576,34 +575,34 @@ export class Dstore implements IDstore {
|
|
|
576
575
|
selection: readonly string[] = [],
|
|
577
576
|
cursor?: string
|
|
578
577
|
): Promise<RunQueryResponse> {
|
|
579
|
-
assertIsString(kindName)
|
|
580
|
-
assertIsArray(filters)
|
|
581
|
-
assertIsNumber(limit)
|
|
578
|
+
assertIsString(kindName);
|
|
579
|
+
assertIsArray(filters);
|
|
580
|
+
assertIsNumber(limit);
|
|
582
581
|
try {
|
|
583
|
-
const q = this.createQuery(kindName)
|
|
582
|
+
const q = this.createQuery(kindName);
|
|
584
583
|
for (const filterSpec of filters) {
|
|
585
|
-
assertIsObject(filterSpec)
|
|
584
|
+
assertIsObject(filterSpec);
|
|
586
585
|
// @ts-ignore
|
|
587
|
-
q.filter(...filterSpec)
|
|
586
|
+
q.filter(new PropertyFilter(...filterSpec));
|
|
588
587
|
}
|
|
589
588
|
for (const orderField of ordering) {
|
|
590
|
-
q.order(orderField)
|
|
589
|
+
q.order(orderField);
|
|
591
590
|
}
|
|
592
591
|
if (limit > 0) {
|
|
593
|
-
q.limit(limit)
|
|
592
|
+
q.limit(limit);
|
|
594
593
|
}
|
|
595
594
|
if (selection.length > 0) {
|
|
596
|
-
q.select(selection as any)
|
|
595
|
+
q.select(selection as any);
|
|
597
596
|
}
|
|
598
|
-
return await this.runQuery(q)
|
|
597
|
+
return await this.runQuery(q);
|
|
599
598
|
} catch (error) {
|
|
600
|
-
await setImmediate()
|
|
599
|
+
await setImmediate();
|
|
601
600
|
throw new DstoreError('datastore.query error', error as Error, {
|
|
602
601
|
kindName,
|
|
603
602
|
filters,
|
|
604
603
|
limit,
|
|
605
604
|
ordering,
|
|
606
|
-
})
|
|
605
|
+
});
|
|
607
606
|
}
|
|
608
607
|
}
|
|
609
608
|
|
|
@@ -619,10 +618,10 @@ export class Dstore implements IDstore {
|
|
|
619
618
|
* In fact the generated ID is namespaced via an incomplete [[Key]] of the given Kind.
|
|
620
619
|
*/
|
|
621
620
|
async allocateOneId(kindName = 'Numbering'): Promise<string> {
|
|
622
|
-
assertIsString(kindName)
|
|
623
|
-
const ret = (await this.datastore.allocateIds(this.key([kindName]), 1))[0][0].id
|
|
624
|
-
assertIsString(ret)
|
|
625
|
-
return ret
|
|
621
|
+
assertIsString(kindName);
|
|
622
|
+
const ret = (await this.datastore.allocateIds(this.key([kindName]), 1))[0][0].id;
|
|
623
|
+
assertIsString(ret);
|
|
624
|
+
return ret;
|
|
626
625
|
}
|
|
627
626
|
|
|
628
627
|
/** This tries to give high level access to transactions.
|
|
@@ -640,27 +639,27 @@ export class Dstore implements IDstore {
|
|
|
640
639
|
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.
|
|
641
640
|
*/
|
|
642
641
|
async runInTransaction<T>(func: () => Promise<T>): Promise<T> {
|
|
643
|
-
let ret
|
|
644
|
-
const transaction: Transaction = this.datastore.transaction()
|
|
642
|
+
let ret;
|
|
643
|
+
const transaction: Transaction = this.datastore.transaction();
|
|
645
644
|
await transactionAsyncLocalStorage.run(transaction, async () => {
|
|
646
|
-
const [transactionInfo, transactionRunApiResponse] = await transaction.run()
|
|
647
|
-
let commitApiResponse
|
|
645
|
+
const [transactionInfo, transactionRunApiResponse] = await transaction.run();
|
|
646
|
+
let commitApiResponse;
|
|
648
647
|
try {
|
|
649
|
-
ret = await func()
|
|
648
|
+
ret = await func();
|
|
650
649
|
} catch (error) {
|
|
651
|
-
const rollbackInfo = await transaction.rollback()
|
|
650
|
+
const rollbackInfo = await transaction.rollback();
|
|
652
651
|
debug(
|
|
653
652
|
'Transaction failed, rollback initiated: %O %O %O %O',
|
|
654
653
|
transactionInfo,
|
|
655
654
|
transactionRunApiResponse,
|
|
656
655
|
rollbackInfo,
|
|
657
656
|
error
|
|
658
|
-
)
|
|
659
|
-
await setImmediate()
|
|
660
|
-
throw new DstoreError('datastore.transaction execution error', error as Error)
|
|
657
|
+
);
|
|
658
|
+
await setImmediate();
|
|
659
|
+
throw new DstoreError('datastore.transaction execution error', error as Error);
|
|
661
660
|
}
|
|
662
661
|
try {
|
|
663
|
-
commitApiResponse = (await transaction.commit())[0]
|
|
662
|
+
commitApiResponse = (await transaction.commit())[0];
|
|
664
663
|
} catch (error) {
|
|
665
664
|
debug(
|
|
666
665
|
'Transaction commit failed: %O %O %O %O ret: %O',
|
|
@@ -669,36 +668,36 @@ export class Dstore implements IDstore {
|
|
|
669
668
|
commitApiResponse,
|
|
670
669
|
error,
|
|
671
670
|
ret
|
|
672
|
-
)
|
|
673
|
-
await setImmediate()
|
|
674
|
-
throw new DstoreError('datastore.transaction execution error', error as Error)
|
|
671
|
+
);
|
|
672
|
+
await setImmediate();
|
|
673
|
+
throw new DstoreError('datastore.transaction execution error', error as Error);
|
|
675
674
|
}
|
|
676
|
-
})
|
|
677
|
-
return ret as T
|
|
675
|
+
});
|
|
676
|
+
return ret as T;
|
|
678
677
|
}
|
|
679
678
|
}
|
|
680
679
|
|
|
681
680
|
export class DstoreError extends Error {
|
|
682
|
-
public readonly extensions: Record<string, unknown
|
|
681
|
+
public readonly extensions: Record<string, unknown>;
|
|
683
682
|
public readonly originalError: Error | undefined;
|
|
684
|
-
readonly [key: string]: unknown
|
|
683
|
+
readonly [key: string]: unknown;
|
|
685
684
|
|
|
686
685
|
constructor(message: string, originalError: Error | undefined, extensions?: Record<string, unknown>) {
|
|
687
|
-
super(`${message}: ${originalError?.message}`)
|
|
686
|
+
super(`${message}: ${originalError?.message}`);
|
|
688
687
|
|
|
689
688
|
// if no name provided, use the default. defineProperty ensures that it stays non-enumerable
|
|
690
689
|
if (!this.name) {
|
|
691
|
-
Object.defineProperty(this, 'name', { value: 'DstoreError' })
|
|
690
|
+
Object.defineProperty(this, 'name', { value: 'DstoreError' });
|
|
692
691
|
}
|
|
693
692
|
// metadata: Metadata { internalRepr: Map(0) {}, options: {} },
|
|
694
|
-
this.originalError = originalError
|
|
695
|
-
this.extensions = { ...extensions }
|
|
693
|
+
this.originalError = originalError;
|
|
694
|
+
this.extensions = { ...extensions };
|
|
696
695
|
this.stack =
|
|
697
696
|
(this.stack?.split('\n')[0] || '') +
|
|
698
697
|
'\n' +
|
|
699
698
|
(originalError?.stack?.split('\n')?.slice(1)?.join('\n') || '') +
|
|
700
699
|
'\n' +
|
|
701
|
-
(this.stack?.split('\n')?.slice(1)?.join('\n') || '')
|
|
700
|
+
(this.stack?.split('\n')?.slice(1)?.join('\n') || '');
|
|
702
701
|
|
|
703
702
|
// These are usually present on Datastore Errors
|
|
704
703
|
// logger.error({ err: originalError, extensions }, message);
|