@naturalcycles/db-lib 9.23.2 → 9.24.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,6 +1,6 @@
1
1
  import { AnyObjectWithId, CommonLogger, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId, StringMap } from '@naturalcycles/js-lib';
2
2
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
3
- import { CommonDB, CommonDBTransactionOptions, CommonDBType, DBOperation, DBTransactionFn } from '../..';
3
+ import { CommonDB, CommonDBSupport, CommonDBTransactionOptions, CommonDBType, DBOperation, DBTransactionFn } from '../..';
4
4
  import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions, DBTransaction, RunQueryResult } from '../../db.model';
5
5
  import { DBQuery } from '../../query/dbQuery';
6
6
  export interface InMemoryDBCfg {
@@ -47,23 +47,7 @@ export interface InMemoryDBCfg {
47
47
  }
48
48
  export declare class InMemoryDB implements CommonDB {
49
49
  dbType: CommonDBType;
50
- support: {
51
- queries?: boolean;
52
- dbQueryFilter?: boolean;
53
- dbQueryFilterIn?: boolean;
54
- dbQueryOrder?: boolean;
55
- dbQuerySelectFields?: boolean;
56
- insertSaveMethod?: boolean;
57
- updateSaveMethod?: boolean;
58
- patchByQuery?: boolean;
59
- increment?: boolean;
60
- createTable?: boolean;
61
- tableSchemas?: boolean;
62
- streaming?: boolean;
63
- bufferValues?: boolean;
64
- nullValues?: boolean;
65
- transactions?: boolean;
66
- };
50
+ support: CommonDBSupport;
67
51
  constructor(cfg?: Partial<InMemoryDBCfg>);
68
52
  cfg: InMemoryDBCfg;
69
53
  data: StringMap<StringMap<AnyObjectWithId>>;
@@ -10,6 +10,7 @@ class InMemoryDB {
10
10
  this.dbType = __1.CommonDBType.document;
11
11
  this.support = {
12
12
  ...__1.commonDBFullSupport,
13
+ timeMachine: false,
13
14
  };
14
15
  // data[table][id] > {id: 'a', created: ... }
15
16
  this.data = {};
@@ -1,6 +1,6 @@
1
1
  import { JsonSchemaObject, JsonSchemaRootObject, ObjectWithId, StringMap } from '@naturalcycles/js-lib';
2
2
  import type { ReadableTyped } from '@naturalcycles/nodejs-lib';
3
- import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions, CommonDBStreamOptions, CommonDBTransactionOptions, DBTransactionFn, RunQueryResult } from './db.model';
3
+ import { CommonDBCreateOptions, CommonDBOptions, CommonDBReadOptions, CommonDBSaveOptions, CommonDBStreamOptions, CommonDBTransactionOptions, DBTransactionFn, RunQueryResult } from './db.model';
4
4
  import { DBQuery } from './query/dbQuery';
5
5
  export declare enum CommonDBType {
6
6
  'document' = "document",
@@ -42,12 +42,12 @@ export interface CommonDB {
42
42
  * Order of items returned is not guaranteed to match order of ids.
43
43
  * (Such limitation exists because Datastore doesn't support it).
44
44
  */
45
- getByIds: <ROW extends ObjectWithId>(table: string, ids: string[], opt?: CommonDBOptions) => Promise<ROW[]>;
45
+ getByIds: <ROW extends ObjectWithId>(table: string, ids: string[], opt?: CommonDBReadOptions) => Promise<ROW[]>;
46
46
  /**
47
47
  * Order by 'id' is not supported by all implementations (for example, Datastore doesn't support it).
48
48
  */
49
- runQuery: <ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBOptions) => Promise<RunQueryResult<ROW>>;
50
- runQueryCount: <ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBOptions) => Promise<number>;
49
+ runQuery: <ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBReadOptions) => Promise<RunQueryResult<ROW>>;
50
+ runQueryCount: <ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBReadOptions) => Promise<number>;
51
51
  streamQuery: <ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBStreamOptions) => ReadableTyped<ROW>;
52
52
  /**
53
53
  * rows can have missing ids only if DB supports auto-generating them (like mysql auto_increment).
@@ -74,7 +74,7 @@ export interface CommonDB {
74
74
  *
75
75
  * Returns the number of rows affected.
76
76
  */
77
- patchByQuery: <ROW extends ObjectWithId>(q: DBQuery<ROW>, patch: Partial<ROW>, opt?: CommonDBOptions) => Promise<number>;
77
+ patchByQuery: <ROW extends ObjectWithId>(q: DBQuery<ROW>, patch: Partial<ROW>, opt?: CommonDBReadOptions) => Promise<number>;
78
78
  /**
79
79
  * Should be implemented as a Transaction (best effort), which means that
80
80
  * either ALL or NONE of the operations should be applied.
@@ -123,5 +123,6 @@ export interface CommonDBSupport {
123
123
  bufferValues?: boolean;
124
124
  nullValues?: boolean;
125
125
  transactions?: boolean;
126
+ timeMachine?: boolean;
126
127
  }
127
128
  export declare const commonDBFullSupport: CommonDBSupport;
package/dist/common.db.js CHANGED
@@ -22,4 +22,5 @@ exports.commonDBFullSupport = {
22
22
  bufferValues: true,
23
23
  nullValues: true,
24
24
  transactions: true,
25
+ timeMachine: true,
25
26
  };
@@ -3,7 +3,7 @@ import { AsyncMapper, BaseDBEntity, CommonLogger, JsonSchemaObject, JsonSchemaRo
3
3
  import { AjvSchema, ObjectSchema, ReadableTyped } from '@naturalcycles/nodejs-lib';
4
4
  import { CommonDBTransactionOptions, DBTransaction, RunQueryResult } from '../db.model';
5
5
  import { DBQuery, RunnableDBQuery } from '../query/dbQuery';
6
- import { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoPatchByIdOptions, CommonDaoPatchOptions, CommonDaoSaveBatchOptions, CommonDaoSaveOptions, CommonDaoStreamDeleteOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoStreamSaveOptions } from './common.dao.model';
6
+ import { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoPatchByIdOptions, CommonDaoPatchOptions, CommonDaoReadOptions, CommonDaoSaveBatchOptions, CommonDaoSaveOptions, CommonDaoStreamDeleteOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoStreamSaveOptions } from './common.dao.model';
7
7
  /**
8
8
  * Lowest common denominator API between supported Databases.
9
9
  *
@@ -15,13 +15,13 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
15
15
  cfg: CommonDaoCfg<BM, DBM, ID>;
16
16
  constructor(cfg: CommonDaoCfg<BM, DBM, ID>);
17
17
  create(part?: Partial<BM>, opt?: CommonDaoOptions): BM;
18
- getById(id?: ID | null, opt?: CommonDaoOptions): Promise<BM | null>;
19
- getByIdOrEmpty(id: ID, part?: Partial<BM>, opt?: CommonDaoOptions): Promise<BM>;
20
- getByIdAsDBM(id?: ID | null, opt?: CommonDaoOptions): Promise<DBM | null>;
21
- getByIds(ids: ID[], opt?: CommonDaoOptions): Promise<BM[]>;
22
- getByIdsAsDBM(ids: ID[], opt?: CommonDaoOptions): Promise<DBM[]>;
23
- requireById(id: ID, opt?: CommonDaoOptions): Promise<BM>;
24
- requireByIdAsDBM(id: ID, opt?: CommonDaoOptions): Promise<DBM>;
18
+ getById(id?: ID | null, opt?: CommonDaoReadOptions): Promise<BM | null>;
19
+ getByIdOrEmpty(id: ID, part?: Partial<BM>, opt?: CommonDaoReadOptions): Promise<BM>;
20
+ getByIdAsDBM(id?: ID | null, opt?: CommonDaoReadOptions): Promise<DBM | null>;
21
+ getByIds(ids: ID[], opt?: CommonDaoReadOptions): Promise<BM[]>;
22
+ getByIdsAsDBM(ids: ID[], opt?: CommonDaoReadOptions): Promise<DBM[]>;
23
+ requireById(id: ID, opt?: CommonDaoReadOptions): Promise<BM>;
24
+ requireByIdAsDBM(id: ID, opt?: CommonDaoReadOptions): Promise<DBM>;
25
25
  private throwRequiredError;
26
26
  /**
27
27
  * Throws if readOnly is true
@@ -32,25 +32,25 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
32
32
  */
33
33
  private requireObjectMutability;
34
34
  private ensureUniqueId;
35
- getBy(by: keyof DBM, value: any, limit?: number, opt?: CommonDaoOptions): Promise<BM[]>;
36
- getOneBy(by: keyof DBM, value: any, opt?: CommonDaoOptions): Promise<BM | null>;
37
- getAll(opt?: CommonDaoOptions): Promise<BM[]>;
35
+ getBy(by: keyof DBM, value: any, limit?: number, opt?: CommonDaoReadOptions): Promise<BM[]>;
36
+ getOneBy(by: keyof DBM, value: any, opt?: CommonDaoReadOptions): Promise<BM | null>;
37
+ getAll(opt?: CommonDaoReadOptions): Promise<BM[]>;
38
38
  /**
39
39
  * Pass `table` to override table
40
40
  */
41
41
  query(table?: string): RunnableDBQuery<BM, DBM, ID>;
42
- runQuery(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<BM[]>;
43
- runQuerySingleColumn<T = any>(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<T[]>;
42
+ runQuery(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<BM[]>;
43
+ runQuerySingleColumn<T = any>(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<T[]>;
44
44
  /**
45
45
  * Convenience method that runs multiple queries in parallel and then merges their results together.
46
46
  * Does deduplication by id.
47
47
  * Order is not guaranteed, as queries run in parallel.
48
48
  */
49
- runUnionQueries(queries: DBQuery<DBM>[], opt?: CommonDaoOptions): Promise<BM[]>;
50
- runQueryExtended(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<RunQueryResult<BM>>;
51
- runQueryAsDBM(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<DBM[]>;
52
- runQueryExtendedAsDBM(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<RunQueryResult<DBM>>;
53
- runQueryCount(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<number>;
49
+ runUnionQueries(queries: DBQuery<DBM>[], opt?: CommonDaoReadOptions): Promise<BM[]>;
50
+ runQueryExtended(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<RunQueryResult<BM>>;
51
+ runQueryAsDBM(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<DBM[]>;
52
+ runQueryExtendedAsDBM(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<RunQueryResult<DBM>>;
53
+ runQueryCount(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<number>;
54
54
  streamQueryForEach(q: DBQuery<DBM>, mapper: AsyncMapper<BM, void>, opt?: CommonDaoStreamForEachOptions<BM>): Promise<void>;
55
55
  streamQueryAsDBMForEach(q: DBQuery<DBM>, mapper: AsyncMapper<DBM, void>, opt?: CommonDaoStreamForEachOptions<DBM>): Promise<void>;
56
56
  /**
@@ -67,7 +67,7 @@ export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity
67
67
  * You can do `.pipe(transformNoOp)` to make it "valid again".
68
68
  */
69
69
  streamQuery(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions<BM>): ReadableTyped<BM>;
70
- queryIds(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<ID[]>;
70
+ queryIds(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<ID[]>;
71
71
  streamQueryIds(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions<ID>): ReadableTyped<ID>;
72
72
  streamQueryIdsForEach(q: DBQuery<DBM>, mapper: AsyncMapper<ID, void>, opt?: CommonDaoStreamForEachOptions<ID>): Promise<void>;
73
73
  /**
@@ -193,8 +193,8 @@ export declare class CommonDaoTransaction {
193
193
  * Perform a graceful rollback without throwing/re-throwing any error.
194
194
  */
195
195
  rollback(): Promise<void>;
196
- getById<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(dao: CommonDao<BM, DBM, ID>, id?: ID | null, opt?: CommonDaoOptions): Promise<BM | null>;
197
- getByIds<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(dao: CommonDao<BM, DBM, ID>, ids: ID[], opt?: CommonDaoOptions): Promise<BM[]>;
196
+ getById<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(dao: CommonDao<BM, DBM, ID>, id?: ID | null, opt?: CommonDaoReadOptions): Promise<BM | null>;
197
+ getByIds<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(dao: CommonDao<BM, DBM, ID>, ids: ID[], opt?: CommonDaoReadOptions): Promise<BM[]>;
198
198
  save<BM extends BaseDBEntity, DBM extends BaseDBEntity>(dao: CommonDao<BM, DBM>, bm: Unsaved<BM>, opt?: CommonDaoSaveOptions<BM, DBM>): Promise<BM>;
199
199
  saveBatch<BM extends BaseDBEntity, DBM extends BaseDBEntity>(dao: CommonDao<BM, DBM>, bms: Unsaved<BM>[], opt?: CommonDaoSaveBatchOptions<DBM>): Promise<BM[]>;
200
200
  /**
@@ -1,4 +1,4 @@
1
- import { BaseDBEntity, CommonLogger, ErrorMode, Promisable, ZodError, ZodSchema } from '@naturalcycles/js-lib';
1
+ import { BaseDBEntity, CommonLogger, ErrorMode, Promisable, UnixTimestampNumber, ZodError, ZodSchema } from '@naturalcycles/js-lib';
2
2
  import { AjvSchema, AjvValidationError, JoiValidationError, ObjectSchema, TransformLogProgressOptions, TransformMapOptions } from '@naturalcycles/nodejs-lib';
3
3
  import { CommonDB } from '../common.db';
4
4
  import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions } from '../db.model';
@@ -200,6 +200,13 @@ export interface CommonDaoOptions extends CommonDBOptions {
200
200
  */
201
201
  table?: string;
202
202
  }
203
+ export interface CommonDaoReadOptions extends CommonDaoOptions {
204
+ /**
205
+ * If provided (and supported by the DB) - will read the data at that point in time (aka "Time machine" feature).
206
+ * This feature is named PITR (point-in-time-recovery) query in Datastore.
207
+ */
208
+ readAt?: UnixTimestampNumber;
209
+ }
203
210
  export interface CommonDaoSaveOptions<BM extends BaseDBEntity, DBM extends BaseDBEntity> extends CommonDaoSaveBatchOptions<DBM> {
204
211
  /**
205
212
  * If provided - a check will be made.
@@ -248,7 +255,7 @@ export interface CommonDaoStreamSaveOptions<DBM extends BaseDBEntity> extends Co
248
255
  }
249
256
  export interface CommonDaoStreamForEachOptions<IN> extends CommonDaoStreamOptions<IN>, TransformMapOptions<IN, any> {
250
257
  }
251
- export interface CommonDaoStreamOptions<IN> extends CommonDaoOptions, TransformLogProgressOptions<IN> {
258
+ export interface CommonDaoStreamOptions<IN> extends CommonDaoReadOptions, TransformLogProgressOptions<IN> {
252
259
  /**
253
260
  * @default true (for streams)
254
261
  */
@@ -1,4 +1,4 @@
1
- import { ObjectWithId } from '@naturalcycles/js-lib';
1
+ import { ObjectWithId, UnixTimestampNumber } from '@naturalcycles/js-lib';
2
2
  import { CommonDB } from './common.db';
3
3
  /**
4
4
  * Similar to SQL INSERT, UPDATE.
@@ -45,6 +45,13 @@ export interface CommonDBOptions {
45
45
  */
46
46
  tx?: DBTransaction;
47
47
  }
48
+ export interface CommonDBReadOptions extends CommonDBOptions {
49
+ /**
50
+ * If provided (and supported by the DB) - will read the data at that point in time (aka "Time machine" feature).
51
+ * This feature is named PITR (point-in-time-recovery) query in Datastore.
52
+ */
53
+ readAt?: UnixTimestampNumber;
54
+ }
48
55
  /**
49
56
  * All properties default to undefined.
50
57
  */
@@ -62,7 +69,7 @@ export interface CommonDBSaveOptions<ROW extends ObjectWithId> extends CommonDBO
62
69
  */
63
70
  assignGeneratedIds?: boolean;
64
71
  }
65
- export type CommonDBStreamOptions = CommonDBOptions;
72
+ export type CommonDBStreamOptions = CommonDBReadOptions;
66
73
  export interface CommonDBCreateOptions extends CommonDBOptions {
67
74
  /**
68
75
  * Caution! If set to true - will actually DROP the table!
@@ -1,6 +1,6 @@
1
1
  import { AsyncMapper, BaseDBEntity, ObjectWithId } from '@naturalcycles/js-lib';
2
2
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
3
- import { CommonDaoOptions, CommonDaoStreamDeleteOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions } from '..';
3
+ import { CommonDaoOptions, CommonDaoReadOptions, CommonDaoStreamDeleteOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions } from '..';
4
4
  import { CommonDao } from '../commondao/common.dao';
5
5
  import { RunQueryResult } from '../db.model';
6
6
  /**
@@ -93,18 +93,18 @@ export declare class RunnableDBQuery<BM extends BaseDBEntity, DBM extends BaseDB
93
93
  * Pass `table` to override table.
94
94
  */
95
95
  constructor(dao: CommonDao<BM, DBM, ID>, table?: string);
96
- runQuery(opt?: CommonDaoOptions): Promise<BM[]>;
97
- runQuerySingleColumn<T = any>(opt?: CommonDaoOptions): Promise<T[]>;
98
- runQueryAsDBM(opt?: CommonDaoOptions): Promise<DBM[]>;
99
- runQueryExtended(opt?: CommonDaoOptions): Promise<RunQueryResult<BM>>;
100
- runQueryExtendedAsDBM(opt?: CommonDaoOptions): Promise<RunQueryResult<DBM>>;
101
- runQueryCount(opt?: CommonDaoOptions): Promise<number>;
96
+ runQuery(opt?: CommonDaoReadOptions): Promise<BM[]>;
97
+ runQuerySingleColumn<T = any>(opt?: CommonDaoReadOptions): Promise<T[]>;
98
+ runQueryAsDBM(opt?: CommonDaoReadOptions): Promise<DBM[]>;
99
+ runQueryExtended(opt?: CommonDaoReadOptions): Promise<RunQueryResult<BM>>;
100
+ runQueryExtendedAsDBM(opt?: CommonDaoReadOptions): Promise<RunQueryResult<DBM>>;
101
+ runQueryCount(opt?: CommonDaoReadOptions): Promise<number>;
102
102
  patchByQuery(patch: Partial<DBM>, opt?: CommonDaoOptions): Promise<number>;
103
103
  streamQueryForEach(mapper: AsyncMapper<BM, void>, opt?: CommonDaoStreamForEachOptions<BM>): Promise<void>;
104
104
  streamQueryAsDBMForEach(mapper: AsyncMapper<DBM, void>, opt?: CommonDaoStreamForEachOptions<DBM>): Promise<void>;
105
105
  streamQuery(opt?: CommonDaoStreamOptions<BM>): ReadableTyped<BM>;
106
106
  streamQueryAsDBM(opt?: CommonDaoStreamOptions<DBM>): ReadableTyped<DBM>;
107
- queryIds(opt?: CommonDaoOptions): Promise<ID[]>;
107
+ queryIds(opt?: CommonDaoReadOptions): Promise<ID[]>;
108
108
  streamQueryIds(opt?: CommonDaoStreamOptions<ID>): ReadableTyped<ID>;
109
109
  streamQueryIdsForEach(mapper: AsyncMapper<ID, void>, opt?: CommonDaoStreamForEachOptions<ID>): Promise<void>;
110
110
  deleteByQuery(opt?: CommonDaoStreamDeleteOptions<DBM>): Promise<number>;
@@ -58,6 +58,14 @@ function runCommonDaoTest(db, quirks = {}) {
58
58
  test('getByIds(...) should return empty', async () => {
59
59
  expect(await dao.getByIds(['abc', 'abcd'])).toEqual([]);
60
60
  });
61
+ // TimeMachine
62
+ if (support.timeMachine) {
63
+ test('getByIds(...) 10 minutes ago should return []', async () => {
64
+ expect(await dao.getByIds([item1.id, 'abc'], {
65
+ readAt: js_lib_1.localTime.now().minus(10, 'minute').unix,
66
+ })).toEqual([]);
67
+ });
68
+ }
61
69
  // SAVE
62
70
  if (support.nullValues) {
63
71
  test('should allow to save and load null values', async () => {
@@ -45,6 +45,14 @@ function runCommonDBTest(db, quirks = {}) {
45
45
  test('getByIds(...) should return empty', async () => {
46
46
  expect(await db.getByIds(test_model_1.TEST_TABLE, ['abc', 'abcd'])).toEqual([]);
47
47
  });
48
+ // TimeMachine
49
+ if (support.timeMachine) {
50
+ test('getByIds(...) 10 minutes ago should return []', async () => {
51
+ expect(await db.getByIds(test_model_1.TEST_TABLE, [item1.id, 'abc'], {
52
+ readAt: js_lib_1.localTime.now().minus(10, 'minute').unix,
53
+ })).toEqual([]);
54
+ });
55
+ }
48
56
  // SAVE
49
57
  if (support.nullValues) {
50
58
  test('should allow to save and load null values', async () => {
package/package.json CHANGED
@@ -45,7 +45,7 @@
45
45
  "engines": {
46
46
  "node": ">=20.13"
47
47
  },
48
- "version": "9.23.2",
48
+ "version": "9.24.1",
49
49
  "description": "Lowest Common Denominator API to supported Databases",
50
50
  "keywords": [
51
51
  "db",
@@ -28,6 +28,7 @@ import {
28
28
  import {
29
29
  CommonDB,
30
30
  commonDBFullSupport,
31
+ CommonDBSupport,
31
32
  CommonDBTransactionOptions,
32
33
  CommonDBType,
33
34
  DBOperation,
@@ -94,8 +95,9 @@ export interface InMemoryDBCfg {
94
95
  export class InMemoryDB implements CommonDB {
95
96
  dbType = CommonDBType.document
96
97
 
97
- support = {
98
+ support: CommonDBSupport = {
98
99
  ...commonDBFullSupport,
100
+ timeMachine: false,
99
101
  }
100
102
 
101
103
  constructor(cfg?: Partial<InMemoryDBCfg>) {
package/src/common.db.ts CHANGED
@@ -8,6 +8,7 @@ import type { ReadableTyped } from '@naturalcycles/nodejs-lib'
8
8
  import {
9
9
  CommonDBCreateOptions,
10
10
  CommonDBOptions,
11
+ CommonDBReadOptions,
11
12
  CommonDBSaveOptions,
12
13
  CommonDBStreamOptions,
13
14
  CommonDBTransactionOptions,
@@ -71,7 +72,7 @@ export interface CommonDB {
71
72
  getByIds: <ROW extends ObjectWithId>(
72
73
  table: string,
73
74
  ids: string[],
74
- opt?: CommonDBOptions,
75
+ opt?: CommonDBReadOptions,
75
76
  ) => Promise<ROW[]>
76
77
 
77
78
  // QUERY
@@ -80,12 +81,12 @@ export interface CommonDB {
80
81
  */
81
82
  runQuery: <ROW extends ObjectWithId>(
82
83
  q: DBQuery<ROW>,
83
- opt?: CommonDBOptions,
84
+ opt?: CommonDBReadOptions,
84
85
  ) => Promise<RunQueryResult<ROW>>
85
86
 
86
87
  runQueryCount: <ROW extends ObjectWithId>(
87
88
  q: DBQuery<ROW>,
88
- opt?: CommonDBOptions,
89
+ opt?: CommonDBReadOptions,
89
90
  ) => Promise<number>
90
91
 
91
92
  streamQuery: <ROW extends ObjectWithId>(
@@ -133,7 +134,7 @@ export interface CommonDB {
133
134
  patchByQuery: <ROW extends ObjectWithId>(
134
135
  q: DBQuery<ROW>,
135
136
  patch: Partial<ROW>,
136
- opt?: CommonDBOptions,
137
+ opt?: CommonDBReadOptions,
137
138
  ) => Promise<number>
138
139
 
139
140
  // TRANSACTION
@@ -192,6 +193,7 @@ export interface CommonDBSupport {
192
193
  bufferValues?: boolean
193
194
  nullValues?: boolean
194
195
  transactions?: boolean
196
+ timeMachine?: boolean
195
197
  }
196
198
 
197
199
  export const commonDBFullSupport: CommonDBSupport = {
@@ -210,4 +212,5 @@ export const commonDBFullSupport: CommonDBSupport = {
210
212
  bufferValues: true,
211
213
  nullValues: true,
212
214
  transactions: true,
215
+ timeMachine: true,
213
216
  }
@@ -3,6 +3,7 @@ import {
3
3
  CommonLogger,
4
4
  ErrorMode,
5
5
  Promisable,
6
+ UnixTimestampNumber,
6
7
  ZodError,
7
8
  ZodSchema,
8
9
  } from '@naturalcycles/js-lib'
@@ -251,6 +252,14 @@ export interface CommonDaoOptions extends CommonDBOptions {
251
252
  table?: string
252
253
  }
253
254
 
255
+ export interface CommonDaoReadOptions extends CommonDaoOptions {
256
+ /**
257
+ * If provided (and supported by the DB) - will read the data at that point in time (aka "Time machine" feature).
258
+ * This feature is named PITR (point-in-time-recovery) query in Datastore.
259
+ */
260
+ readAt?: UnixTimestampNumber
261
+ }
262
+
254
263
  export interface CommonDaoSaveOptions<BM extends BaseDBEntity, DBM extends BaseDBEntity>
255
264
  extends CommonDaoSaveBatchOptions<DBM> {
256
265
  /**
@@ -314,7 +323,7 @@ export interface CommonDaoStreamForEachOptions<IN>
314
323
  TransformMapOptions<IN, any> {}
315
324
 
316
325
  export interface CommonDaoStreamOptions<IN>
317
- extends CommonDaoOptions,
326
+ extends CommonDaoReadOptions,
318
327
  TransformLogProgressOptions<IN> {
319
328
  /**
320
329
  * @default true (for streams)
@@ -55,6 +55,7 @@ import {
55
55
  CommonDaoOptions,
56
56
  CommonDaoPatchByIdOptions,
57
57
  CommonDaoPatchOptions,
58
+ CommonDaoReadOptions,
58
59
  CommonDaoSaveBatchOptions,
59
60
  CommonDaoSaveOptions,
60
61
  CommonDaoStreamDeleteOptions,
@@ -116,7 +117,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
116
117
  // overrides are disabled now, as they obfuscate errors when ID branded type is used
117
118
  // async getById(id: undefined | null, opt?: CommonDaoOptions): Promise<null>
118
119
  // async getById(id?: ID | null, opt?: CommonDaoOptions): Promise<BM | null>
119
- async getById(id?: ID | null, opt: CommonDaoOptions = {}): Promise<BM | null> {
120
+ async getById(id?: ID | null, opt: CommonDaoReadOptions = {}): Promise<BM | null> {
120
121
  if (!id) return null
121
122
  const op = `getById(${id})`
122
123
  const table = opt.table || this.cfg.table
@@ -132,7 +133,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
132
133
  return bm || null
133
134
  }
134
135
 
135
- async getByIdOrEmpty(id: ID, part: Partial<BM> = {}, opt?: CommonDaoOptions): Promise<BM> {
136
+ async getByIdOrEmpty(id: ID, part: Partial<BM> = {}, opt?: CommonDaoReadOptions): Promise<BM> {
136
137
  const bm = await this.getById(id, opt)
137
138
  if (bm) return bm
138
139
 
@@ -141,7 +142,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
141
142
 
142
143
  // async getByIdAsDBM(id: undefined | null, opt?: CommonDaoOptions): Promise<null>
143
144
  // async getByIdAsDBM(id?: ID | null, opt?: CommonDaoOptions): Promise<DBM | null>
144
- async getByIdAsDBM(id?: ID | null, opt: CommonDaoOptions = {}): Promise<DBM | null> {
145
+ async getByIdAsDBM(id?: ID | null, opt: CommonDaoReadOptions = {}): Promise<DBM | null> {
145
146
  if (!id) return null
146
147
  const op = `getByIdAsDBM(${id})`
147
148
  const table = opt.table || this.cfg.table
@@ -156,7 +157,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
156
157
  return dbm || null
157
158
  }
158
159
 
159
- async getByIds(ids: ID[], opt: CommonDaoOptions = {}): Promise<BM[]> {
160
+ async getByIds(ids: ID[], opt: CommonDaoReadOptions = {}): Promise<BM[]> {
160
161
  if (!ids.length) return []
161
162
  const op = `getByIds ${ids.length} id(s) (${_truncate(ids.slice(0, 10).join(', '), 50)})`
162
163
  const table = opt.table || this.cfg.table
@@ -173,7 +174,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
173
174
  return bms
174
175
  }
175
176
 
176
- async getByIdsAsDBM(ids: ID[], opt: CommonDaoOptions = {}): Promise<DBM[]> {
177
+ async getByIdsAsDBM(ids: ID[], opt: CommonDaoReadOptions = {}): Promise<DBM[]> {
177
178
  if (!ids.length) return []
178
179
  const op = `getByIdsAsDBM ${ids.length} id(s) (${_truncate(ids.slice(0, 10).join(', '), 50)})`
179
180
  const table = opt.table || this.cfg.table
@@ -189,7 +190,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
189
190
  return dbms
190
191
  }
191
192
 
192
- async requireById(id: ID, opt: CommonDaoOptions = {}): Promise<BM> {
193
+ async requireById(id: ID, opt: CommonDaoReadOptions = {}): Promise<BM> {
193
194
  const r = await this.getById(id, opt)
194
195
  if (!r) {
195
196
  this.throwRequiredError(id, opt)
@@ -197,7 +198,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
197
198
  return r
198
199
  }
199
200
 
200
- async requireByIdAsDBM(id: ID, opt: CommonDaoOptions = {}): Promise<DBM> {
201
+ async requireByIdAsDBM(id: ID, opt: CommonDaoReadOptions = {}): Promise<DBM> {
201
202
  const r = await this.getByIdAsDBM(id, opt)
202
203
  if (!r) {
203
204
  this.throwRequiredError(id, opt)
@@ -246,16 +247,16 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
246
247
  }
247
248
  }
248
249
 
249
- async getBy(by: keyof DBM, value: any, limit = 0, opt?: CommonDaoOptions): Promise<BM[]> {
250
+ async getBy(by: keyof DBM, value: any, limit = 0, opt?: CommonDaoReadOptions): Promise<BM[]> {
250
251
  return await this.query().filterEq(by, value).limit(limit).runQuery(opt)
251
252
  }
252
253
 
253
- async getOneBy(by: keyof DBM, value: any, opt?: CommonDaoOptions): Promise<BM | null> {
254
+ async getOneBy(by: keyof DBM, value: any, opt?: CommonDaoReadOptions): Promise<BM | null> {
254
255
  const [bm] = await this.query().filterEq(by, value).limit(1).runQuery(opt)
255
256
  return bm || null
256
257
  }
257
258
 
258
- async getAll(opt?: CommonDaoOptions): Promise<BM[]> {
259
+ async getAll(opt?: CommonDaoReadOptions): Promise<BM[]> {
259
260
  return await this.query().runQuery(opt)
260
261
  }
261
262
 
@@ -267,12 +268,12 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
267
268
  return new RunnableDBQuery<BM, DBM, ID>(this, table)
268
269
  }
269
270
 
270
- async runQuery(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<BM[]> {
271
+ async runQuery(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<BM[]> {
271
272
  const { rows } = await this.runQueryExtended(q, opt)
272
273
  return rows
273
274
  }
274
275
 
275
- async runQuerySingleColumn<T = any>(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<T[]> {
276
+ async runQuerySingleColumn<T = any>(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<T[]> {
276
277
  _assert(
277
278
  q._selectedFieldNames?.length === 1,
278
279
  `runQuerySingleColumn requires exactly 1 column to be selected: ${q.pretty()}`,
@@ -289,14 +290,17 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
289
290
  * Does deduplication by id.
290
291
  * Order is not guaranteed, as queries run in parallel.
291
292
  */
292
- async runUnionQueries(queries: DBQuery<DBM>[], opt?: CommonDaoOptions): Promise<BM[]> {
293
+ async runUnionQueries(queries: DBQuery<DBM>[], opt?: CommonDaoReadOptions): Promise<BM[]> {
293
294
  const results = (
294
295
  await pMap(queries, async q => (await this.runQueryExtended(q, opt)).rows)
295
296
  ).flat()
296
297
  return _uniqBy(results, r => r.id)
297
298
  }
298
299
 
299
- async runQueryExtended(q: DBQuery<DBM>, opt: CommonDaoOptions = {}): Promise<RunQueryResult<BM>> {
300
+ async runQueryExtended(
301
+ q: DBQuery<DBM>,
302
+ opt: CommonDaoReadOptions = {},
303
+ ): Promise<RunQueryResult<BM>> {
300
304
  this.validateQueryIndexes(q) // throws if query uses `excludeFromIndexes` property
301
305
  q.table = opt.table || q.table
302
306
  const op = `runQuery(${q.pretty()})`
@@ -317,14 +321,14 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
317
321
  }
318
322
  }
319
323
 
320
- async runQueryAsDBM(q: DBQuery<DBM>, opt?: CommonDaoOptions): Promise<DBM[]> {
324
+ async runQueryAsDBM(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<DBM[]> {
321
325
  const { rows } = await this.runQueryExtendedAsDBM(q, opt)
322
326
  return rows
323
327
  }
324
328
 
325
329
  async runQueryExtendedAsDBM(
326
330
  q: DBQuery<DBM>,
327
- opt: CommonDaoOptions = {},
331
+ opt: CommonDaoReadOptions = {},
328
332
  ): Promise<RunQueryResult<DBM>> {
329
333
  this.validateQueryIndexes(q) // throws if query uses `excludeFromIndexes` property
330
334
  q.table = opt.table || q.table
@@ -343,7 +347,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
343
347
  return { rows: dbms, ...queryResult }
344
348
  }
345
349
 
346
- async runQueryCount(q: DBQuery<DBM>, opt: CommonDaoOptions = {}): Promise<number> {
350
+ async runQueryCount(q: DBQuery<DBM>, opt: CommonDaoReadOptions = {}): Promise<number> {
347
351
  this.validateQueryIndexes(q) // throws if query uses `excludeFromIndexes` property
348
352
  q.table = opt.table || q.table
349
353
  const op = `runQueryCount(${q.pretty()})`
@@ -548,7 +552,7 @@ export class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, I
548
552
  )
549
553
  }
550
554
 
551
- async queryIds(q: DBQuery<DBM>, opt: CommonDaoOptions = {}): Promise<ID[]> {
555
+ async queryIds(q: DBQuery<DBM>, opt: CommonDaoReadOptions = {}): Promise<ID[]> {
552
556
  this.validateQueryIndexes(q) // throws if query uses `excludeFromIndexes` property
553
557
  q.table = opt.table || q.table
554
558
  const { rows } = await this.cfg.db.runQuery(q.select(['id']), opt)
@@ -1411,7 +1415,7 @@ export class CommonDaoTransaction {
1411
1415
  async getById<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(
1412
1416
  dao: CommonDao<BM, DBM, ID>,
1413
1417
  id?: ID | null,
1414
- opt?: CommonDaoOptions,
1418
+ opt?: CommonDaoReadOptions,
1415
1419
  ): Promise<BM | null> {
1416
1420
  return await dao.getById(id, { ...opt, tx: this.tx })
1417
1421
  }
@@ -1419,7 +1423,7 @@ export class CommonDaoTransaction {
1419
1423
  async getByIds<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']>(
1420
1424
  dao: CommonDao<BM, DBM, ID>,
1421
1425
  ids: ID[],
1422
- opt?: CommonDaoOptions,
1426
+ opt?: CommonDaoReadOptions,
1423
1427
  ): Promise<BM[]> {
1424
1428
  return await dao.getByIds(ids, { ...opt, tx: this.tx })
1425
1429
  }
package/src/db.model.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ObjectWithId } from '@naturalcycles/js-lib'
1
+ import { ObjectWithId, UnixTimestampNumber } from '@naturalcycles/js-lib'
2
2
  import { CommonDB } from './common.db'
3
3
 
4
4
  /**
@@ -52,6 +52,14 @@ export interface CommonDBOptions {
52
52
  tx?: DBTransaction
53
53
  }
54
54
 
55
+ export interface CommonDBReadOptions extends CommonDBOptions {
56
+ /**
57
+ * If provided (and supported by the DB) - will read the data at that point in time (aka "Time machine" feature).
58
+ * This feature is named PITR (point-in-time-recovery) query in Datastore.
59
+ */
60
+ readAt?: UnixTimestampNumber
61
+ }
62
+
55
63
  /**
56
64
  * All properties default to undefined.
57
65
  */
@@ -72,7 +80,7 @@ export interface CommonDBSaveOptions<ROW extends ObjectWithId> extends CommonDBO
72
80
  assignGeneratedIds?: boolean
73
81
  }
74
82
 
75
- export type CommonDBStreamOptions = CommonDBOptions
83
+ export type CommonDBStreamOptions = CommonDBReadOptions
76
84
 
77
85
  export interface CommonDBCreateOptions extends CommonDBOptions {
78
86
  /**
@@ -8,6 +8,7 @@ import {
8
8
  import { ReadableTyped } from '@naturalcycles/nodejs-lib'
9
9
  import {
10
10
  CommonDaoOptions,
11
+ CommonDaoReadOptions,
11
12
  CommonDaoStreamDeleteOptions,
12
13
  CommonDaoStreamForEachOptions,
13
14
  CommonDaoStreamOptions,
@@ -251,27 +252,27 @@ export class RunnableDBQuery<
251
252
  super(table || dao.cfg.table)
252
253
  }
253
254
 
254
- async runQuery(opt?: CommonDaoOptions): Promise<BM[]> {
255
+ async runQuery(opt?: CommonDaoReadOptions): Promise<BM[]> {
255
256
  return await this.dao.runQuery(this, opt)
256
257
  }
257
258
 
258
- async runQuerySingleColumn<T = any>(opt?: CommonDaoOptions): Promise<T[]> {
259
+ async runQuerySingleColumn<T = any>(opt?: CommonDaoReadOptions): Promise<T[]> {
259
260
  return await this.dao.runQuerySingleColumn<T>(this, opt)
260
261
  }
261
262
 
262
- async runQueryAsDBM(opt?: CommonDaoOptions): Promise<DBM[]> {
263
+ async runQueryAsDBM(opt?: CommonDaoReadOptions): Promise<DBM[]> {
263
264
  return await this.dao.runQueryAsDBM(this, opt)
264
265
  }
265
266
 
266
- async runQueryExtended(opt?: CommonDaoOptions): Promise<RunQueryResult<BM>> {
267
+ async runQueryExtended(opt?: CommonDaoReadOptions): Promise<RunQueryResult<BM>> {
267
268
  return await this.dao.runQueryExtended(this, opt)
268
269
  }
269
270
 
270
- async runQueryExtendedAsDBM(opt?: CommonDaoOptions): Promise<RunQueryResult<DBM>> {
271
+ async runQueryExtendedAsDBM(opt?: CommonDaoReadOptions): Promise<RunQueryResult<DBM>> {
271
272
  return await this.dao.runQueryExtendedAsDBM(this, opt)
272
273
  }
273
274
 
274
- async runQueryCount(opt?: CommonDaoOptions): Promise<number> {
275
+ async runQueryCount(opt?: CommonDaoReadOptions): Promise<number> {
275
276
  return await this.dao.runQueryCount(this, opt)
276
277
  }
277
278
 
@@ -301,7 +302,7 @@ export class RunnableDBQuery<
301
302
  return this.dao.streamQueryAsDBM(this, opt)
302
303
  }
303
304
 
304
- async queryIds(opt?: CommonDaoOptions): Promise<ID[]> {
305
+ async queryIds(opt?: CommonDaoReadOptions): Promise<ID[]> {
305
306
  return await this.dao.queryIds(this, opt)
306
307
  }
307
308
 
@@ -80,6 +80,17 @@ export function runCommonDaoTest(db: CommonDB, quirks: CommonDBImplementationQui
80
80
  expect(await dao.getByIds(['abc', 'abcd'])).toEqual([])
81
81
  })
82
82
 
83
+ // TimeMachine
84
+ if (support.timeMachine) {
85
+ test('getByIds(...) 10 minutes ago should return []', async () => {
86
+ expect(
87
+ await dao.getByIds([item1.id, 'abc'], {
88
+ readAt: localTime.now().minus(10, 'minute').unix,
89
+ }),
90
+ ).toEqual([])
91
+ })
92
+ }
93
+
83
94
  // SAVE
84
95
  if (support.nullValues) {
85
96
  test('should allow to save and load null values', async () => {
@@ -1,4 +1,4 @@
1
- import { _deepFreeze, _filterObject, _pick, _sortBy, pMap } from '@naturalcycles/js-lib'
1
+ import { _deepFreeze, _filterObject, _pick, _sortBy, localTime, pMap } from '@naturalcycles/js-lib'
2
2
  import { CommonDB, CommonDBType } from '../common.db'
3
3
  import { DBQuery } from '../query/dbQuery'
4
4
  import {
@@ -77,6 +77,17 @@ export function runCommonDBTest(db: CommonDB, quirks: CommonDBImplementationQuir
77
77
  expect(await db.getByIds(TEST_TABLE, ['abc', 'abcd'])).toEqual([])
78
78
  })
79
79
 
80
+ // TimeMachine
81
+ if (support.timeMachine) {
82
+ test('getByIds(...) 10 minutes ago should return []', async () => {
83
+ expect(
84
+ await db.getByIds(TEST_TABLE, [item1.id, 'abc'], {
85
+ readAt: localTime.now().minus(10, 'minute').unix,
86
+ }),
87
+ ).toEqual([])
88
+ })
89
+ }
90
+
80
91
  // SAVE
81
92
  if (support.nullValues) {
82
93
  test('should allow to save and load null values', async () => {
@@ -107,6 +107,7 @@ export class FakeDBTransaction implements DBTransaction {
107
107
  ): Promise<ROW[]> {
108
108
  return await this.db.getByIds(table, ids, opt)
109
109
  }
110
+
110
111
  // async runQuery<ROW extends ObjectWithId>(
111
112
  // q: DBQuery<ROW>,
112
113
  // opt?: CommonDBOptions,
@@ -120,6 +121,7 @@ export class FakeDBTransaction implements DBTransaction {
120
121
  ): Promise<void> {
121
122
  await this.db.saveBatch(table, rows, opt)
122
123
  }
124
+
123
125
  async deleteByIds(table: string, ids: string[], opt?: CommonDBOptions): Promise<number> {
124
126
  return await this.db.deleteByIds(table, ids, opt)
125
127
  }