UNPKG

@naturalcycles/db-lib

Version:

Lowest Common Denominator API to supported Databases

302 lines (277 loc) 8.41 kB
import type { NonNegativeInteger, ObjectWithId, StringMap } from '@naturalcycles/js-lib/types' import type { JsonSchema } from '@naturalcycles/nodejs-lib/ajv' import type { Pipeline } from '@naturalcycles/nodejs-lib/stream' import type { CommonDBCreateOptions, CommonDBOptions, CommonDBReadOptions, CommonDBSaveOptions, CommonDBStreamOptions, CommonDBTransactionOptions, DBTransaction, DBTransactionFn, RunQueryResult, } from '../db.model.js' import type { DBQuery } from '../query/dbQuery.js' export interface CommonDB { /** * Relational databases are expected to return `null` for all missing properties. */ dbType: CommonDBType /** * Manifest of supported features. */ support: CommonDBSupport /** * Checks that connection/credentials/etc is okay. * Also acts as a "warmup request" for a DB. * It SHOULD fail if DB setup is wrong (e.g on wrong credentials). * It SHOULD succeed if e.g getByIds(['nonExistingKey']) doesn't throw. */ ping: () => Promise<void> /** * Return all tables (table names) available in this DB. */ getTables: () => Promise<string[]> /** * $id of the schema SHOULD be like this: * `${tableName}.schema.json` * * This is important for the code to rely on it, and it's verified by dbTest */ getTableSchema: <ROW extends ObjectWithId>(table: string) => Promise<JsonSchema<ROW>> /** * Will do like `create table ...` for mysql. * Caution! dropIfExists defaults to false. If set to true - will actually DROP the table! */ createTable: <ROW extends ObjectWithId>( table: string, schema: JsonSchema<ROW>, opt?: CommonDBCreateOptions, ) => Promise<void> // GET /** * Order of items returned is not guaranteed to match order of ids. * (Such limitation exists because Datastore doesn't support it). */ getByIds: <ROW extends ObjectWithId>( table: string, ids: string[], opt?: CommonDBReadOptions, ) => Promise<ROW[]> /** * Get rows from multiple tables at once. * Mimics the API of some NoSQL databases like Firestore. * * Takes `map`, which is a map from "table name" to an array of ids. * Example: * { * 'TableOne': ['id1', 'id2'], * 'TableTwo': ['id3'], * } * * Returns a map with the same keys (table names) and arrays of rows as values. * Even if some table is not found, it will return an empty array of results for that table. * * @experimental */ multiGet: <ROW extends ObjectWithId>( idsByTable: StringMap<string[]>, opt?: CommonDBReadOptions, ) => Promise<StringMap<ROW[]>> // QUERY /** * Order by 'id' is not supported by all implementations (for example, Datastore doesn't support it). */ runQuery: <ROW extends ObjectWithId>( q: DBQuery<ROW>, opt?: CommonDBReadOptions, ) => Promise<RunQueryResult<ROW>> runQueryCount: <ROW extends ObjectWithId>( q: DBQuery<ROW>, opt?: CommonDBReadOptions, ) => Promise<NonNegativeInteger> streamQuery: <ROW extends ObjectWithId>( q: DBQuery<ROW>, opt?: CommonDBStreamOptions, ) => Pipeline<ROW> // SAVE /** * rows can have missing ids only if DB supports auto-generating them (like mysql auto_increment). */ saveBatch: <ROW extends ObjectWithId>( table: string, rows: ROW[], opt?: CommonDBSaveOptions<ROW>, ) => Promise<void> /** * Save rows for multiple tables at once. * Mimics the API of some NoSQL databases like Firestore. * * Takes `map`, which is a map from "table name" to an array of rows. * Example: * { * 'TableOne': [{ id: 'id1', ... }, { id: 'id2', ... }], * 'TableTwo': [{ id: 'id3', ... }], * } * * @experimental */ multiSave: <ROW extends ObjectWithId>( rowsByTable: StringMap<ROW[]>, opt?: CommonDBSaveOptions<ROW>, ) => Promise<void> /** * Perform a partial update of a row by its id. * Unlike save - doesn't require to first load the doc. * Mimics the API of some NoSQL databases like Firestore. * * The object with given id has to exist, otherwise an error will be thrown. * * @experimental */ patchById: <ROW extends ObjectWithId>( table: string, id: string, patch: Partial<ROW>, opt?: CommonDBOptions, ) => Promise<void> // DELETE /** * Returns number of deleted items. * Not supported by all implementations (e.g Datastore will always return same number as number of ids). */ deleteByIds: (table: string, ids: string[], opt?: CommonDBOptions) => Promise<NonNegativeInteger> /** * Deletes rows from multiple tables at once. * Mimics the API of some NoSQL databases like Firestore. * Takes `map`, which is a map from "table name" to an array of ids to delete. * Example: * { * 'TableOne': ['id1', 'id2'], * 'TableTwo': ['id3'], * } * * Returns number of deleted items. * Not supported by all implementations (e.g Datastore will always return same number as number of ids). * * @experimental */ multiDelete: ( idsByTable: StringMap<string[]>, opt?: CommonDBOptions, ) => Promise<NonNegativeInteger> /** * Returns number of deleted items. * Not supported by all implementations (e.g Datastore will always return same number as number of ids). */ deleteByQuery: <ROW extends ObjectWithId>( q: DBQuery<ROW>, opt?: CommonDBOptions, ) => Promise<NonNegativeInteger> /** * Applies patch to all the rows that are matched by the query. * * Example: * * UPDATE table SET A = B where $QUERY_CONDITION * * patch would be { A: 'B' } for that query. * * Returns the number of rows affected. */ patchByQuery: <ROW extends ObjectWithId>( q: DBQuery<ROW>, patch: Partial<ROW>, opt?: CommonDBReadOptions, ) => Promise<number> // TRANSACTION /** * Should be implemented as a Transaction (best effort), which means that * either ALL or NONE of the operations should be applied. * * Transaction is automatically committed if fn resolves normally. * Transaction is rolled back if fn throws, the error is re-thrown in that case. * Graceful rollback is allowed on tx.rollback() * * By default, transaction is read-write, * unless specified as readOnly in CommonDBTransactionOptions. */ runInTransaction: (fn: DBTransactionFn, opt?: CommonDBTransactionOptions) => Promise<void> /** * Experimental API to support more manual transaction control. * * @experimental */ createTransaction: (opt?: CommonDBTransactionOptions) => Promise<DBTransaction> /** * Increments a value of a property by a given amount. * This is a batch operation, so it allows to increment multiple rows at once. * * - table - the table to apply operations on * - prop - name of the property to increment (in each of the rows passed) * - incrementMap - map from id to increment value * * Example of incrementMap: * { rowId1: 2, rowId2: 3 } * * Returns the incrementMap with the same keys and updated values. * * @experimental */ incrementBatch: ( table: string, prop: string, incrementMap: StringMap<number>, opt?: CommonDBOptions, ) => Promise<StringMap<number>> } export enum CommonDBType { 'document' = 'document', 'relational' = 'relational', } /** * Manifest of supported features. */ export interface CommonDBSupport { queries?: boolean dbQueryFilter?: boolean dbQueryFilterIn?: boolean dbQueryOrder?: boolean dbQuerySelectFields?: boolean insertSaveMethod?: boolean updateSaveMethod?: boolean patchByQuery?: boolean patchById?: boolean increment?: boolean createTable?: boolean tableSchemas?: boolean streaming?: boolean bufferValues?: boolean nullValues?: boolean transactions?: boolean createTransaction?: boolean timeMachine?: boolean multiTableOperations?: boolean } export const commonDBFullSupport: Required<CommonDBSupport> = { queries: true, dbQueryFilter: true, dbQueryFilterIn: true, dbQueryOrder: true, dbQuerySelectFields: true, insertSaveMethod: true, updateSaveMethod: true, patchByQuery: true, patchById: true, increment: true, createTable: true, tableSchemas: true, streaming: true, bufferValues: true, nullValues: true, transactions: true, createTransaction: true, timeMachine: true, multiTableOperations: true, }