@naturalcycles/db-lib 8.36.1 → 8.37.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/adapter/cachedb/cache.db.d.ts +1 -1
- package/dist/adapter/cachedb/cache.db.model.d.ts +1 -1
- package/dist/adapter/file/file.db.d.ts +1 -1
- package/dist/adapter/file/file.db.js +1 -0
- package/dist/adapter/inmemory/inMemory.db.d.ts +1 -1
- package/dist/adapter/inmemory/inMemory.db.js +8 -8
- package/dist/base.common.db.d.ts +1 -1
- package/dist/common.db.d.ts +4 -1
- package/dist/commondao/common.dao.d.ts +4 -3
- package/dist/commondao/common.dao.model.d.ts +1 -1
- package/dist/db.model.d.ts +14 -1
- package/dist/index.d.ts +2 -2
- package/dist/testing/dbTest.d.ts +2 -0
- package/dist/testing/dbTest.js +16 -1
- package/package.json +1 -1
- package/src/adapter/cachedb/cache.db.model.ts +1 -1
- package/src/adapter/cachedb/cache.db.ts +1 -1
- package/src/adapter/file/file.db.ts +6 -3
- package/src/adapter/inmemory/inMemory.db.ts +13 -10
- package/src/base.common.db.ts +1 -1
- package/src/common.db.ts +4 -1
- package/src/commondao/common.dao.model.ts +1 -1
- package/src/commondao/common.dao.ts +8 -6
- package/src/db.model.ts +16 -1
- package/src/index.ts +2 -0
- package/src/testing/dbTest.ts +22 -0
|
@@ -24,7 +24,7 @@ export declare class CacheDB extends BaseCommonDB implements CommonDB {
|
|
|
24
24
|
createTable<ROW extends ObjectWithId>(table: string, schema: JsonSchemaObject<ROW>, opt?: CacheDBCreateOptions): Promise<void>;
|
|
25
25
|
getByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], opt?: CacheDBSaveOptions<ROW>): Promise<ROW[]>;
|
|
26
26
|
deleteByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], opt?: CacheDBOptions): Promise<number>;
|
|
27
|
-
saveBatch<ROW extends ObjectWithId
|
|
27
|
+
saveBatch<ROW extends Partial<ObjectWithId>>(table: string, rows: ROW[], opt?: CacheDBSaveOptions<ROW>): Promise<void>;
|
|
28
28
|
runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CacheDBSaveOptions<ROW>): Promise<RunQueryResult<ROW>>;
|
|
29
29
|
runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CacheDBOptions): Promise<number>;
|
|
30
30
|
streamQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CacheDBStreamOptions): Readable;
|
|
@@ -47,7 +47,7 @@ export interface CacheDBOptions {
|
|
|
47
47
|
*/
|
|
48
48
|
onlyCache?: boolean;
|
|
49
49
|
}
|
|
50
|
-
export interface CacheDBSaveOptions<ROW extends ObjectWithId
|
|
50
|
+
export interface CacheDBSaveOptions<ROW extends Partial<ObjectWithId>> extends CacheDBOptions, CommonDBSaveOptions<ROW> {
|
|
51
51
|
}
|
|
52
52
|
export interface CacheDBStreamOptions extends CacheDBOptions, CommonDBStreamOptions {
|
|
53
53
|
}
|
|
@@ -22,7 +22,7 @@ export declare class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
22
22
|
ping(): Promise<void>;
|
|
23
23
|
getTables(): Promise<string[]>;
|
|
24
24
|
getByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], _opt?: CommonDBOptions): Promise<ROW[]>;
|
|
25
|
-
saveBatch<ROW extends ObjectWithId
|
|
25
|
+
saveBatch<ROW extends Partial<ObjectWithId>>(table: string, rows: ROW[], _opt?: CommonDBSaveOptions<ROW>): Promise<void>;
|
|
26
26
|
/**
|
|
27
27
|
* Implementation is optimized for loading/saving _whole files_.
|
|
28
28
|
*/
|
|
@@ -46,6 +46,7 @@ class FileDB extends __1.BaseCommonDB {
|
|
|
46
46
|
// 2. Merge with new data (using ids)
|
|
47
47
|
let saved = 0;
|
|
48
48
|
rows.forEach(r => {
|
|
49
|
+
(0, js_lib_1._assert)(r.id, 'FileDB: row.id is required');
|
|
49
50
|
if (!(0, js_lib_1._deepEquals)(byId[r.id], r)) {
|
|
50
51
|
byId[r.id] = r;
|
|
51
52
|
saved++;
|
|
@@ -53,7 +53,7 @@ export declare class InMemoryDB implements CommonDB {
|
|
|
53
53
|
getTableSchema<ROW extends ObjectWithId>(_table: string): Promise<JsonSchemaRootObject<ROW>>;
|
|
54
54
|
createTable<ROW extends ObjectWithId>(_table: string, _schema: JsonSchemaObject<ROW>, opt?: CommonDBCreateOptions): Promise<void>;
|
|
55
55
|
getByIds<ROW extends ObjectWithId>(_table: string, ids: ROW['id'][], _opt?: CommonDBOptions): Promise<ROW[]>;
|
|
56
|
-
saveBatch<ROW extends ObjectWithId
|
|
56
|
+
saveBatch<ROW extends Partial<ObjectWithId>>(_table: string, rows: ROW[], opt?: CommonDBSaveOptions<ROW>): Promise<void>;
|
|
57
57
|
deleteByIds<ROW extends ObjectWithId>(_table: string, ids: ROW['id'][], _opt?: CommonDBOptions): Promise<number>;
|
|
58
58
|
deleteByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<number>;
|
|
59
59
|
runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<RunQueryResult<ROW>>;
|
|
@@ -73,25 +73,25 @@ class InMemoryDB {
|
|
|
73
73
|
(_a = this.data)[table] || (_a[table] = {});
|
|
74
74
|
return ids.map(id => this.data[table][id]).filter(Boolean);
|
|
75
75
|
}
|
|
76
|
-
async saveBatch(_table, rows,
|
|
76
|
+
async saveBatch(_table, rows, opt = {}) {
|
|
77
77
|
var _a;
|
|
78
78
|
const table = this.cfg.tablesPrefix + _table;
|
|
79
79
|
(_a = this.data)[table] || (_a[table] = {});
|
|
80
80
|
rows.forEach(r => {
|
|
81
81
|
if (!r.id) {
|
|
82
82
|
this.cfg.logger?.warn({ rows });
|
|
83
|
-
throw new Error(`InMemoryDB
|
|
83
|
+
throw new Error(`InMemoryDB doesn't support id auto-generation in saveBatch, row without id was given`);
|
|
84
|
+
}
|
|
85
|
+
if (opt.saveMethod === 'insert' && this.data[table][r.id]) {
|
|
86
|
+
throw new Error(`InMemoryDB: INSERT failed, entity exists: ${table}.${r.id}`);
|
|
87
|
+
}
|
|
88
|
+
if (opt.saveMethod === 'update' && !this.data[table][r.id]) {
|
|
89
|
+
throw new Error(`InMemoryDB: UPDATE failed, entity doesn't exist: ${table}.${r.id}`);
|
|
84
90
|
}
|
|
85
91
|
// JSON parse/stringify (deep clone) is to:
|
|
86
92
|
// 1. Not store values "by reference" (avoid mutation bugs)
|
|
87
93
|
// 2. Simulate real DB that would do something like that in a transport layer anyway
|
|
88
94
|
this.data[table][r.id] = JSON.parse(JSON.stringify(r), nodejs_lib_1.bufferReviver);
|
|
89
|
-
// special treatment for Buffers (assign them raw, without JSON parse/stringify)
|
|
90
|
-
// Object.entries(r).forEach(([k, v]) => {
|
|
91
|
-
// if (Buffer.isBuffer(v)) {
|
|
92
|
-
// this.data[table]![r.id]![k] = v
|
|
93
|
-
// }
|
|
94
|
-
// })
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
async deleteByIds(_table, ids, _opt) {
|
package/dist/base.common.db.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export declare class BaseCommonDB implements CommonDB {
|
|
|
18
18
|
getByIds<ROW extends ObjectWithId>(_table: string, _ids: ROW['id'][]): Promise<ROW[]>;
|
|
19
19
|
runQuery<ROW extends ObjectWithId>(_q: DBQuery<ROW>): Promise<RunQueryResult<ROW>>;
|
|
20
20
|
runQueryCount<ROW extends ObjectWithId>(_q: DBQuery<ROW>): Promise<number>;
|
|
21
|
-
saveBatch<ROW extends ObjectWithId
|
|
21
|
+
saveBatch<ROW extends Partial<ObjectWithId>>(_table: string, _rows: ROW[], _opt?: CommonDBSaveOptions<ROW>): Promise<void>;
|
|
22
22
|
streamQuery<ROW extends ObjectWithId>(_q: DBQuery<ROW>): ReadableTyped<ROW>;
|
|
23
23
|
/**
|
|
24
24
|
* Naive implementation.
|
package/dist/common.db.d.ts
CHANGED
|
@@ -38,7 +38,10 @@ export interface CommonDB {
|
|
|
38
38
|
runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBOptions): Promise<RunQueryResult<ROW>>;
|
|
39
39
|
runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBOptions): Promise<number>;
|
|
40
40
|
streamQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBStreamOptions): ReadableTyped<ROW>;
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* rows can have missing ids only if DB supports auto-generating them (like mysql auto_increment).
|
|
43
|
+
*/
|
|
44
|
+
saveBatch<ROW extends Partial<ObjectWithId>>(table: string, rows: ROW[], opt?: CommonDBSaveOptions<ROW>): Promise<void>;
|
|
42
45
|
/**
|
|
43
46
|
* Returns number of deleted items.
|
|
44
47
|
* Not supported by all implementations (e.g Datastore will always return same number as number of ids).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AsyncMapper, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId, Saved } from '@naturalcycles/js-lib';
|
|
1
|
+
import { AsyncMapper, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId, Saved, Unsaved } from '@naturalcycles/js-lib';
|
|
2
2
|
import { AjvSchema, ObjectSchemaTyped, ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
3
3
|
import { DBModelType, RunQueryResult } from '../db.model';
|
|
4
4
|
import { DBQuery, RunnableDBQuery } from '../query/dbQuery';
|
|
@@ -81,10 +81,11 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
|
|
|
81
81
|
*/
|
|
82
82
|
assignIdCreatedUpdated(obj: DBM, opt?: CommonDaoOptions): DBM;
|
|
83
83
|
assignIdCreatedUpdated(obj: BM, opt?: CommonDaoOptions): Saved<BM>;
|
|
84
|
+
assignIdCreatedUpdated(obj: Unsaved<BM>, opt?: CommonDaoOptions): Saved<BM>;
|
|
84
85
|
/**
|
|
85
86
|
* Mutates with id, created, updated
|
|
86
87
|
*/
|
|
87
|
-
save(bm: BM
|
|
88
|
+
save(bm: Unsaved<BM>, opt?: CommonDaoSaveOptions<DBM>): Promise<Saved<BM>>;
|
|
88
89
|
private ensureImmutableDontExist;
|
|
89
90
|
private ensureUniqueId;
|
|
90
91
|
private throwIfObjectExists;
|
|
@@ -99,7 +100,7 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
|
|
|
99
100
|
patch(id: ID, patch: Partial<BM>, opt?: CommonDaoSaveOptions<DBM>): Promise<Saved<BM>>;
|
|
100
101
|
patchAsDBM(id: ID, patch: Partial<DBM>, opt?: CommonDaoSaveOptions<DBM>): Promise<DBM>;
|
|
101
102
|
saveAsDBM(dbm: DBM, opt?: CommonDaoSaveOptions<DBM>): Promise<DBM>;
|
|
102
|
-
saveBatch(bms: BM[], opt?: CommonDaoSaveOptions<DBM>): Promise<Saved<BM>[]>;
|
|
103
|
+
saveBatch(bms: Unsaved<BM>[], opt?: CommonDaoSaveOptions<DBM>): Promise<Saved<BM>[]>;
|
|
103
104
|
saveBatchAsDBM(dbms: DBM[], opt?: CommonDaoSaveOptions<DBM>): Promise<DBM[]>;
|
|
104
105
|
/**
|
|
105
106
|
* @returns number of deleted items
|
|
@@ -165,7 +165,7 @@ export interface CommonDaoOptions extends CommonDBOptions {
|
|
|
165
165
|
/**
|
|
166
166
|
* All properties default to undefined.
|
|
167
167
|
*/
|
|
168
|
-
export interface CommonDaoSaveOptions<DBM extends ObjectWithId
|
|
168
|
+
export interface CommonDaoSaveOptions<DBM extends Partial<ObjectWithId>> extends CommonDaoOptions, CommonDBSaveOptions<DBM> {
|
|
169
169
|
/**
|
|
170
170
|
* @default false
|
|
171
171
|
*
|
package/dist/db.model.d.ts
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import { AnyObjectWithId, ObjectWithId } from '@naturalcycles/js-lib';
|
|
2
|
+
/**
|
|
3
|
+
* Similar to SQL INSERT, UPDATE.
|
|
4
|
+
* Insert will fail if row already exists.
|
|
5
|
+
* Update will fail if row is missing.
|
|
6
|
+
* Upsert will auto-detect and use Insert or Update to not fail.
|
|
7
|
+
*
|
|
8
|
+
* Default is Upsert.
|
|
9
|
+
*/
|
|
10
|
+
export declare type CommonDBSaveMethod = 'upsert' | 'insert' | 'update';
|
|
2
11
|
export interface CommonDBOptions {
|
|
3
12
|
}
|
|
4
13
|
/**
|
|
5
14
|
* All properties default to undefined.
|
|
6
15
|
*/
|
|
7
|
-
export interface CommonDBSaveOptions<ROW extends ObjectWithId = AnyObjectWithId> extends CommonDBOptions {
|
|
16
|
+
export interface CommonDBSaveOptions<ROW extends Partial<ObjectWithId> = AnyObjectWithId> extends CommonDBOptions {
|
|
8
17
|
excludeFromIndexes?: (keyof ROW)[];
|
|
18
|
+
/**
|
|
19
|
+
* Default is `upsert`
|
|
20
|
+
*/
|
|
21
|
+
saveMethod?: CommonDBSaveMethod;
|
|
9
22
|
}
|
|
10
23
|
export declare type CommonDBStreamOptions = CommonDBOptions;
|
|
11
24
|
export interface CommonDBCreateOptions extends CommonDBOptions {
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { DBLibError } from './cnst';
|
|
|
6
6
|
import { CommonDB } from './common.db';
|
|
7
7
|
import { CommonDao } from './commondao/common.dao';
|
|
8
8
|
import { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoLogLevel, CommonDaoOptions, CommonDaoSaveOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoHooks } from './commondao/common.dao.model';
|
|
9
|
-
import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions, CommonDBStreamOptions, DBDeleteByIdsOperation, DBModelType, DBOperation, DBRelation, DBSaveBatchOperation, RunQueryResult } from './db.model';
|
|
9
|
+
import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveMethod, CommonDBSaveOptions, CommonDBStreamOptions, DBDeleteByIdsOperation, DBModelType, DBOperation, DBRelation, DBSaveBatchOperation, RunQueryResult } from './db.model';
|
|
10
10
|
import { CommonKeyValueDao, CommonKeyValueDaoCfg } from './kv/commonKeyValueDao';
|
|
11
11
|
import { CommonKeyValueDB, KeyValueDBTuple } from './kv/commonKeyValueDB';
|
|
12
12
|
import { createdUpdatedFields, createdUpdatedIdFields, deserializeJsonField, serializeJsonField } from './model.util';
|
|
@@ -17,5 +17,5 @@ import { DBQuery, DBQueryFilter, DBQueryFilterOperator, dbQueryFilterOperatorVal
|
|
|
17
17
|
import { DBTransaction, RunnableDBTransaction } from './transaction/dbTransaction';
|
|
18
18
|
import { commitDBTransactionSimple, mergeDBOperations } from './transaction/dbTransaction.util';
|
|
19
19
|
export * from './kv/commonKeyValueDaoMemoCache';
|
|
20
|
-
export type { DBQueryFilterOperator, DBQueryFilter, DBQueryOrder, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoSaveOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoHooks, CommonDBOptions, CommonDBSaveOptions, CommonDBStreamOptions, CommonDBCreateOptions, CommonDB, RunQueryResult, CommonDaoCfg, InMemoryDBCfg, InMemoryKeyValueDBCfg, DBPipelineBackupOptions, DBPipelineRestoreOptions, DBPipelineCopyOptions, DBOperation, DBSaveBatchOperation, DBDeleteByIdsOperation, CommonKeyValueDB, CommonKeyValueDaoCfg, KeyValueDBTuple, };
|
|
20
|
+
export type { DBQueryFilterOperator, DBQueryFilter, DBQueryOrder, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoSaveOptions, CommonDaoStreamForEachOptions, CommonDaoStreamOptions, CommonDaoHooks, CommonDBOptions, CommonDBSaveOptions, CommonDBSaveMethod, CommonDBStreamOptions, CommonDBCreateOptions, CommonDB, RunQueryResult, CommonDaoCfg, InMemoryDBCfg, InMemoryKeyValueDBCfg, DBPipelineBackupOptions, DBPipelineRestoreOptions, DBPipelineCopyOptions, DBOperation, DBSaveBatchOperation, DBDeleteByIdsOperation, CommonKeyValueDB, CommonKeyValueDaoCfg, KeyValueDBTuple, };
|
|
21
21
|
export { DBQuery, dbQueryFilterOperatorValues, RunnableDBQuery, CommonDaoLogLevel, DBRelation, DBModelType, CommonDao, createdUpdatedFields, createdUpdatedIdFields, InMemoryDB, InMemoryKeyValueDB, queryInMemory, serializeJsonField, deserializeJsonField, dbPipelineBackup, dbPipelineRestore, dbPipelineCopy, DBLibError, BaseCommonDB, DBTransaction, RunnableDBTransaction, mergeDBOperations, commitDBTransactionSimple, CommonKeyValueDao, };
|
package/dist/testing/dbTest.d.ts
CHANGED
package/dist/testing/dbTest.js
CHANGED
|
@@ -12,7 +12,7 @@ const test_util_1 = require("./test.util");
|
|
|
12
12
|
function runCommonDBTest(db, features = {}, quirks = {}) {
|
|
13
13
|
const { querying = true, tableSchemas = true, createTable = true, dbQueryFilter = true,
|
|
14
14
|
// dbQueryFilterIn = true,
|
|
15
|
-
dbQueryOrder = true, dbQuerySelectFields = true, streaming = true, strongConsistency = true, bufferSupport = true, nullValues = true, documentDB = true, } = features;
|
|
15
|
+
dbQueryOrder = true, dbQuerySelectFields = true, insert = true, update = true, streaming = true, strongConsistency = true, bufferSupport = true, nullValues = true, documentDB = true, } = features;
|
|
16
16
|
// const {
|
|
17
17
|
// allowExtraPropertiesInResponse,
|
|
18
18
|
// allowBooleansAsUndefined,
|
|
@@ -87,9 +87,24 @@ function runCommonDBTest(db, features = {}, quirks = {}) {
|
|
|
87
87
|
expect(Object.keys(item3Loaded)).not.toContain('k2');
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
|
+
if (update) {
|
|
91
|
+
test('saveBatch UPDATE method should throw', async () => {
|
|
92
|
+
await expect(db.saveBatch(test_model_1.TEST_TABLE, items, { saveMethod: 'update' })).rejects.toThrow();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
90
95
|
test('saveBatch test items', async () => {
|
|
91
96
|
await db.saveBatch(test_model_1.TEST_TABLE, items);
|
|
92
97
|
});
|
|
98
|
+
if (insert) {
|
|
99
|
+
test('saveBatch INSERT method should throw', async () => {
|
|
100
|
+
await expect(db.saveBatch(test_model_1.TEST_TABLE, items, { saveMethod: 'insert' })).rejects.toThrow();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (update) {
|
|
104
|
+
test('saveBatch UPDATE method should pass', async () => {
|
|
105
|
+
await db.saveBatch(test_model_1.TEST_TABLE, items, { saveMethod: 'update' });
|
|
106
|
+
});
|
|
107
|
+
}
|
|
93
108
|
// GET not empty
|
|
94
109
|
test('getByIds all items', async () => {
|
|
95
110
|
const rows = await db.getByIds(test_model_1.TEST_TABLE, items.map(i => i.id).concat('abcd'));
|
package/package.json
CHANGED
|
@@ -57,7 +57,7 @@ export interface CacheDBOptions {
|
|
|
57
57
|
onlyCache?: boolean
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
export interface CacheDBSaveOptions<ROW extends ObjectWithId
|
|
60
|
+
export interface CacheDBSaveOptions<ROW extends Partial<ObjectWithId>>
|
|
61
61
|
extends CacheDBOptions,
|
|
62
62
|
CommonDBSaveOptions<ROW> {}
|
|
63
63
|
|
|
@@ -146,7 +146,7 @@ export class CacheDB extends BaseCommonDB implements CommonDB {
|
|
|
146
146
|
return deletedIds
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
override async saveBatch<ROW extends ObjectWithId
|
|
149
|
+
override async saveBatch<ROW extends Partial<ObjectWithId>>(
|
|
150
150
|
table: string,
|
|
151
151
|
rows: ROW[],
|
|
152
152
|
opt: CacheDBSaveOptions<ROW> = {},
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
_filterUndefinedValues,
|
|
14
14
|
ObjectWithId,
|
|
15
15
|
AnyObjectWithId,
|
|
16
|
+
_assert,
|
|
16
17
|
} from '@naturalcycles/js-lib'
|
|
17
18
|
import { readableCreate, ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
18
19
|
import { dimGrey } from '@naturalcycles/nodejs-lib/dist/colors'
|
|
@@ -71,7 +72,7 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
71
72
|
return ids.map(id => byId[id]!).filter(Boolean)
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
override async saveBatch<ROW extends ObjectWithId
|
|
75
|
+
override async saveBatch<ROW extends Partial<ObjectWithId>>(
|
|
75
76
|
table: string,
|
|
76
77
|
rows: ROW[],
|
|
77
78
|
_opt?: CommonDBSaveOptions<ROW>,
|
|
@@ -79,13 +80,15 @@ export class FileDB extends BaseCommonDB implements CommonDB {
|
|
|
79
80
|
if (!rows.length) return // save some api calls
|
|
80
81
|
|
|
81
82
|
// 1. Load the whole file
|
|
82
|
-
const byId = _by(await this.loadFile<ROW>(table), r => r.id)
|
|
83
|
+
const byId = _by(await this.loadFile<ROW & ObjectWithId>(table), r => r.id)
|
|
83
84
|
|
|
84
85
|
// 2. Merge with new data (using ids)
|
|
85
86
|
let saved = 0
|
|
86
87
|
rows.forEach(r => {
|
|
88
|
+
_assert(r.id, 'FileDB: row.id is required')
|
|
89
|
+
|
|
87
90
|
if (!_deepEquals(byId[r.id], r)) {
|
|
88
|
-
byId[r.id] = r
|
|
91
|
+
byId[r.id] = r as any
|
|
89
92
|
saved++
|
|
90
93
|
}
|
|
91
94
|
})
|
|
@@ -151,10 +151,10 @@ export class InMemoryDB implements CommonDB {
|
|
|
151
151
|
return ids.map(id => this.data[table]![id]).filter(Boolean) as ROW[]
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
async saveBatch<ROW extends ObjectWithId
|
|
154
|
+
async saveBatch<ROW extends Partial<ObjectWithId>>(
|
|
155
155
|
_table: string,
|
|
156
156
|
rows: ROW[],
|
|
157
|
-
|
|
157
|
+
opt: CommonDBSaveOptions<ROW> = {},
|
|
158
158
|
): Promise<void> {
|
|
159
159
|
const table = this.cfg.tablesPrefix + _table
|
|
160
160
|
this.data[table] ||= {}
|
|
@@ -162,20 +162,23 @@ export class InMemoryDB implements CommonDB {
|
|
|
162
162
|
rows.forEach(r => {
|
|
163
163
|
if (!r.id) {
|
|
164
164
|
this.cfg.logger?.warn({ rows })
|
|
165
|
-
throw new Error(
|
|
165
|
+
throw new Error(
|
|
166
|
+
`InMemoryDB doesn't support id auto-generation in saveBatch, row without id was given`,
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (opt.saveMethod === 'insert' && this.data[table]![r.id]) {
|
|
171
|
+
throw new Error(`InMemoryDB: INSERT failed, entity exists: ${table}.${r.id}`)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (opt.saveMethod === 'update' && !this.data[table]![r.id]) {
|
|
175
|
+
throw new Error(`InMemoryDB: UPDATE failed, entity doesn't exist: ${table}.${r.id}`)
|
|
166
176
|
}
|
|
167
177
|
|
|
168
178
|
// JSON parse/stringify (deep clone) is to:
|
|
169
179
|
// 1. Not store values "by reference" (avoid mutation bugs)
|
|
170
180
|
// 2. Simulate real DB that would do something like that in a transport layer anyway
|
|
171
181
|
this.data[table]![r.id] = JSON.parse(JSON.stringify(r), bufferReviver)
|
|
172
|
-
|
|
173
|
-
// special treatment for Buffers (assign them raw, without JSON parse/stringify)
|
|
174
|
-
// Object.entries(r).forEach(([k, v]) => {
|
|
175
|
-
// if (Buffer.isBuffer(v)) {
|
|
176
|
-
// this.data[table]![r.id]![k] = v
|
|
177
|
-
// }
|
|
178
|
-
// })
|
|
179
182
|
})
|
|
180
183
|
}
|
|
181
184
|
|
package/src/base.common.db.ts
CHANGED
package/src/common.db.ts
CHANGED
|
@@ -70,7 +70,10 @@ export interface CommonDB {
|
|
|
70
70
|
): ReadableTyped<ROW>
|
|
71
71
|
|
|
72
72
|
// SAVE
|
|
73
|
-
|
|
73
|
+
/**
|
|
74
|
+
* rows can have missing ids only if DB supports auto-generating them (like mysql auto_increment).
|
|
75
|
+
*/
|
|
76
|
+
saveBatch<ROW extends Partial<ObjectWithId>>(
|
|
74
77
|
table: string,
|
|
75
78
|
rows: ROW[],
|
|
76
79
|
opt?: CommonDBSaveOptions<ROW>,
|
|
@@ -209,7 +209,7 @@ export interface CommonDaoOptions extends CommonDBOptions {
|
|
|
209
209
|
/**
|
|
210
210
|
* All properties default to undefined.
|
|
211
211
|
*/
|
|
212
|
-
export interface CommonDaoSaveOptions<DBM extends ObjectWithId
|
|
212
|
+
export interface CommonDaoSaveOptions<DBM extends Partial<ObjectWithId>>
|
|
213
213
|
extends CommonDaoOptions,
|
|
214
214
|
CommonDBSaveOptions<DBM> {
|
|
215
215
|
/**
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
pMap,
|
|
16
16
|
pTimeout,
|
|
17
17
|
Saved,
|
|
18
|
+
Unsaved,
|
|
18
19
|
} from '@naturalcycles/js-lib'
|
|
19
20
|
import {
|
|
20
21
|
_pipeline,
|
|
@@ -573,10 +574,11 @@ export class CommonDao<
|
|
|
573
574
|
*/
|
|
574
575
|
assignIdCreatedUpdated(obj: DBM, opt?: CommonDaoOptions): DBM
|
|
575
576
|
assignIdCreatedUpdated(obj: BM, opt?: CommonDaoOptions): Saved<BM>
|
|
576
|
-
assignIdCreatedUpdated(obj:
|
|
577
|
+
assignIdCreatedUpdated(obj: Unsaved<BM>, opt?: CommonDaoOptions): Saved<BM>
|
|
578
|
+
assignIdCreatedUpdated(obj: DBM | BM | Unsaved<BM>, opt: CommonDaoOptions = {}): DBM | Saved<BM> {
|
|
577
579
|
const now = Math.floor(Date.now() / 1000)
|
|
578
580
|
|
|
579
|
-
obj.id ||= this.cfg.hooks!.createId?.(obj)
|
|
581
|
+
obj.id ||= this.cfg.hooks!.createId?.(obj as BM)
|
|
580
582
|
|
|
581
583
|
if (this.cfg.created) {
|
|
582
584
|
obj['created'] ||= obj['updated'] || now
|
|
@@ -593,11 +595,11 @@ export class CommonDao<
|
|
|
593
595
|
/**
|
|
594
596
|
* Mutates with id, created, updated
|
|
595
597
|
*/
|
|
596
|
-
async save(bm: BM
|
|
598
|
+
async save(bm: Unsaved<BM>, opt: CommonDaoSaveOptions<DBM> = {}): Promise<Saved<BM>> {
|
|
597
599
|
this.requireWriteAccess()
|
|
598
600
|
const idWasGenerated = !bm.id
|
|
599
601
|
this.assignIdCreatedUpdated(bm, opt) // mutates
|
|
600
|
-
const dbm = await this.bmToDBM(bm, opt)
|
|
602
|
+
const dbm = await this.bmToDBM(bm as BM, opt)
|
|
601
603
|
const table = opt.table || this.cfg.table
|
|
602
604
|
if (opt.ensureUniqueId && idWasGenerated) await this.ensureUniqueId(table, dbm)
|
|
603
605
|
if (this.cfg.immutable) await this.ensureImmutableDontExist(table, [dbm.id])
|
|
@@ -702,11 +704,11 @@ export class CommonDao<
|
|
|
702
704
|
return dbm
|
|
703
705
|
}
|
|
704
706
|
|
|
705
|
-
async saveBatch(bms: BM[], opt: CommonDaoSaveOptions<DBM> = {}): Promise<Saved<BM>[]> {
|
|
707
|
+
async saveBatch(bms: Unsaved<BM>[], opt: CommonDaoSaveOptions<DBM> = {}): Promise<Saved<BM>[]> {
|
|
706
708
|
this.requireWriteAccess()
|
|
707
709
|
const table = opt.table || this.cfg.table
|
|
708
710
|
bms.forEach(bm => this.assignIdCreatedUpdated(bm, opt))
|
|
709
|
-
const dbms = await this.bmsToDBM(bms, opt)
|
|
711
|
+
const dbms = await this.bmsToDBM(bms as BM[], opt)
|
|
710
712
|
if (opt.ensureUniqueId) throw new AppError('ensureUniqueId is not supported in saveBatch')
|
|
711
713
|
if (this.cfg.immutable)
|
|
712
714
|
await this.ensureImmutableDontExist(
|
package/src/db.model.ts
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
import { AnyObjectWithId, ObjectWithId } from '@naturalcycles/js-lib'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Similar to SQL INSERT, UPDATE.
|
|
5
|
+
* Insert will fail if row already exists.
|
|
6
|
+
* Update will fail if row is missing.
|
|
7
|
+
* Upsert will auto-detect and use Insert or Update to not fail.
|
|
8
|
+
*
|
|
9
|
+
* Default is Upsert.
|
|
10
|
+
*/
|
|
11
|
+
export type CommonDBSaveMethod = 'upsert' | 'insert' | 'update'
|
|
12
|
+
|
|
3
13
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
4
14
|
export interface CommonDBOptions {}
|
|
5
15
|
|
|
6
16
|
/**
|
|
7
17
|
* All properties default to undefined.
|
|
8
18
|
*/
|
|
9
|
-
export interface CommonDBSaveOptions<ROW extends ObjectWithId = AnyObjectWithId>
|
|
19
|
+
export interface CommonDBSaveOptions<ROW extends Partial<ObjectWithId> = AnyObjectWithId>
|
|
10
20
|
extends CommonDBOptions {
|
|
11
21
|
excludeFromIndexes?: (keyof ROW)[]
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default is `upsert`
|
|
25
|
+
*/
|
|
26
|
+
saveMethod?: CommonDBSaveMethod
|
|
12
27
|
}
|
|
13
28
|
|
|
14
29
|
export type CommonDBStreamOptions = CommonDBOptions
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import {
|
|
19
19
|
CommonDBCreateOptions,
|
|
20
20
|
CommonDBOptions,
|
|
21
|
+
CommonDBSaveMethod,
|
|
21
22
|
CommonDBSaveOptions,
|
|
22
23
|
CommonDBStreamOptions,
|
|
23
24
|
DBDeleteByIdsOperation,
|
|
@@ -62,6 +63,7 @@ export type {
|
|
|
62
63
|
CommonDaoHooks,
|
|
63
64
|
CommonDBOptions,
|
|
64
65
|
CommonDBSaveOptions,
|
|
66
|
+
CommonDBSaveMethod,
|
|
65
67
|
CommonDBStreamOptions,
|
|
66
68
|
CommonDBCreateOptions,
|
|
67
69
|
CommonDB,
|
package/src/testing/dbTest.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface CommonDBImplementationFeatures {
|
|
|
21
21
|
dbQueryFilterIn?: boolean
|
|
22
22
|
dbQueryOrder?: boolean
|
|
23
23
|
dbQuerySelectFields?: boolean
|
|
24
|
+
insert?: boolean
|
|
25
|
+
update?: boolean
|
|
24
26
|
|
|
25
27
|
createTable?: boolean
|
|
26
28
|
tableSchemas?: boolean
|
|
@@ -80,6 +82,8 @@ export function runCommonDBTest(
|
|
|
80
82
|
// dbQueryFilterIn = true,
|
|
81
83
|
dbQueryOrder = true,
|
|
82
84
|
dbQuerySelectFields = true,
|
|
85
|
+
insert = true,
|
|
86
|
+
update = true,
|
|
83
87
|
streaming = true,
|
|
84
88
|
strongConsistency = true,
|
|
85
89
|
bufferSupport = true,
|
|
@@ -176,10 +180,28 @@ export function runCommonDBTest(
|
|
|
176
180
|
})
|
|
177
181
|
}
|
|
178
182
|
|
|
183
|
+
if (update) {
|
|
184
|
+
test('saveBatch UPDATE method should throw', async () => {
|
|
185
|
+
await expect(db.saveBatch(TEST_TABLE, items, { saveMethod: 'update' })).rejects.toThrow()
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
179
189
|
test('saveBatch test items', async () => {
|
|
180
190
|
await db.saveBatch(TEST_TABLE, items)
|
|
181
191
|
})
|
|
182
192
|
|
|
193
|
+
if (insert) {
|
|
194
|
+
test('saveBatch INSERT method should throw', async () => {
|
|
195
|
+
await expect(db.saveBatch(TEST_TABLE, items, { saveMethod: 'insert' })).rejects.toThrow()
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (update) {
|
|
200
|
+
test('saveBatch UPDATE method should pass', async () => {
|
|
201
|
+
await db.saveBatch(TEST_TABLE, items, { saveMethod: 'update' })
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
183
205
|
// GET not empty
|
|
184
206
|
test('getByIds all items', async () => {
|
|
185
207
|
const rows = await db.getByIds<TestItemDBM>(TEST_TABLE, items.map(i => i.id).concat('abcd'))
|