@naturalcycles/firestore-lib 2.14.4 → 2.15.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/firestore.db.d.ts +3 -3
- package/dist/firestore.db.js +38 -9
- package/dist/firestoreStreamReadable.d.ts +1 -0
- package/dist/firestoreStreamReadable.js +9 -1
- package/dist/query.util.d.ts +3 -1
- package/dist/query.util.js +12 -1
- package/package.json +2 -3
- package/src/firestore.db.ts +47 -14
- package/src/firestoreStreamReadable.ts +15 -2
- package/src/query.util.ts +15 -2
package/dist/firestore.db.d.ts
CHANGED
|
@@ -12,9 +12,9 @@ export declare class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
|
12
12
|
support: CommonDBSupport;
|
|
13
13
|
getByIds<ROW extends ObjectWithId>(table: string, ids: string[], opt?: FirestoreDBReadOptions): Promise<ROW[]>;
|
|
14
14
|
multiGet<ROW extends ObjectWithId>(map: StringMap<string[]>, opt?: CommonDBReadOptions): Promise<StringMap<ROW[]>>;
|
|
15
|
-
runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?:
|
|
16
|
-
runFirestoreQuery<ROW extends ObjectWithId>(q: Query): Promise<ROW[]>;
|
|
17
|
-
runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>,
|
|
15
|
+
runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: FirestoreDBReadOptions): Promise<RunQueryResult<ROW>>;
|
|
16
|
+
runFirestoreQuery<ROW extends ObjectWithId>(q: Query, opt?: FirestoreDBReadOptions): Promise<ROW[]>;
|
|
17
|
+
runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: FirestoreDBReadOptions): Promise<number>;
|
|
18
18
|
streamQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt_?: FirestoreDBStreamOptions): Pipeline<ROW>;
|
|
19
19
|
saveBatch<ROW extends ObjectWithId>(table: string, rows: ROW[], opt?: FirestoreDBSaveOptions<ROW>): Promise<void>;
|
|
20
20
|
multiSave<ROW extends ObjectWithId>(map: StringMap<ROW[]>, opt?: FirestoreDBSaveOptions<ROW>): Promise<void>;
|
package/dist/firestore.db.js
CHANGED
|
@@ -10,7 +10,7 @@ import { Pipeline } from '@naturalcycles/nodejs-lib/stream';
|
|
|
10
10
|
import { escapeDocId, unescapeDocId } from './firestore.util.js';
|
|
11
11
|
import { FirestoreShardedReadable } from './firestoreShardedReadable.js';
|
|
12
12
|
import { FirestoreStreamReadable } from './firestoreStreamReadable.js';
|
|
13
|
-
import { dbQueryToFirestoreQuery } from './query.util.js';
|
|
13
|
+
import { dbQueryToFirestoreQuery, readAtToReadTime } from './query.util.js';
|
|
14
14
|
export class FirestoreDB extends BaseCommonDB {
|
|
15
15
|
constructor(cfg) {
|
|
16
16
|
super();
|
|
@@ -30,10 +30,17 @@ export class FirestoreDB extends BaseCommonDB {
|
|
|
30
30
|
async getByIds(table, ids, opt = {}) {
|
|
31
31
|
if (!ids.length)
|
|
32
32
|
return [];
|
|
33
|
-
// todo: support PITR: https://firebase.google.com/docs/firestore/enterprise/use-pitr#read-pitr
|
|
34
33
|
const { firestore } = this.cfg;
|
|
35
34
|
const col = firestore.collection(table);
|
|
36
|
-
|
|
35
|
+
const refs = ids.map(id => col.doc(escapeDocId(id)));
|
|
36
|
+
const readTime = readAtToReadTime(opt);
|
|
37
|
+
if (readTime) {
|
|
38
|
+
_assert(!opt.tx, 'readAt is not supported inside an existing transaction');
|
|
39
|
+
}
|
|
40
|
+
const snapshots = await (readTime
|
|
41
|
+
? firestore.runTransaction(tx => tx.getAll(...refs), { readOnly: true, readTime })
|
|
42
|
+
: (opt.tx?.tx || firestore).getAll(...refs));
|
|
43
|
+
return snapshots
|
|
37
44
|
.map(doc => {
|
|
38
45
|
const data = doc.data();
|
|
39
46
|
if (data === undefined)
|
|
@@ -54,7 +61,13 @@ export class FirestoreDB extends BaseCommonDB {
|
|
|
54
61
|
const col = firestore.collection(table);
|
|
55
62
|
refs.push(...ids.map(id => col.doc(escapeDocId(id))));
|
|
56
63
|
}
|
|
57
|
-
const
|
|
64
|
+
const readTime = readAtToReadTime(opt);
|
|
65
|
+
if (readTime) {
|
|
66
|
+
_assert(!opt.tx, 'readAt is not supported inside an existing transaction');
|
|
67
|
+
}
|
|
68
|
+
const snapshots = await (readTime
|
|
69
|
+
? firestore.runTransaction(tx => tx.getAll(...refs), { readOnly: true, readTime })
|
|
70
|
+
: (opt.tx?.tx || firestore).getAll(...refs));
|
|
58
71
|
snapshots.forEach(snap => {
|
|
59
72
|
const data = snap.data();
|
|
60
73
|
if (data === undefined)
|
|
@@ -69,7 +82,7 @@ export class FirestoreDB extends BaseCommonDB {
|
|
|
69
82
|
return result;
|
|
70
83
|
}
|
|
71
84
|
// QUERY
|
|
72
|
-
async runQuery(q, opt) {
|
|
85
|
+
async runQuery(q, opt = {}) {
|
|
73
86
|
const idFilter = q._filters.find(f => f.name === 'id');
|
|
74
87
|
if (idFilter) {
|
|
75
88
|
const ids = Array.isArray(idFilter.val) ? idFilter.val : [idFilter.val];
|
|
@@ -78,18 +91,34 @@ export class FirestoreDB extends BaseCommonDB {
|
|
|
78
91
|
};
|
|
79
92
|
}
|
|
80
93
|
const firestoreQuery = dbQueryToFirestoreQuery(q, this.cfg.firestore.collection(q.table));
|
|
81
|
-
let rows = await this.runFirestoreQuery(firestoreQuery);
|
|
94
|
+
let rows = await this.runFirestoreQuery(firestoreQuery, opt);
|
|
82
95
|
// Special case when projection query didn't specify 'id'
|
|
83
96
|
if (q._selectedFieldNames && !q._selectedFieldNames.includes('id')) {
|
|
84
97
|
rows = rows.map(r => _omit(r, ['id']));
|
|
85
98
|
}
|
|
86
99
|
return { rows };
|
|
87
100
|
}
|
|
88
|
-
async runFirestoreQuery(q) {
|
|
101
|
+
async runFirestoreQuery(q, opt = {}) {
|
|
102
|
+
const readTime = readAtToReadTime(opt);
|
|
103
|
+
if (readTime) {
|
|
104
|
+
const qs = await this.cfg.firestore.runTransaction(tx => tx.get(q), {
|
|
105
|
+
readOnly: true,
|
|
106
|
+
readTime,
|
|
107
|
+
});
|
|
108
|
+
return this.querySnapshotToArray(qs);
|
|
109
|
+
}
|
|
89
110
|
return this.querySnapshotToArray(await q.get());
|
|
90
111
|
}
|
|
91
|
-
async runQueryCount(q,
|
|
112
|
+
async runQueryCount(q, opt = {}) {
|
|
92
113
|
const firestoreQuery = dbQueryToFirestoreQuery(q, this.cfg.firestore.collection(q.table));
|
|
114
|
+
const readTime = readAtToReadTime(opt);
|
|
115
|
+
if (readTime) {
|
|
116
|
+
const r = await this.cfg.firestore.runTransaction(tx => tx.get(firestoreQuery.count()), {
|
|
117
|
+
readOnly: true,
|
|
118
|
+
readTime,
|
|
119
|
+
});
|
|
120
|
+
return r.data().count;
|
|
121
|
+
}
|
|
93
122
|
const r = await firestoreQuery.count().get();
|
|
94
123
|
return r.data().count;
|
|
95
124
|
}
|
|
@@ -101,7 +130,7 @@ export class FirestoreDB extends BaseCommonDB {
|
|
|
101
130
|
...this.cfg.streamOptions,
|
|
102
131
|
...opt_,
|
|
103
132
|
};
|
|
104
|
-
if (opt.experimentalCursorStream) {
|
|
133
|
+
if (opt.experimentalCursorStream || opt.readAt) {
|
|
105
134
|
return Pipeline.from(new FirestoreStreamReadable(firestoreQuery, q, opt));
|
|
106
135
|
}
|
|
107
136
|
if (opt.experimentalShardedStream) {
|
|
@@ -18,6 +18,7 @@ export declare class FirestoreStreamReadable<T extends ObjectWithId = any> exten
|
|
|
18
18
|
* For debugging.
|
|
19
19
|
*/
|
|
20
20
|
countReads: number;
|
|
21
|
+
private readonly readTime?;
|
|
21
22
|
private readonly opt;
|
|
22
23
|
private logger;
|
|
23
24
|
constructor(q: Query, dbQuery: DBQuery<T>, opt: FirestoreDBStreamOptions);
|
|
@@ -6,6 +6,7 @@ import { TimeoutError } from '@naturalcycles/js-lib/error/error.util.js';
|
|
|
6
6
|
import { createCommonLoggerAtLevel } from '@naturalcycles/js-lib/log';
|
|
7
7
|
import { pRetry } from '@naturalcycles/js-lib/promise/pRetry.js';
|
|
8
8
|
import { unescapeDocId } from './firestore.util.js';
|
|
9
|
+
import { readAtToReadTime } from './query.util.js';
|
|
9
10
|
export class FirestoreStreamReadable extends Readable {
|
|
10
11
|
q;
|
|
11
12
|
table;
|
|
@@ -20,6 +21,7 @@ export class FirestoreStreamReadable extends Readable {
|
|
|
20
21
|
* For debugging.
|
|
21
22
|
*/
|
|
22
23
|
countReads = 0;
|
|
24
|
+
readTime;
|
|
23
25
|
opt;
|
|
24
26
|
logger;
|
|
25
27
|
constructor(q, dbQuery, opt) {
|
|
@@ -34,7 +36,7 @@ export class FirestoreStreamReadable extends Readable {
|
|
|
34
36
|
batchSize,
|
|
35
37
|
highWaterMark,
|
|
36
38
|
};
|
|
37
|
-
|
|
39
|
+
this.readTime = readAtToReadTime(opt);
|
|
38
40
|
this.originalLimit = dbQuery._limitValue;
|
|
39
41
|
this.table = dbQuery.table;
|
|
40
42
|
const logger = createCommonLoggerAtLevel(opt.logger, opt.logLevel);
|
|
@@ -135,6 +137,12 @@ export class FirestoreStreamReadable extends Readable {
|
|
|
135
137
|
const { table, logger } = this;
|
|
136
138
|
try {
|
|
137
139
|
return await pRetry(async () => {
|
|
140
|
+
if (this.readTime) {
|
|
141
|
+
return await this.q.firestore.runTransaction(tx => tx.get(q), {
|
|
142
|
+
readOnly: true,
|
|
143
|
+
readTime: this.readTime,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
138
146
|
return await q.get();
|
|
139
147
|
}, {
|
|
140
148
|
name: `FirestoreStreamReadable.query(${table})`,
|
package/dist/query.util.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Timestamp } from '@google-cloud/firestore';
|
|
1
2
|
import type { Query } from '@google-cloud/firestore';
|
|
2
|
-
import type { DBQuery } from '@naturalcycles/db-lib';
|
|
3
|
+
import type { CommonDBReadOptions, DBQuery } from '@naturalcycles/db-lib';
|
|
3
4
|
import type { ObjectWithId } from '@naturalcycles/js-lib/types';
|
|
5
|
+
export declare function readAtToReadTime(opt: CommonDBReadOptions): Timestamp | undefined;
|
|
4
6
|
export declare function dbQueryToFirestoreQuery<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, emptyQuery: Query): Query;
|
package/dist/query.util.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import { FieldPath } from '@google-cloud/firestore';
|
|
1
|
+
import { FieldPath, Timestamp } from '@google-cloud/firestore';
|
|
2
|
+
import { _round } from '@naturalcycles/js-lib';
|
|
3
|
+
export function readAtToReadTime(opt) {
|
|
4
|
+
if (!opt.readAt)
|
|
5
|
+
return;
|
|
6
|
+
// Same logic as Datastore: round to whole minutes, guard against future
|
|
7
|
+
let readTimeMs = _round(opt.readAt, 60) * 1000;
|
|
8
|
+
if (readTimeMs >= Date.now() - 1000) {
|
|
9
|
+
readTimeMs -= 60_000;
|
|
10
|
+
}
|
|
11
|
+
return Timestamp.fromMillis(readTimeMs);
|
|
12
|
+
}
|
|
2
13
|
// Map DBQueryFilterOp to WhereFilterOp
|
|
3
14
|
// Currently it's fully aligned!
|
|
4
15
|
const OP_MAP = {};
|
package/package.json
CHANGED
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@types/node": "^25",
|
|
13
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
14
|
-
"dotenv": "^17",
|
|
13
|
+
"@typescript/native-preview": "7.0.0-dev.20260301.1",
|
|
15
14
|
"firebase-admin": "^13",
|
|
16
15
|
"@naturalcycles/dev-lib": "18.4.2"
|
|
17
16
|
},
|
|
@@ -39,7 +38,7 @@
|
|
|
39
38
|
"engines": {
|
|
40
39
|
"node": ">=24.10.0"
|
|
41
40
|
},
|
|
42
|
-
"version": "2.
|
|
41
|
+
"version": "2.15.0",
|
|
43
42
|
"description": "Firestore implementation of CommonDB interface",
|
|
44
43
|
"author": "Natural Cycles Team",
|
|
45
44
|
"license": "MIT",
|
package/src/firestore.db.ts
CHANGED
|
@@ -35,7 +35,7 @@ import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream'
|
|
|
35
35
|
import { escapeDocId, unescapeDocId } from './firestore.util.js'
|
|
36
36
|
import { FirestoreShardedReadable } from './firestoreShardedReadable.js'
|
|
37
37
|
import { FirestoreStreamReadable } from './firestoreStreamReadable.js'
|
|
38
|
-
import { dbQueryToFirestoreQuery } from './query.util.js'
|
|
38
|
+
import { dbQueryToFirestoreQuery, readAtToReadTime } from './query.util.js'
|
|
39
39
|
|
|
40
40
|
export class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
41
41
|
constructor(cfg: FirestoreDBCfg) {
|
|
@@ -63,16 +63,20 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
|
63
63
|
): Promise<ROW[]> {
|
|
64
64
|
if (!ids.length) return []
|
|
65
65
|
|
|
66
|
-
// todo: support PITR: https://firebase.google.com/docs/firestore/enterprise/use-pitr#read-pitr
|
|
67
|
-
|
|
68
66
|
const { firestore } = this.cfg
|
|
69
67
|
const col = firestore.collection(table)
|
|
68
|
+
const refs = ids.map(id => col.doc(escapeDocId(id)))
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
const readTime = readAtToReadTime(opt)
|
|
71
|
+
if (readTime) {
|
|
72
|
+
_assert(!opt.tx, 'readAt is not supported inside an existing transaction')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const snapshots = await (readTime
|
|
76
|
+
? firestore.runTransaction(tx => tx.getAll(...refs), { readOnly: true, readTime })
|
|
77
|
+
: ((opt.tx as FirestoreDBTransaction)?.tx || firestore).getAll(...refs))
|
|
78
|
+
|
|
79
|
+
return snapshots
|
|
76
80
|
.map(doc => {
|
|
77
81
|
const data = doc.data()
|
|
78
82
|
if (data === undefined) return
|
|
@@ -97,7 +101,15 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
|
97
101
|
refs.push(...ids.map(id => col.doc(escapeDocId(id))))
|
|
98
102
|
}
|
|
99
103
|
|
|
100
|
-
const
|
|
104
|
+
const readTime = readAtToReadTime(opt)
|
|
105
|
+
if (readTime) {
|
|
106
|
+
_assert(!opt.tx, 'readAt is not supported inside an existing transaction')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const snapshots = await (readTime
|
|
110
|
+
? firestore.runTransaction(tx => tx.getAll(...refs), { readOnly: true, readTime })
|
|
111
|
+
: ((opt.tx as FirestoreDBTransaction)?.tx || firestore).getAll(...refs))
|
|
112
|
+
|
|
101
113
|
snapshots.forEach(snap => {
|
|
102
114
|
const data = snap.data()
|
|
103
115
|
if (data === undefined) return
|
|
@@ -115,7 +127,7 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
|
115
127
|
// QUERY
|
|
116
128
|
override async runQuery<ROW extends ObjectWithId>(
|
|
117
129
|
q: DBQuery<ROW>,
|
|
118
|
-
opt
|
|
130
|
+
opt: FirestoreDBReadOptions = {},
|
|
119
131
|
): Promise<RunQueryResult<ROW>> {
|
|
120
132
|
const idFilter = q._filters.find(f => f.name === 'id')
|
|
121
133
|
if (idFilter) {
|
|
@@ -127,7 +139,7 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
|
127
139
|
|
|
128
140
|
const firestoreQuery = dbQueryToFirestoreQuery(q, this.cfg.firestore.collection(q.table))
|
|
129
141
|
|
|
130
|
-
let rows = await this.runFirestoreQuery<ROW>(firestoreQuery)
|
|
142
|
+
let rows = await this.runFirestoreQuery<ROW>(firestoreQuery, opt)
|
|
131
143
|
|
|
132
144
|
// Special case when projection query didn't specify 'id'
|
|
133
145
|
if (q._selectedFieldNames && !q._selectedFieldNames.includes('id')) {
|
|
@@ -137,15 +149,36 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
|
137
149
|
return { rows }
|
|
138
150
|
}
|
|
139
151
|
|
|
140
|
-
async runFirestoreQuery<ROW extends ObjectWithId>(
|
|
152
|
+
async runFirestoreQuery<ROW extends ObjectWithId>(
|
|
153
|
+
q: Query,
|
|
154
|
+
opt: FirestoreDBReadOptions = {},
|
|
155
|
+
): Promise<ROW[]> {
|
|
156
|
+
const readTime = readAtToReadTime(opt)
|
|
157
|
+
if (readTime) {
|
|
158
|
+
const qs = await this.cfg.firestore.runTransaction(tx => tx.get(q), {
|
|
159
|
+
readOnly: true,
|
|
160
|
+
readTime,
|
|
161
|
+
})
|
|
162
|
+
return this.querySnapshotToArray(qs)
|
|
163
|
+
}
|
|
141
164
|
return this.querySnapshotToArray(await q.get())
|
|
142
165
|
}
|
|
143
166
|
|
|
144
167
|
override async runQueryCount<ROW extends ObjectWithId>(
|
|
145
168
|
q: DBQuery<ROW>,
|
|
146
|
-
|
|
169
|
+
opt: FirestoreDBReadOptions = {},
|
|
147
170
|
): Promise<number> {
|
|
148
171
|
const firestoreQuery = dbQueryToFirestoreQuery(q, this.cfg.firestore.collection(q.table))
|
|
172
|
+
|
|
173
|
+
const readTime = readAtToReadTime(opt)
|
|
174
|
+
if (readTime) {
|
|
175
|
+
const r = await this.cfg.firestore.runTransaction(tx => tx.get(firestoreQuery.count()), {
|
|
176
|
+
readOnly: true,
|
|
177
|
+
readTime,
|
|
178
|
+
})
|
|
179
|
+
return r.data().count
|
|
180
|
+
}
|
|
181
|
+
|
|
149
182
|
const r = await firestoreQuery.count().get()
|
|
150
183
|
return r.data().count
|
|
151
184
|
}
|
|
@@ -163,7 +196,7 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
|
163
196
|
...opt_,
|
|
164
197
|
}
|
|
165
198
|
|
|
166
|
-
if (opt.experimentalCursorStream) {
|
|
199
|
+
if (opt.experimentalCursorStream || opt.readAt) {
|
|
167
200
|
return Pipeline.from(new FirestoreStreamReadable(firestoreQuery, q, opt))
|
|
168
201
|
}
|
|
169
202
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { Readable } from 'node:stream'
|
|
2
2
|
import { FieldPath } from '@google-cloud/firestore'
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
Query,
|
|
5
|
+
QueryDocumentSnapshot,
|
|
6
|
+
QuerySnapshot,
|
|
7
|
+
Timestamp,
|
|
8
|
+
} from '@google-cloud/firestore'
|
|
4
9
|
import type { DBQuery } from '@naturalcycles/db-lib'
|
|
5
10
|
import { localTime } from '@naturalcycles/js-lib/datetime/localTime.js'
|
|
6
11
|
import { _ms } from '@naturalcycles/js-lib/datetime/time.util.js'
|
|
@@ -12,6 +17,7 @@ import type { ObjectWithId } from '@naturalcycles/js-lib/types'
|
|
|
12
17
|
import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream'
|
|
13
18
|
import type { FirestoreDBStreamOptions } from './firestore.db.js'
|
|
14
19
|
import { unescapeDocId } from './firestore.util.js'
|
|
20
|
+
import { readAtToReadTime } from './query.util.js'
|
|
15
21
|
|
|
16
22
|
export class FirestoreStreamReadable<T extends ObjectWithId = any>
|
|
17
23
|
extends Readable
|
|
@@ -30,6 +36,7 @@ export class FirestoreStreamReadable<T extends ObjectWithId = any>
|
|
|
30
36
|
*/
|
|
31
37
|
countReads = 0
|
|
32
38
|
|
|
39
|
+
private readonly readTime?: Timestamp
|
|
33
40
|
private readonly opt: FirestoreDBStreamOptions & { batchSize: number; highWaterMark: number }
|
|
34
41
|
private logger: CommonLogger
|
|
35
42
|
|
|
@@ -49,7 +56,7 @@ export class FirestoreStreamReadable<T extends ObjectWithId = any>
|
|
|
49
56
|
batchSize,
|
|
50
57
|
highWaterMark,
|
|
51
58
|
}
|
|
52
|
-
|
|
59
|
+
this.readTime = readAtToReadTime(opt)
|
|
53
60
|
|
|
54
61
|
this.originalLimit = dbQuery._limitValue
|
|
55
62
|
this.table = dbQuery.table
|
|
@@ -176,6 +183,12 @@ export class FirestoreStreamReadable<T extends ObjectWithId = any>
|
|
|
176
183
|
try {
|
|
177
184
|
return await pRetry(
|
|
178
185
|
async () => {
|
|
186
|
+
if (this.readTime) {
|
|
187
|
+
return await this.q.firestore.runTransaction(tx => tx.get(q), {
|
|
188
|
+
readOnly: true,
|
|
189
|
+
readTime: this.readTime,
|
|
190
|
+
})
|
|
191
|
+
}
|
|
179
192
|
return await q.get()
|
|
180
193
|
},
|
|
181
194
|
{
|
package/src/query.util.ts
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
import { FieldPath } from '@google-cloud/firestore'
|
|
1
|
+
import { FieldPath, Timestamp } from '@google-cloud/firestore'
|
|
2
2
|
import type { Query, WhereFilterOp } from '@google-cloud/firestore'
|
|
3
|
-
import type { DBQuery, DBQueryFilterOperator } from '@naturalcycles/db-lib'
|
|
3
|
+
import type { CommonDBReadOptions, DBQuery, DBQueryFilterOperator } from '@naturalcycles/db-lib'
|
|
4
|
+
import { _round } from '@naturalcycles/js-lib'
|
|
4
5
|
import type { ObjectWithId } from '@naturalcycles/js-lib/types'
|
|
5
6
|
|
|
7
|
+
export function readAtToReadTime(opt: CommonDBReadOptions): Timestamp | undefined {
|
|
8
|
+
if (!opt.readAt) return
|
|
9
|
+
|
|
10
|
+
// Same logic as Datastore: round to whole minutes, guard against future
|
|
11
|
+
let readTimeMs = _round(opt.readAt, 60) * 1000
|
|
12
|
+
if (readTimeMs >= Date.now() - 1000) {
|
|
13
|
+
readTimeMs -= 60_000
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return Timestamp.fromMillis(readTimeMs)
|
|
17
|
+
}
|
|
18
|
+
|
|
6
19
|
// Map DBQueryFilterOp to WhereFilterOp
|
|
7
20
|
// Currently it's fully aligned!
|
|
8
21
|
const OP_MAP: Partial<Record<DBQueryFilterOperator, WhereFilterOp>> = {
|