@egi/smart-db
Version:
Unified Smart DB Access
433 lines (432 loc) • 21.1 kB
TypeScript
import { Observable } from "rxjs";
import { AbstractModel } from "./models/abstract-model";
import { SmartDbVersionViewModel } from "./models/smart-db-version-view-model";
import { SmartDbDictionary } from "./models/smart-db-dictionary";
import { SmartDb } from "./smart-db";
import { SmartLog, SmartLogOptions } from "@egi/smart-log";
import { SmartDbSqlBuildData } from "./smart-db-sql-build-data";
import { SmartError } from "./smart-error";
import { SmartDbDateTimeMode, SmartToolOptions } from "./smart-tools";
/**
* Lifecycle states of a {@link SmartDb} database connection.
* - `INIT` — object created, connection not yet attempted.
* - `PREPARING` — driver is establishing the connection.
* - `CONNECTED` — connection established; schema upgrades may still be running.
* - `READY` — fully initialized and ready for queries.
* - `CLOSED` — connection has been closed.
* - `ERROR` — initialization or connection failed; check {@link SmartDb.lastError}.
*/
export declare enum SmartDbReadyState {
INIT = 0,
PREPARING = 1,
CONNECTED = 2,
READY = 3,
CLOSED = 4,
ERROR = 5
}
export interface ModelClass<T> {
attributeMap: ModelAttributeMap;
new (): T;
getClassName(): string;
getTableName(): string;
getPkSequenceName(): string;
getPrimaryKey(): string;
from(other: any): any;
getPrimaryKeyName(): string;
}
export interface DbDictionaryEntry {
cls: ModelClass<any>;
}
/** Configuration options passed to the {@link SmartDb} constructor. */
export interface SmartDbOptions<T extends SmartDb<T>> extends SmartToolOptions {
/** When `true`, skip schema upgrades and only establish the connection (sets state to READY after CONNECTED). */
connectOnly?: boolean;
/** When `true`, do not call {@link SmartDb.initDb} from the constructor. Call it manually later. */
delayInit?: boolean;
/** When `true`, persists log entries to the `smart_db_log` database table once it exists. */
dbLogging?: boolean;
/** Logging configuration (log level, format, etc.). */
logOptions?: SmartLogOptions;
/**
* One or more module names to initialize via the upgrade manager.
* Each module must have a corresponding `${module}-init.sql` file in the SQL assets directory.
*/
module?: string | string[];
/** When `true`, LIKE patterns with `%` or `_` emit an explicit `ESCAPE '\\'` clause (needed for Oracle). */
needsExplicitEscape?: boolean;
/**
* Callback invoked once the database reaches READY state (or ERROR).
* Receives the database instance and an error object if initialization failed.
*/
onReady?: (db: T, error?: any) => void;
/** When `true`, suppress all log output (sets log level to Fatal). */
silent?: boolean;
/** When `true`, skip automatic schema upgrades during {@link SmartDb.initDb}. */
skipAutoUpgrade?: boolean;
/** One or more dictionary classes providing default data for lookup tables. */
smartDbDictionary?: typeof SmartDbDictionary | (typeof SmartDbDictionary)[];
/** Directory path where SQL upgrade script files are located. Defaults to the built-in assets directory. */
sqlFilesDirectory?: string;
}
/** Result returned by INSERT/UPDATE/DELETE statement execution. */
export interface SmartDbRunResult {
/** Number of rows affected by the statement (alias for `changes` on some drivers). */
affected: number;
/** Number of rows changed by the statement. */
changes: number;
/** Auto-generated primary key of the last inserted row (0 for UPDATE/DELETE). */
lastId: number;
}
/** Metadata about a database table returned by {@link SmartDb.getTableInfo}. */
export interface SmartDbTableInfo {
/** Descriptions of each column in the table. */
fields: SmartDbFieldDescription[];
/** The table name as it appears in the database. */
name: string;
/** For Oracle: the sequence name used to generate PK values, if any. */
pkSequenceName?: string;
}
/** Metadata about a single column in a database table. */
export interface SmartDbFieldDescription {
/** Column index (used by SQLite). */
cid?: string | number;
/** Full database type string including precision/scale, e.g. `"VARCHAR2(100)"`. */
dbFullType: string;
/** Base database type, e.g. `"VARCHAR2"`, `"INTEGER"`, `"DATE"`. */
dbType: string;
/** Default value for the column, if any. */
defaultValue?: number | string | boolean;
/** `true` if this column is (part of) the primary key. */
isPk?: boolean;
/** Column name as it appears in the database. */
name: string;
/** `true` if the column has a NOT NULL constraint. */
notNull?: boolean;
/** `true` if this is a virtual/computed column. */
virtual?: boolean;
}
/** Describes a field-to-field or field-to-value comparison used inside a {@link SqlWhere} `expression` clause. */
export interface SmartDbSqlComparison {
/** The left-hand side of the comparison: a column name or a {@link SqlFieldDescriptor}. */
compare: string | SqlFieldDescriptor;
/** The comparison operator. Defaults to `EQ` (`=`) when omitted. */
operation?: SqlOperationType;
/** The right-hand side: a scalar value or a {@link SqlFieldDescriptor}. */
with: SqlValueType | SqlFieldDescriptor;
}
/** Scalar value types accepted as SQL parameter values. */
export type SqlValueType = string | number | boolean | Date | null;
/** Names of value types accepted as SQL parameter values. */
export type SqlValueTypes = "string" | "number" | "boolean" | "Date" | "any" | "clob" | "SqlValueType";
/**
* Controls how column names are styled in query results.
* - `Database` — original column names as returned by the database (e.g. `CREATED_AT`).
* - `TypeScript` — camelCase aliases defined in the model's attribute map (e.g. `createdAt`).
* - `All` — both styles are included in the result objects.
*/
export declare enum FieldNamingStyle {
Database = 0,
TypeScript = 1,
All = 2
}
export interface GenericModelData {
[key: string]: any;
}
export interface AttributeInfo {
alias?: string;
alternative?: boolean;
attribute: string;
dbFullType?: string;
dbType?: string;
physical?: boolean;
type: SqlValueTypes;
typeScriptStyle?: boolean;
virtual?: boolean;
}
export interface ModelDbOptions {
dateTimeMode?: SmartDbDateTimeMode;
useNativeDate?: boolean;
}
export interface MakeDbValueOptions extends ModelDbOptions {
dbFullType?: string;
}
export interface ModelAttributeMap {
[key: string]: AttributeInfo;
}
export interface AbstractModelGlobals<T extends AbstractModel<T, D>, D extends GenericModelData> {
attributeMap: ModelAttributeMap;
from: (other: T | D) => T;
getTableName: () => string;
}
/**
* Recursive WHERE clause builder.
*
* Each key in the object is interpreted as a column name whose value must match,
* unless it is one of the reserved keys below:
*
* - `and` — sub-clause(s) joined with `AND`.
* - `or` — sub-clause(s) joined with `OR`.
* - `expression` — array of {@link SmartDbSqlComparison} for field-to-field comparisons.
*
* Plain key/value pairs generate equality conditions (or `LIKE` when the value contains `%`/`_`).
* Pass a {@link SqlOperation} object as the value for other operators (GT, IN, IS NULL, etc.).
* Pass an array as the value to generate an `IN (...)` condition automatically.
* Pass `null` as the value to generate an `IS NULL` condition.
*
* @example
* ```ts
* // WHERE status = 'active' AND age >= 18
* const where: SqlWhere = {
* status: 'active',
* age: { operation: SqlOperationType.GE, value: 18 }
* };
* ```
*/
export interface SqlWhere {
and?: SqlWhere | SqlWhere[];
expression?: SmartDbSqlComparison[];
or?: SqlWhere | SqlWhere[];
[key: string]: SqlValueType | SqlValueType[] | SqlOperation | SqlWhere | SqlWhere[] | SmartDbSqlComparison[];
}
/** A plain object of `{columnName: value}` pairs used for INSERT and UPDATE values. */
export type SqlUpdateValues = GenericModelData;
/**
* ORDER BY specification. A single field name, optionally suffixed with `ASC` or `DESC`,
* or an array of such strings for multi-column ordering.
* @example `"name DESC"` or `["lastName ASC", "firstName ASC"]`
*/
export type SqlOrderBy = string | string[];
type SqlGroupBy = string | string[];
/** LIMIT and OFFSET values for paginating query results. */
export interface SqlLimit {
/** Maximum number of rows to return. */
limit?: number;
/** Number of rows to skip before returning results. */
offset?: number;
}
/**
* Describes one section of a UNION / INTERSECT / MINUS set operation.
* Used in the `union`, `intersect`, and `minus` arrays of {@link SmartDbSqlOptions}.
*/
export interface SectionDescription<T extends AbstractModel<T, D>, D extends GenericModelData> {
/** Fields to SELECT in this section. */
fields?: string | SqlFieldDescriptor | (string | SqlFieldDescriptor)[] | null;
/** GROUP BY clause for this section. */
groupBy?: SqlGroupBy;
/** LIMIT/OFFSET for this section. */
limit?: SqlLimit;
/** The model class or table name for this section. */
model: ModelClass<T> | string;
/** WHERE clause for this section. */
where?: SqlWhere;
}
/**
* Full set of options for {@link SmartDb.get} and related query methods.
*
* @typeParam T - The model class (extends {@link AbstractModel}).
* @typeParam D - The plain data interface for the model.
*/
export interface SmartDbSqlOptions<T extends AbstractModel<T, D>, D extends GenericModelData> {
/**
* When `true`, a multi-column result row is collapsed into a single value
* (useful when SELECTing a single aggregate column).
*/
collapseRow?: boolean;
/** When `true`, wraps the field list in `COUNT(...)` and returns a numeric count. */
count?: boolean;
/** When `true`, adds `DISTINCT` to the SELECT clause. */
distinct?: boolean;
/**
* Fields to SELECT. Accepts a comma-separated string, a single field name,
* a {@link SqlFieldDescriptor}, or an array of field names/descriptors.
* Pass `null` or omit to select all columns (`*`).
*/
fields?: string | SqlFieldDescriptor | (string | SqlFieldDescriptor)[] | null;
/** When `true`, returns only the first result row instead of an array. */
firstOnly?: boolean;
/** GROUP BY clause: a field name or array of field names. */
groupBy?: SqlGroupBy;
/** HAVING clause applied after GROUP BY. Uses the same {@link SqlWhere} syntax. */
having?: SqlWhere;
/** SQLite `INDEXED BY` hint to force use of a specific index. */
indexedBy?: string;
/** One or more INTERSECT sections to append to the query. */
intersect?: SectionDescription<T, D> | SectionDescription<T, D>[];
/** LIMIT and OFFSET for pagination. */
limit?: SqlLimit;
/** One or more MINUS (EXCEPT) sections to append to the query. */
minus?: SectionDescription<T, D> | SectionDescription<T, D>[];
/** ORDER BY clause: a field name (optionally with `ASC`/`DESC`) or array of field names. */
orderBy?: SqlOrderBy;
/** Controls whether results use database column names or camelCase TypeScript aliases. */
style?: FieldNamingStyle;
/** One or more UNION sections to append to the query. */
union?: SectionDescription<T, D> | SectionDescription<T, D>[];
/** WHERE clause to filter results. */
where?: SqlWhere;
}
/**
* SQL comparison operators used in {@link SqlOperation} and {@link SmartDbSqlComparison}.
* Use the helper functions from `smart-db-globals` (e.g. `OP()`, `IN()`, `LITERAL()`)
* to construct {@link SqlOperation} objects rather than building them manually.
*/
export declare enum SqlOperationType {
GT = ">",
GE = ">=",
LT = "<",
LE = "<=",
EQ = "=",
NE = "!=",
IN = "IN",
NOT_IN = "NOT IN",
LIKE = "LIKE",
NOT_LIKE = "NOT LIKE",
IS_NULL = "IS NULL",
IS_NOT_NULL = "IS NOT NULL",
LITERAL = "LITERAL",
BETWEEN = "BETWEEN",
VALUE = "VALUE"
}
/**
* Aggregate/field operations used in {@link SqlFieldDescriptor} for SELECT field expressions.
* Use the helper functions `COUNT()`, `SUM()`, `AVG()`, `MIN()`, `MAX()`, `FIELD()`, `VALUE()`
* from `smart-db-globals` to create descriptors.
*/
export declare enum SqlFieldOperationType {
VALUE = "VALUE",
FIELD = "FIELD",
COUNT = "COUNT",
MIN = "MIN",
MAX = "MAX",
AVG = "AVG",
SUM = "SUM",
COUNT_FIELD = "COUNT_FIELD",
COALESCE = "COALESCE"
}
/**
* Describes a SELECT field expression such as an aggregate function or aliased column.
* Construct instances using the helper functions exported from `smart-db-globals`:
* `COUNT()`, `SUM()`, `AVG()`, `MIN()`, `MAX()`, `FIELD()`, `VALUE()`.
*/
export interface SqlFieldDescriptor {
/** Optional SQL alias (`AS alias`) for the expression. */
alias?: string;
/** When `true`, the `value` is emitted verbatim into SQL without quoting or translation. */
literal?: boolean;
/** The aggregate or field operation to apply. */
operation: SqlFieldOperationType;
/** The operand(s) for the operation: a field name, scalar value, or nested descriptor. */
value: SqlFieldDescriptor | SqlValueType | (SqlFieldDescriptor | SqlValueType)[];
}
/**
* A WHERE condition with an explicit operator.
* Used as the value for a column key in a {@link SqlWhere} object.
* Prefer the helper functions `OP()`, `IN()`, `NE()`, `LITERAL()` from `smart-db-globals`.
*
* @example
* ```ts
* // WHERE age BETWEEN 18 AND 65
* const where: SqlWhere = { age: { operation: SqlOperationType.BETWEEN, value: [18, 65] } };
* ```
*/
export interface SqlOperation {
/** For LITERAL operations: the operator to use before the literal value. Defaults to `EQ`. */
literalOperation?: SqlOperationType;
/** The comparison operator. */
operation: SqlOperationType;
/** The value(s) to compare against. Use an array for IN/NOT_IN and BETWEEN. */
value: SqlValueType | SqlValueType[];
}
export interface KeyValueList {
[key: string]: SqlValueType;
}
export interface IndexedGenericModelData<T extends AbstractModel<T, D>, D extends GenericModelData> {
[key: string]: T | GenericModelData;
}
export interface SmartDbSqlBuildDataResults {
sql: string;
values: SqlValueType[];
}
export interface SmartDbConnector {
}
/**
* Public interface of {@link SmartDb}.
* Describes all methods and properties available to consumers of any SmartDB driver.
*/
export interface SmartDbInterface {
/** `true` when SQL statements are being written to the database log table. */
readonly dbLogging: boolean;
/** `true` when the database connection has been established (CONNECTED or later). */
readonly isConnected: boolean;
/** `true` when the database is fully initialized and ready for queries. */
readonly isReady: boolean;
/** The last error that occurred during initialization or query execution. */
lastError: SmartError;
/** Observable that emits every time the ready-state changes. */
readonly onReady: Observable<SmartDbReadyState>;
/** Current lifecycle state of the database connection. */
readonly readyState: SmartDbReadyState;
/** `true` when the driver supports synchronous calls (SQLite only). */
readonly supportSyncCalls: boolean;
/** Active date/time conversion mode for this connection. */
readonly dateTimeMode: SmartDbDateTimeMode;
/** Returns a string identifying the database type, e.g. `"sqlite"`, `"mysql"`, `"oracle"`. */
getDatabaseType(): string;
/** Returns metadata about the columns of a database table. */
getTableInfo(table: string): Promise<SmartDbTableInfo>;
/** Returns the raw underlying driver connection object. */
getDb(): any;
/** Returns the current SmartDb options. */
getOptions(): SmartDbOptions<any>;
/** Resolves when the database reaches `targetState` (defaults to READY). */
databaseReady(targetState?: SmartDbReadyState): Promise<SmartDbReadyState>;
/** Runs schema upgrade scripts and marks the database as READY. */
initDb(): Promise<SmartDbVersionViewModel[]>;
/** Closes the connection asynchronously. */
close(): Promise<boolean>;
/** Closes the connection synchronously. */
closeSync(): boolean;
commit(): Promise<void>;
commitSync(): void;
rollback(): Promise<void>;
rollbackSync(): void;
hasTransaction(): boolean;
hasConcurrentTransactions(): boolean;
get<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, options: SmartDbSqlOptions<T, D>): Promise<(T | D)[] | T | D | SqlValueType | IndexedGenericModelData<T, D> | string | string[] | false>;
/** Returns the first matching model instance. Alias for `get()` with `firstOnly: true`. */
async<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, options: SmartDbSqlOptions<T, D>): Promise<T>;
getFirst<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, where?: SqlWhere, fields?: SqlFieldDescriptor | string | (SqlFieldDescriptor | string)[] | null, orderBy?: SqlOrderBy): Promise<T>;
getAll<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, where?: SqlWhere, fields?: string | string[] | null, orderBy?: SqlOrderBy, limit?: SqlLimit): Promise<T[]>;
getAllWithOptions<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, options: SmartDbSqlOptions<T, D>): Promise<T[]>;
insert<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, values: SqlUpdateValues | T): Promise<number>;
update<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, values: SqlUpdateValues | T, where?: SqlWhere): Promise<number>;
delete<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, where?: SqlWhere): Promise<number>;
exists<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, type?: "view" | "table" | "index", indexTableName?: string): Promise<boolean>;
getSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, options: SmartDbSqlOptions<T, D>): (T | D)[] | T | D | SqlValueType | IndexedGenericModelData<T, D> | string | string[] | false;
getFirstSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, where?: SqlWhere, fields?: SqlFieldDescriptor | string | (SqlFieldDescriptor | string)[] | null, orderBy?: SqlOrderBy): T | false;
getFirstWithOptionsSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, options: SmartDbSqlOptions<T, D>): T | false;
getAllSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, where?: SqlWhere, fields?: string | string[] | null, orderBy?: SqlOrderBy, limit?: SqlLimit): T[] | false;
getAllWithOptionsSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, options: SmartDbSqlOptions<T, D>): T[] | false;
insertSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, values: SqlUpdateValues | T): number | false;
updateSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, values: SqlUpdateValues | T, where?: SqlWhere): number | false;
deleteSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, where?: SqlWhere): number | false;
existsSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, type?: "view" | "table" | "index", indexTableName?: string): boolean;
exec(script: string, params?: SqlValueType[]): Promise<void>;
execSync(script: string, params?: SqlValueType[]): boolean;
execScript(script: string, ignoreDropError?: boolean): Promise<void>;
execScriptSync(script: string): void;
query(script: string, params?: SqlValueType[]): Promise<GenericModelData[]>;
querySync(script: string, params?: SqlValueType[]): GenericModelData[];
buildSelectStatement<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, options: SmartDbSqlOptions<T, D>): SmartDbSqlBuildData;
getDbConnector(): string | SmartDbConnector;
getDbQuote(): string;
getLastBuildData<T extends AbstractModel<T, D>, D extends GenericModelData>(): SmartDbSqlBuildData;
getLastError(): SmartError;
getLogger(): SmartLog;
getModuleVersions(): SmartDbVersionViewModel[];
makeDbValue(value: SqlValueType, options?: MakeDbValueOptions): SqlValueType;
toDate(d: Date | number | string): Date | null;
toDbDate(d: Date | number): string;
toDbTimestamp(d: Date | number): string;
}
export {};