From 8fb2b6f1495892b60d1e76b2f5092ea349cc15b6 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Wed, 22 Sep 2021 23:29:59 +0800 Subject: [PATCH 01/26] feat: add connector TS types Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/connector.d.ts | 88 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/types/connector.d.ts b/types/connector.d.ts index a1116d00b..63f517e7d 100644 --- a/types/connector.d.ts +++ b/types/connector.d.ts @@ -3,23 +3,105 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Callback, DataSource, Options, PromiseOrVoid} from '..'; +import {Callback, DataSource, ModelBaseClass, ModelDefinition, ModelProperties, Options, PromiseOrVoid, PropertyDefinition, PropertyType, Schema} from '..'; // Copyright IBM Corp. 2018. All Rights Reserved. // Node module: loopback-datasource-juggler // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +export type DiscoveryScopeOptions = { + owner?: string, + all?: boolean, + views?: boolean, + limit?: number, + offset?: number, +} + +export type SchemaDiscoveryOptions = { + owner?: string, + relations?: boolean, + all?: boolean, + views?: boolean, +} + +export type PrimaryKeysDiscoveryReturnValue = { + owner?: string | null, + tableName?: string, + columnName?: string, + keySeq?: number, + pkName?: string | null, +} + +export type ForeignKeysDiscoveryReturnValue = { + fkOwner?: string | null, + fkName?: string | null, + fkTableName?: string, + fkColumnName?: string, + keySeq?: number, + pkOwner?: string | null, + pkName?: string | null, + pkTableName?: string, + pkColumnName?: string, +} + +export type ModelPropertiesDiscoveryReturnValue = { + owner?: string, + tableName?: string, + columnName?: string, + dataType?: string, + dataLength?: number, + dataPrecision?: number, + dataScale?: number, + nullable?: boolean, +} + /** * Connector from `loopback-connector` module */ export interface Connector { name: string; // Name/type of the connector dataSource?: DataSource; - connect(callback?: Callback): PromiseOrVoid; // Connect to the underlying system - disconnect(callback?: Callback): PromiseOrVoid; // Disconnect from the underlying system + connect?(callback?: Callback): PromiseOrVoid; // Connect to the underlying system + disconnect?(callback?: Callback): PromiseOrVoid; // Disconnect from the underlying system ping(callback?: Callback): PromiseOrVoid; // Ping the underlying system execute?(...args: any[]): Promise; + getDefaultIdType(): object; + getTypes?(): string[]; + define?(def: {model: ModelBaseClass, properties: PropertyDefinition, settings: ModelDefinition['settings']}): void; + defineProperty?(model: string, prop: string, params: PropertyDefinition): void; + defineForeignKey?(modelName: string, key: string, foreignModelName: string, cb: Callback): void; + defineForeignKey?(modelName: string, key: string, cb: Callback): void; + discoverModelDefinitions?(options: DiscoveryScopeOptions, cb?: Callback): Promise; + /** + * @deprecated + */ + discoverModelDefinitionsSync?(options: DiscoveryScopeOptions): ModelDefinition + discoverModelProperties?(modelName: string, options: DiscoveryScopeOptions, cb: Callback): Promise; + /** + * @deprecated + */ + discoverModelPropertiesSync?(modelName: string, options: DiscoveryScopeOptions): ModelPropertiesDiscoveryReturnValue; + discoverPrimaryKeys?(modelName: string, options: {owner?: string}, cb: Callback): Promise; + /** + * @deprecated + */ + discoverPrimaryKeysSync?(modelName: string, options: {owner?: string}): PrimaryKeysDiscoveryReturnValue; + discoverForeignKeys?(modelName: string[], options: {owner?: string}, cb: Callback): Promise; + /** + * @deprecated + */ + discoverForeignKeysSync?(modelName: string[], options: {owner?: string}): ForeignKeysDiscoveryReturnValue; + discoverExportedForeignKeys?(modelName: string, options: {owner?: string}, cb: Callback): Promise; + /** + * @deprecated + */ + discoverExportedForeignKeysSync?(modelName: string, options?: {owner?: string}): ForeignKeysDiscoveryReturnValue; + discoverSchemas(tableName: string, options: SchemaDiscoveryOptions, cb: Callback): Promise; + + isActual?(models: string[], cb: Callback): void; + freezeDataSource?(): void; + freezeSchema?(): void; [property: string]: any; // Other properties that vary by connectors } From 6281ebe51d83887b8fa9f3f052765b493b84dc87 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Fri, 24 Sep 2021 17:17:33 +0800 Subject: [PATCH 02/26] feat: add types for connector and datasource Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/connector.d.ts | 239 ++++++++++++++++++++++++++++++++++++------ types/datasource.d.ts | 133 +++++++++++++++++++++-- types/model.d.ts | 7 ++ 3 files changed, 339 insertions(+), 40 deletions(-) diff --git a/types/connector.d.ts b/types/connector.d.ts index 63f517e7d..782c857ad 100644 --- a/types/connector.d.ts +++ b/types/connector.d.ts @@ -3,13 +3,36 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Callback, DataSource, ModelBaseClass, ModelDefinition, ModelProperties, Options, PromiseOrVoid, PropertyDefinition, PropertyType, Schema} from '..'; +import { AnyObject } from 'strong-globalize/lib/config'; +import {Callback, DataSource, ModelBase, ModelBaseClass, ModelDefinition, ModelProperties, Options, PromiseOrVoid, PropertyDefinition, PropertyType, Schema, Where} from '..'; // Copyright IBM Corp. 2018. All Rights Reserved. // Node module: loopback-datasource-juggler // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +export type ConnectorSettings = Options & { + name?: string, + /** + * Overrides {@link ConnectorSettings.adapter} if defined. + */ + connector?: Connector, + /** + * @deprecated Use {@link ConnectorSettings.connector} instead. + */ + adapter?: Connector, + connectionTimeout?: number, + maxOfflineRequests?: number, + lazyConnect?: boolean, + debug?: boolean, +} + +export type IDPropertiesDiscoveryOptions = { + owner?: string, +} | { + schema?: string +} + export type DiscoveryScopeOptions = { owner?: string, all?: boolean, @@ -20,32 +43,39 @@ export type DiscoveryScopeOptions = { export type SchemaDiscoveryOptions = { owner?: string, + /** + * Overriden by {@link SchemaDiscoveryOptions.associations}. + */ relations?: boolean, all?: boolean, views?: boolean, + disableCamelCase?: boolean, + nameMapper?: (type: 'table' | 'model' | 'fk' | string, name: string) => string | null; + associations?: boolean; } -export type PrimaryKeysDiscoveryReturnValue = { - owner?: string | null, - tableName?: string, - columnName?: string, - keySeq?: number, - pkName?: string | null, +export type DiscoveredPrimaryKeys = { + owner: string | null, + tableName: string, + columnName: string, + keySeq: number, + pkName: string | null, } -export type ForeignKeysDiscoveryReturnValue = { - fkOwner?: string | null, - fkName?: string | null, - fkTableName?: string, - fkColumnName?: string, - keySeq?: number, - pkOwner?: string | null, - pkName?: string | null, - pkTableName?: string, - pkColumnName?: string, +export type DiscoveredForeignKeys = { + fkOwner: string | null, + fkName: string | null, + fkTableName: string, + fkColumnName: string, + keySeq: number, + + pkOwner: string | null, + pkName: string | null, + pkTableName: string, + pkColumnName: string, } -export type ModelPropertiesDiscoveryReturnValue = { +export type DiscoveredModelProperties = { owner?: string, tableName?: string, columnName?: string, @@ -56,57 +86,192 @@ export type ModelPropertiesDiscoveryReturnValue = { nullable?: boolean, } + +// #TODO(achrinza): The codebase suggets that `context` differs +// depending on the situation, and that there's no cohesive interface. +// Hence, we'll need to identify all the possible contexts. +export type Context = { + Model: ModelBaseClass, + instance?: object, + query?: {where: Where}, + data?: AnyObject, + hookState?: AnyObject, + options?: Options, + isNewInstance?: boolean, + currentInstance?: ModelBase, +} + /** * Connector from `loopback-connector` module */ export interface Connector { name: string; // Name/type of the connector - dataSource?: DataSource; + _models?: object[]; connect?(callback?: Callback): PromiseOrVoid; // Connect to the underlying system disconnect?(callback?: Callback): PromiseOrVoid; // Disconnect from the underlying system - ping(callback?: Callback): PromiseOrVoid; // Ping the underlying system + + /** + * Ping the underlying connector to test the connections. + * + * @remarks + * Unlike {@link DataSource.ping}, if no callback is provided, a + * {@link Promise} return value is not guaranteed. + * + * @param callback Callback function + * @returns a {@link Promise} or `void` + */ + ping?(callback?: Callback): PromiseOrVoid; // Ping the underlying system execute?(...args: any[]): Promise; - getDefaultIdType(): object; + + /** + * Get the connector's types collection. + * + * @remarks + * For example, ['db', 'nosql', 'mongodb'] would be represent a datasource of + * type 'db', with a subtype of 'nosql', and would use the 'mongodb' connector. + * + * Alternatively, ['rest'] would be a different type altogether, and would have + * no subtype. + * + * @returns The connector's type collection. + */ getTypes?(): string[]; define?(def: {model: ModelBaseClass, properties: PropertyDefinition, settings: ModelDefinition['settings']}): void; defineProperty?(model: string, prop: string, params: PropertyDefinition): void; defineForeignKey?(modelName: string, key: string, foreignModelName: string, cb: Callback): void; defineForeignKey?(modelName: string, key: string, cb: Callback): void; - discoverModelDefinitions?(options: DiscoveryScopeOptions, cb?: Callback): Promise; + + getDefaultIdType(): object; + isRelational(): boolean; + /** + * Discover existing database tables. + * + * @param options Discovery options + * @param cb Callback function + */ + discoverModelDefinitions?(options: DiscoveryScopeOptions, cb: Callback): Promise; + + /** + * {@inheritDoc Connector.discoverModelDefinitions} * @deprecated */ - discoverModelDefinitionsSync?(options: DiscoveryScopeOptions): ModelDefinition - discoverModelProperties?(modelName: string, options: DiscoveryScopeOptions, cb: Callback): Promise; + discoverModelDefinitionsSync?(options: DiscoveryScopeOptions): ModelDefinition[] + + /** + * Discover properties for a given model. + * + * @param modelName Target table/view name + * @param options Discovery options + * @param cb Callback function + */ + discoverModelProperties?(modelName: string, options: DiscoveryScopeOptions, cb: Callback): Promise; /** + * {@inheritDoc Connector.discoverModelProperties} * @deprecated */ - discoverModelPropertiesSync?(modelName: string, options: DiscoveryScopeOptions): ModelPropertiesDiscoveryReturnValue; - discoverPrimaryKeys?(modelName: string, options: {owner?: string}, cb: Callback): Promise; + discoverModelPropertiesSync?(modelName: string, options: DiscoveryScopeOptions): DiscoveredModelProperties; + + /** + * Discover primary keys for a given owner/model name. + * + * @param modelName Target model name + * @param options Discovery options + * @param cb Callback function + */ + discoverPrimaryKeys?(modelName: string, options: IDPropertiesDiscoveryOptions, cb: Callback): Promise; + /** + * {@inheritDoc Connector.discoverPrimaryKeys} * @deprecated */ - discoverPrimaryKeysSync?(modelName: string, options: {owner?: string}): PrimaryKeysDiscoveryReturnValue; - discoverForeignKeys?(modelName: string[], options: {owner?: string}, cb: Callback): Promise; + discoverPrimaryKeysSync?(modelName: string, options: IDPropertiesDiscoveryOptions): DiscoveredPrimaryKeys; + + /** + * Discover foreign keys for a given owner/model name. + * + * @param modelName Target model name + * @param options Discovery options + * @param cb Callback function + */ + discoverForeignKeys?(modelName: string[], options: IDPropertiesDiscoveryOptions, cb: Callback): Promise; /** + * {@inheritDoc Connector.discoverForeignKeys} * @deprecated */ - discoverForeignKeysSync?(modelName: string[], options: {owner?: string}): ForeignKeysDiscoveryReturnValue; - discoverExportedForeignKeys?(modelName: string, options: {owner?: string}, cb: Callback): Promise; + discoverForeignKeysSync?(modelName: string[], options: IDPropertiesDiscoveryOptions): DiscoveredForeignKeys; + + /** + * Retrieve a description of the foreign key columns that reference the given + * table's primary key columns (i.e. The foreign keys exported by a table), + * ordered by `fkOwner`, `fkTableName`, and `keySeq`. + * + * @param modelName Target model name + * @param options Discovery options + * @param cb Callback function + */ + discoverExportedForeignKeys?(modelName: string, options: IDPropertiesDiscoveryOptions, cb: Callback): Promise; + /** + * {@inheritDoc Connector/discoverExportedForeignKeys} * @deprecated */ - discoverExportedForeignKeysSync?(modelName: string, options?: {owner?: string}): ForeignKeysDiscoveryReturnValue; + discoverExportedForeignKeysSync?(modelName: string, options?: {owner?: string}): DiscoveredForeignKeys; + + /** + * Discover schema from a given table name / view name. + * + * @param tableName Target table name + * @param options Discovery options + * @param cb Callback function + */ discoverSchemas(tableName: string, options: SchemaDiscoveryOptions, cb: Callback): Promise; - isActual?(models: string[], cb: Callback): void; + /** + * Check whether or not migrations are required for the database schema to match + * the model definitions attached to the {@link DataSource}. + * + * @param models Name of models to check. If not defined, all models are checked. + * @param cb + */ + isActual?(models?: string | string[], cb?: Callback): void; freezeDataSource?(): void; freezeSchema?(): void; + + /** + * Normalize connector-specific return data into standardised Juggler context + * data. + * + * @remarks + * Depending on the connector, the database response can contain information + * about the updated record(s). This object usually has a database-specific + * structure and does not match model properties. For example, MySQL returns + * `OkPacket: {fieldCount, affectedRows, insertId, ... , changedRows}`. + * + * The return value is normalised data. + * + * If the connector DDL and DML functions map directly to a hash-map of + * model properties and their values, this function does not need to be + * implemented. + * + * @param context + * @param dbResponse + */ + generateContextData?(context: Context, dbResponse: unknown): Context; + [property: string]: any; // Other properties that vary by connectors } +export interface BuiltConnector extends Connector { + dataSource: DataSource; + log: DataSource['log']; + logger(query: string, start: number): (query: string) => void; +} + /** * Base connector class + * + * @internal */ export declare class ConnectorBase implements Connector { name: string; // Name/type of the connector; @@ -125,6 +290,16 @@ export declare class ConnectorBase implements Connector { static initialize(dataSource: DataSource, callback?: Callback): void; constructor(settings?: Options); + [property: string]: any; + _models?: object[] | undefined; + getDefaultIdType(): object; + isRelational(): boolean; + discoverSchemas(tableName: string, options: SchemaDiscoveryOptions, cb: Callback): Promise; +} + +export declare interface ConnectorStatic { + initialize(this: DataSource, callback: Callback): void; + new (settings: ConnectorSettings): Connector; } export declare class Memory extends ConnectorBase {} diff --git a/types/datasource.d.ts b/types/datasource.d.ts index ca128cc1e..c5f8f87e7 100644 --- a/types/datasource.d.ts +++ b/types/datasource.d.ts @@ -13,6 +13,7 @@ import { } from './model'; import {EventEmitter} from 'events'; import {IsolationLevel, Transaction} from './transaction-mixin'; +import { ConnectorSettings } from '..'; /** * LoopBack models can manipulate data via the DataSource object. @@ -76,17 +77,51 @@ import {IsolationLevel, Transaction} from './transaction-mixin'; */ export declare class DataSource extends EventEmitter { name: string; - settings: Options; + settings: ConnectorSettings; initialized?: boolean; connected?: boolean; connecting?: boolean; + + /** + * {@inheritDoc DataSource.connector} + * @deprecated Use {@link DataSource.connector} instead. + */ + adapter?: Connector; + /** + * Connector instance. + */ connector?: Connector; - + definitions: {[modelName: string]: ModelDefinition}; - + DataAccessObject: AnyObject & {prototype: AnyObject}; + + pendingConnectCallbacks?: Callback[]; + + /** + * Log benchmarked message. + * + * @remarks + * This property is assigned to the defined to the attached connector's + * {@link Connector.log | log} class member. + * + * @param sql + * @param t Start time + */ + private log(sql: string, t: number): void; + + private _operations: Record; + + /** + * Default global maximum number of event listeners. + * + * @remarks + * This default can be overriden through + * {@link ConnectorSettings.maxOfflineRequests}. + */ + static DEFAULT_MAX_OFFLINE_REQUESTS: number; constructor(name: string, settings?: Options, modelBuilder?: ModelBuilder); @@ -94,10 +129,48 @@ export declare class DataSource extends EventEmitter { constructor( connectorModule: Connector, - settings?: Options, + settings?: ConnectorSettings, modelBuilder?: ModelBuilder, ); + private setup(dsName: string, settings: ConnectorSettings): void; + private setup(settings: ConnectorSettings): void; + + private _setupConnector(); + + /** + * Get the maximum number of event listeners + * + * @remarks + * Defaults to {@link DataSource.DEFAULT_MAX_OFFLINE_REQUESTS} if not explicitly + * configured in {@link ConnectorSettings.maxOfflineRequests}. + */ + getMaxOfflineRequests(): number; + + // Reason for deprecation is not clear. + /** + * {@inheritDoc Connector.getTypes} + * @deprecated + */ + getTypes(): string[]; + + /** + * Check if the datasource supports the specified types. + * @param types Type name(s) to check against + */ + supportTypes(types: string | string[]): boolean; + + freeze(): void; + + tableName(modelName: string): string; + columnName(modelName: string, propertyName: string): string; + columnNames(modelName: string): string; + columnMetadata(modelName: string, propertyName: string): unknown; + idName(modelName: string): string; + idNames(modelName: string): string[]; + + defineForeignKey(className: string, key: string, foreignClassName: string, pkName?: string): undefined | void; + /** * Create a model class * @param name Name of the model @@ -107,7 +180,7 @@ export declare class DataSource extends EventEmitter { createModel( name: string, properties?: AnyObject, - options?: Options, + options?: ConnectorSettings, ): T; /** @@ -135,10 +208,13 @@ export declare class DataSource extends EventEmitter { /** * Attach an existing model to a data source. + * + * @remarks * This will mixin all of the data access object functions (DAO) into your * modelClass definition. - * @param {ModelBaseClass} modelClass The model constructor that will be enhanced - * by DataAccessObject mixins. + * + * @param modelClass The model constructor that will be + * enhanced by DataAccessObject mixins. */ attach(modelClass: ModelBaseClass): ModelBaseClass; @@ -150,6 +226,9 @@ export declare class DataSource extends EventEmitter { // legacy callback style autoupdate(models: string | string[] | undefined, callback: Callback): void; + /** + * {@inheritDoc Connector.discoverModelDefinitions} + */ discoverModelDefinitions( options?: Options, ): Promise; @@ -163,6 +242,9 @@ export declare class DataSource extends EventEmitter { callback: Callback, ): void; + /** + * {@inheritDoc Connector.discoverModelProperties} + */ discoverModelProperties( modelName: string, options?: Options, @@ -179,6 +261,9 @@ export declare class DataSource extends EventEmitter { callback: Callback, ): void; + /** + * {@inheritDoc Connector.discoverPrimaryKeys} + */ discoverPrimaryKeys( modelName: string, options?: Options, @@ -195,6 +280,9 @@ export declare class DataSource extends EventEmitter { callback: Callback, ): void; + /** + * {@inheritDoc Connector.discoverForeignKeys} + */ discoverForeignKeys( modelName: string, options?: Options, @@ -211,6 +299,9 @@ export declare class DataSource extends EventEmitter { callback: Callback, ): void; + /** + * {@inheritDoc Connector.discoverExportedForeignKeys} + */ discoverExportedForeignKeys( modelName: string, options?: Options, @@ -259,6 +350,9 @@ export declare class DataSource extends EventEmitter { callback: Callback, ): void; + /** + * {@inheritDoc Connector.discoverSchemas} + */ discoverSchemas( tableName: string, options?: Options, @@ -273,7 +367,7 @@ export declare class DataSource extends EventEmitter { tableName: string, options: Options, callback: Callback, - ): void; + ): void; buildModelFromInstance( modelName: string, @@ -281,10 +375,16 @@ export declare class DataSource extends EventEmitter { options?: Options, ): ModelBaseClass; + /** + * Connect to the DataSource. + */ connect(): Promise; // legacy callback style connect(callback: Callback): void; + /** + * Close the connection to the DataSource. + */ disconnect(): Promise; // legacy callback style disconnect(callback: Callback): void; @@ -293,8 +393,25 @@ export declare class DataSource extends EventEmitter { // Note we must use `void | PromiseLike` to avoid breaking // existing LoopBack 4 applications. // TODO(semver-major): change the return type to `Promise` + /** + * An alias for {@link DataSource.disconnect} to allow this datasource to be + * an LB4 life-cycle observer + * + * @remarks + * A `.start()` equivalent was deliberately not provided as the logic for + * establishing connection(s) is more complex and is usually statred + * immediately from the datasource constructor. + */ stop(): void | PromiseLike; + /** + * Ping the underlying connector to test the connections. + * + * @remarks + * If {@link Connector.ping} is not implemented, + * {@link Connector.discoverModelProperties} is used instead. Otherwise, an + * error is thrown. + */ ping(): Promise; // legacy callback style ping(callback: Callback): void; diff --git a/types/model.d.ts b/types/model.d.ts index 7b15a9d6e..0b5bb0c86 100644 --- a/types/model.d.ts +++ b/types/model.d.ts @@ -77,6 +77,11 @@ export interface ModelProperties { export interface ModelSettings extends AnyObject { strict?: boolean; forceId?: boolean; + persistUndefinedAsNull?: boolean; + protectedProperties?: string[]; + protected?: string[]; + hiddenProperties?: string[]; + hidden?: string[]; } /** @@ -233,6 +238,8 @@ export declare class ModelBase { getDataSource(): DataSource; + setStrict(strict: boolean): void; + /** * * @param {string} anotherClass could be string or class. Name of the class From b500cd733dd91213a6183ad61e65c36acb02fb43 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Fri, 24 Sep 2021 17:20:49 +0800 Subject: [PATCH 03/26] fix: update ConnectorSettings.connector Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/connector.d.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/types/connector.d.ts b/types/connector.d.ts index 782c857ad..1b26f8528 100644 --- a/types/connector.d.ts +++ b/types/connector.d.ts @@ -6,21 +6,16 @@ import { AnyObject } from 'strong-globalize/lib/config'; import {Callback, DataSource, ModelBase, ModelBaseClass, ModelDefinition, ModelProperties, Options, PromiseOrVoid, PropertyDefinition, PropertyType, Schema, Where} from '..'; -// Copyright IBM Corp. 2018. All Rights Reserved. -// Node module: loopback-datasource-juggler -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - export type ConnectorSettings = Options & { name?: string, /** * Overrides {@link ConnectorSettings.adapter} if defined. */ - connector?: Connector, + connector?: ConnectorStatic | string, /** * @deprecated Use {@link ConnectorSettings.connector} instead. */ - adapter?: Connector, + adapter?: ConnectorStatic | string, connectionTimeout?: number, maxOfflineRequests?: number, lazyConnect?: boolean, From 6c66be85bbd524e24a2a7eb365d4484ebcd92aab Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Fri, 24 Sep 2021 17:27:46 +0800 Subject: [PATCH 04/26] refactor: NameMapper Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/connector.d.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/types/connector.d.ts b/types/connector.d.ts index 1b26f8528..56ca93cc9 100644 --- a/types/connector.d.ts +++ b/types/connector.d.ts @@ -45,10 +45,12 @@ export type SchemaDiscoveryOptions = { all?: boolean, views?: boolean, disableCamelCase?: boolean, - nameMapper?: (type: 'table' | 'model' | 'fk' | string, name: string) => string | null; - associations?: boolean; + nameMapper?: NameMapper, + associations?: boolean, } +export type NameMapper = (type: 'table' | 'model' | 'fk' | string, name: string) => string | null; + export type DiscoveredPrimaryKeys = { owner: string | null, tableName: string, From 2cbf624329509c222799ffdc0348f7a46ab049b5 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Fri, 24 Sep 2021 17:37:53 +0800 Subject: [PATCH 05/26] feat: add missing non-function class members Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/datasource.d.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/types/datasource.d.ts b/types/datasource.d.ts index c5f8f87e7..94b6573cf 100644 --- a/types/datasource.d.ts +++ b/types/datasource.d.ts @@ -13,7 +13,7 @@ import { } from './model'; import {EventEmitter} from 'events'; import {IsolationLevel, Transaction} from './transaction-mixin'; -import { ConnectorSettings } from '..'; +import { ConnectorSettings, ModelBase } from '..'; /** * LoopBack models can manipulate data via the DataSource object. @@ -93,6 +93,10 @@ export declare class DataSource extends EventEmitter { * Connector instance. */ connector?: Connector; + + modelBuilder: ModelBuilder; + + models: Record; definitions: {[modelName: string]: ModelDefinition}; @@ -112,6 +116,8 @@ export declare class DataSource extends EventEmitter { */ private log(sql: string, t: number): void; + private _queuedInvocations: number; + private _operations: Record; /** From 647917345e51dfe3ab64d53bac2885d39dc44bcb Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Fri, 24 Sep 2021 20:14:32 +0800 Subject: [PATCH 06/26] feat: add operations typedefs Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/datasource.d.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/types/datasource.d.ts b/types/datasource.d.ts index 94b6573cf..a8e0582de 100644 --- a/types/datasource.d.ts +++ b/types/datasource.d.ts @@ -15,6 +15,15 @@ import {EventEmitter} from 'events'; import {IsolationLevel, Transaction} from './transaction-mixin'; import { ConnectorSettings, ModelBase } from '..'; +export type OperationOptions = { + accepts: string[], + returns: string[], + http: object, + remoteEnabled: boolean, + scope: unknown, + fnName: string, +} + /** * LoopBack models can manipulate data via the DataSource object. * Attaching a `DataSource` to a `Model` adds instance methods and static methods to the `Model`. @@ -118,8 +127,16 @@ export declare class DataSource extends EventEmitter { private _queuedInvocations: number; - private _operations: Record; + private _operations: Record; + + operations(): Record; + + defineOperation(name: string, options: OperationOptions, fn: Function): void; + enableRemote(operation: string): void; + + disableRemote(operation: string): void; + /** * Default global maximum number of event listeners. * From b460b944dea843ed70dd9cec40498e491f57d77c Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Fri, 24 Sep 2021 21:43:54 +0800 Subject: [PATCH 07/26] feat: add missing types Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/connector.d.ts | 8 +++++ types/datasource.d.ts | 69 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/types/connector.d.ts b/types/connector.d.ts index 56ca93cc9..01c95e0fe 100644 --- a/types/connector.d.ts +++ b/types/connector.d.ts @@ -134,6 +134,14 @@ export interface Connector { */ getTypes?(): string[]; define?(def: {model: ModelBaseClass, properties: PropertyDefinition, settings: ModelDefinition['settings']}): void; + + /** + * Define a property on the target model. + * + * @param model Name of model + * @param prop Name of property + * @param params Property settings + */ defineProperty?(model: string, prop: string, params: PropertyDefinition): void; defineForeignKey?(modelName: string, key: string, foreignModelName: string, cb: Callback): void; defineForeignKey?(modelName: string, key: string, cb: Callback): void; diff --git a/types/datasource.d.ts b/types/datasource.d.ts index a8e0582de..a575b1d64 100644 --- a/types/datasource.d.ts +++ b/types/datasource.d.ts @@ -13,7 +13,7 @@ import { } from './model'; import {EventEmitter} from 'events'; import {IsolationLevel, Transaction} from './transaction-mixin'; -import { ConnectorSettings, ModelBase } from '..'; +import { ConnectorSettings, ModelBase, ModelSettings } from '..'; export type OperationOptions = { accepts: string[], @@ -185,13 +185,72 @@ export declare class DataSource extends EventEmitter { freeze(): void; + /** + * Return the table name for the specified `modelName`. + * + * @param modelName Target model name + * @returns The table name + */ tableName(modelName: string): string; + + /** + * Retrieve the column name for the specified `modelName` and `propertyName`. + * + * @param modelName Target model name + * @param propertyName Target property name + * @returns The column name + */ columnName(modelName: string, propertyName: string): string; + + /** + * Retrieve the column names for the specified `modelName`. + * + * @param modelName Target model name + * @returns Column names + */ columnNames(modelName: string): string; + + /** + * Retrieve coulmn metadata for the specified `modelName` and `propertyName`. + * + * @param modelName Target model name + * @param propertyName Target property name + * @returns Column metadata + */ columnMetadata(modelName: string, propertyName: string): unknown; + + /** + * Retrieve the ID property name for a model. + * + * @param modelName Target model name + * @returns ID property name + */ idName(modelName: string): string; + + /** + * Retrieve the ID property names sorted by their index. + * + * @param modelName Target model name + * @returns Property names for IDs + */ idNames(modelName: string): string[]; + /** + * Retrieve the ID property definition. + * + * @param modelName Target model name + * @returns The ID property definition + */ + idProperty(modelName: string): PropertyDefinition; + + /** + * Define a foreign key to another model. + * + * @param className The model name that owns the key + * @param key Name of key field + * @param foreignClassName Foreign model name + * @param pkName Primary key used for foreign key + */ defineForeignKey(className: string, key: string, foreignClassName: string, pkName?: string): undefined | void; /** @@ -203,9 +262,15 @@ export declare class DataSource extends EventEmitter { createModel( name: string, properties?: AnyObject, - options?: ConnectorSettings, + options?: ModelSettings, ): T; + /** + * {@inheritDoc DataSource.createModel} + * @deprecated Use {@link DataSource.createModel} instead + */ + define: DataSource['createModel']; + /** * Look up a model class by name * @param modelName Model name From 68521cb8839693b86c2554fb9dc304d4dde8a634 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Fri, 24 Sep 2021 22:21:08 +0800 Subject: [PATCH 08/26] update operation hook context typedef Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/connector.d.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/types/connector.d.ts b/types/connector.d.ts index 01c95e0fe..5e2db7cff 100644 --- a/types/connector.d.ts +++ b/types/connector.d.ts @@ -4,7 +4,7 @@ // License text available at https://opensource.org/licenses/MIT import { AnyObject } from 'strong-globalize/lib/config'; -import {Callback, DataSource, ModelBase, ModelBaseClass, ModelDefinition, ModelProperties, Options, PromiseOrVoid, PropertyDefinition, PropertyType, Schema, Where} from '..'; +import {Callback, DataSource, Filter, ModelBase, ModelBaseClass, ModelDefinition, ModelProperties, Options, PromiseOrVoid, PropertyDefinition, PropertyType, Schema, Where} from '..'; export type ConnectorSettings = Options & { name?: string, @@ -90,12 +90,13 @@ export type DiscoveredModelProperties = { export type Context = { Model: ModelBaseClass, instance?: object, - query?: {where: Where}, + query?: Filter, + where?: Where, data?: AnyObject, hookState?: AnyObject, options?: Options, isNewInstance?: boolean, - currentInstance?: ModelBase, + currentInstance?: Readonly, } /** From 27496ae9300d93e5cff76c33708b963fe27b2b02 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Fri, 24 Sep 2021 22:30:22 +0800 Subject: [PATCH 09/26] feat: update datasource typedefs Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/datasource.d.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/types/datasource.d.ts b/types/datasource.d.ts index a575b1d64..8b3158297 100644 --- a/types/datasource.d.ts +++ b/types/datasource.d.ts @@ -146,9 +146,14 @@ export declare class DataSource extends EventEmitter { */ static DEFAULT_MAX_OFFLINE_REQUESTS: number; - constructor(name: string, settings?: Options, modelBuilder?: ModelBuilder); + /** + * A hash-map of the different relation types. + */ + static relationTypes: Record; - constructor(settings?: Options, modelBuilder?: ModelBuilder); + constructor(name: string, settings?: ConnectorSettings, modelBuilder?: ModelBuilder); + + constructor(settings?: ConnectorSettings, modelBuilder?: ModelBuilder); constructor( connectorModule: Connector, @@ -183,6 +188,14 @@ export declare class DataSource extends EventEmitter { */ supportTypes(types: string | string[]): boolean; + /** + * Returns if the attached connector is relational. + * + * @returns If the attached connector is relational; `undefined` if no + * connector is attached. + */ + isRelational(): boolean | undefined; + freeze(): void; /** From ecb1a7bdf617fc631f1295d6f6aa9f695468f0a4 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Fri, 24 Sep 2021 22:43:56 +0800 Subject: [PATCH 10/26] feat(PropertyDefinition): add missing props Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/model.d.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/types/model.d.ts b/types/model.d.ts index 0b5bb0c86..91e4241db 100644 --- a/types/model.d.ts +++ b/types/model.d.ts @@ -22,6 +22,13 @@ export type PropertyType = export interface PropertyDefinition extends AnyObject { type?: PropertyType; id?: boolean | number; + tableName?: string; + columnName?: string; + dataType?: string; + dataLength?: number; + dataPrecision?: number; + dataScale?: number; + nullable?: boolean; } /** From 54aa233edc63fba3eb26a67b5dd2a803e827d066 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Fri, 24 Sep 2021 22:45:20 +0800 Subject: [PATCH 11/26] feat(DataSource): refine `columnMetadata` return type Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/datasource.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/datasource.d.ts b/types/datasource.d.ts index 8b3158297..5f86c202f 100644 --- a/types/datasource.d.ts +++ b/types/datasource.d.ts @@ -13,7 +13,7 @@ import { } from './model'; import {EventEmitter} from 'events'; import {IsolationLevel, Transaction} from './transaction-mixin'; -import { ConnectorSettings, ModelBase, ModelSettings } from '..'; +import { ColumnMetadata, ConnectorSettings, ModelBase, ModelSettings } from '..'; export type OperationOptions = { accepts: string[], @@ -230,7 +230,7 @@ export declare class DataSource extends EventEmitter { * @param propertyName Target property name * @returns Column metadata */ - columnMetadata(modelName: string, propertyName: string): unknown; + columnMetadata(modelName: string, propertyName: string): ColumnMetadata; /** * Retrieve the ID property name for a model. From f75a599679fa2638db93d3286f96afabd07d16d9 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Fri, 24 Sep 2021 22:51:36 +0800 Subject: [PATCH 12/26] feat(Connector): refine `_models` typedef Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/connector.d.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/types/connector.d.ts b/types/connector.d.ts index 5e2db7cff..66d5f8e64 100644 --- a/types/connector.d.ts +++ b/types/connector.d.ts @@ -104,7 +104,11 @@ export type Context = { */ export interface Connector { name: string; // Name/type of the connector - _models?: object[]; + + /** + * @internal + */ + _models?: Record; connect?(callback?: Callback): PromiseOrVoid; // Connect to the underlying system disconnect?(callback?: Callback): PromiseOrVoid; // Disconnect from the underlying system From 17ff1659ff94e9c04b751c22ad6344415ee2f636 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Fri, 24 Sep 2021 23:03:59 +0800 Subject: [PATCH 13/26] feat: refined defs Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/connector.d.ts | 2 +- types/datasource.d.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/types/connector.d.ts b/types/connector.d.ts index 66d5f8e64..99b6ff7ad 100644 --- a/types/connector.d.ts +++ b/types/connector.d.ts @@ -301,7 +301,7 @@ export declare class ConnectorBase implements Connector { constructor(settings?: Options); [property: string]: any; - _models?: object[] | undefined; + _models?: Record; getDefaultIdType(): object; isRelational(): boolean; discoverSchemas(tableName: string, options: SchemaDiscoveryOptions, cb: Callback): Promise; diff --git a/types/datasource.d.ts b/types/datasource.d.ts index 5f86c202f..0c3200715 100644 --- a/types/datasource.d.ts +++ b/types/datasource.d.ts @@ -259,6 +259,14 @@ export declare class DataSource extends EventEmitter { /** * Define a foreign key to another model. * + * @remarks + * If the attached {@link Connector} did not implement + * {@link Connector.defineForeignKey}, this function will fallback to defining + * a regular property. Furthermore, responsibility of properly implementing + * this feature depends on the connector's implementation. This means a + * database-level foreign key is not guaranteed. Please refer to the + * respective connector's documentation. + * * @param className The model name that owns the key * @param key Name of key field * @param foreignClassName Foreign model name From 723459907bd007de4aa4f484b21d94010eab4513 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Sun, 26 Sep 2021 17:13:05 +0800 Subject: [PATCH 14/26] feat: misc. updates Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/connector.d.ts | 75 ++++++++++++++++++++++++++++++++++++++----- types/datasource.d.ts | 4 +++ 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/types/connector.d.ts b/types/connector.d.ts index 99b6ff7ad..71641de05 100644 --- a/types/connector.d.ts +++ b/types/connector.d.ts @@ -37,17 +37,57 @@ export type DiscoveryScopeOptions = { } export type SchemaDiscoveryOptions = { - owner?: string, + /** - * Overriden by {@link SchemaDiscoveryOptions.associations}. + * Sets if all owners are included. */ - relations?: boolean, all?: boolean, + + /** + * Sets if views are included. + */ views?: boolean, + + /** + * Sets if the database foreign key column names should be transformed. + * + * @remarks + * Used by the default built-in {@link NameMapper} implementation to transform + * the database column names to use camel-case, LoopBack's default naming + * conventions. + * + * @defaultValue `false` + */ disableCamelCase?: boolean, - nameMapper?: NameMapper, - associations?: boolean, + + /** + * A custom database table name, model, and foreign key transformer. + * + * @remarks + * If `null`, no transform is performed. + * If `undefined`, default built-in transformer is used. + */ + nameMapper?: NameMapper | null, } +& ({ + + /** + * Sets if associations/relations should be navigated. + * + * @remarks + * Alias of {@link SchemaDiscoveryOptions.relations} + */ + associations?: boolean, +} | { + /** + * Sets if associations/relations should be navigated. + * + * @remarks + * Alias of {@link SchemaDiscoveryOptions.associations}. + */ + relations?: boolean, +}) +& ({owner?: string} | {schema?: string}) export type NameMapper = (type: 'table' | 'model' | 'fk' | string, name: string) => string | null; @@ -83,7 +123,6 @@ export type DiscoveredModelProperties = { nullable?: boolean, } - // #TODO(achrinza): The codebase suggets that `context` differs // depending on the situation, and that there's no cohesive interface. // Hence, we'll need to identify all the possible contexts. @@ -93,12 +132,20 @@ export type Context = { query?: Filter, where?: Where, data?: AnyObject, - hookState?: AnyObject, - options?: Options, + hookState: object, + options: Options, isNewInstance?: boolean, currentInstance?: Readonly, } +export type ConnectorHookBeforeExecuteContext = { + req: Record, + end: Callback, +} + +export type ConnectorHookAfterExecuteContext = ConnectorHookBeforeExecuteContext & { + res: Record, +} /** * Connector from `loopback-connector` module */ @@ -245,7 +292,19 @@ export interface Connector { * @param cb */ isActual?(models?: string | string[], cb?: Callback): void; + + /** + * Freeze the datasource. Behaviour depends on the {@link Connector}. + */ freezeDataSource?(): void; + + /** + * {@inheritDoc Connector.freezeDataSource} + * + * @remarks + * This is kept for backwards-compatibility with JugglingDB connectors. + * Connectors should implement {@link Connector.freezeDataSource}. + */ freezeSchema?(): void; /** diff --git a/types/datasource.d.ts b/types/datasource.d.ts index 0c3200715..012d6d5a2 100644 --- a/types/datasource.d.ts +++ b/types/datasource.d.ts @@ -24,6 +24,10 @@ export type OperationOptions = { fnName: string, } +export type DiscoverAndBuildModelsOptions = Options & { + base: ModelBaseClass, +} + /** * LoopBack models can manipulate data via the DataSource object. * Attaching a `DataSource` to a `Model` adds instance methods and static methods to the `Model`. From fe451247f6eba0e56bac16b5b547b853fc572f7d Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Mon, 27 Sep 2021 22:58:10 +0800 Subject: [PATCH 15/26] feat: update model settings, prop defs Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/model.d.ts | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/types/model.d.ts b/types/model.d.ts index 91e4241db..be3397b86 100644 --- a/types/model.d.ts +++ b/types/model.d.ts @@ -22,7 +22,7 @@ export type PropertyType = export interface PropertyDefinition extends AnyObject { type?: PropertyType; id?: boolean | number; - tableName?: string; + column?: string; columnName?: string; dataType?: string; dataLength?: number; @@ -84,11 +84,39 @@ export interface ModelProperties { export interface ModelSettings extends AnyObject { strict?: boolean; forceId?: boolean; + + tableName?: string + table?: string; + persistUndefinedAsNull?: boolean; + + /** + * Model properties to be set as protected. + * + * @remarks + * Protected properties are properties that are not serialised to JSON or + * {@link object} when the model is a nested child. + * + * Mostly used by {@link ModelBase.toObject}. + */ protectedProperties?: string[]; + + /** + * {@inheritDoc ModelSettings.protectedProperties} + * @deprecated Use {@link ModelSettings.protectedProperties} instead. + */ protected?: string[]; + hiddenProperties?: string[]; + + /** + * @deprecated Use {@link ModelSettings.hiddenProperties} instead. + */ hidden?: string[]; + automaticValidation?: boolean, + updateOnLoad?: boolean, + validateUpsert?: boolean, + strictDelete?: boolean, } /** From 181578d003d6bf1aa80fcb8985b7e7b8bbb8102a Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Tue, 28 Sep 2021 17:23:31 +0800 Subject: [PATCH 16/26] feat: add initial jutil typedefs Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/jutil.d.ts | 13 +++++++++++++ types/model.d.ts | 2 ++ 2 files changed, 15 insertions(+) create mode 100644 types/jutil.d.ts diff --git a/types/jutil.d.ts b/types/jutil.d.ts new file mode 100644 index 000000000..13aa6dfda --- /dev/null +++ b/types/jutil.d.ts @@ -0,0 +1,13 @@ +export type InheritsOptions = { + staticProperties?: boolean, + override?: boolean, +} + +export type MixinOptions = InheritsOptions & { + instanceProperties?: boolean, + proxyFunctions?: boolean, +} + +export function inherits(newClass: T, baseClass: object, options: InheritsOptions): T; + +export function mixin(newClass: T, mixinClass: object, options: MixinOptions): T; diff --git a/types/model.d.ts b/types/model.d.ts index be3397b86..8a5553962 100644 --- a/types/model.d.ts +++ b/types/model.d.ts @@ -117,6 +117,8 @@ export interface ModelSettings extends AnyObject { updateOnLoad?: boolean, validateUpsert?: boolean, strictDelete?: boolean, + + allowExtendedOperators?: boolean, } /** From 2e7b0cf35fd40bb9f138cf06c525252371fb5082 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Tue, 28 Sep 2021 17:40:34 +0800 Subject: [PATCH 17/26] feat(DataSource): add prototype.mixin Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/datasource.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/datasource.d.ts b/types/datasource.d.ts index 012d6d5a2..a60ddae62 100644 --- a/types/datasource.d.ts +++ b/types/datasource.d.ts @@ -170,6 +170,8 @@ export declare class DataSource extends EventEmitter { private _setupConnector(); + private mixin(ModelCtor: T): T + /** * Get the maximum number of event listeners * From aedf47a1eb50149488c2c1b6ad740a8a413d349f Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Wed, 29 Sep 2021 18:56:34 +0800 Subject: [PATCH 18/26] feat: initial kvao typedefs Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- index.d.ts | 1 + types/kvao.d.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 types/kvao.d.ts diff --git a/index.d.ts b/index.d.ts index 3e6abc203..106f63337 100644 --- a/index.d.ts +++ b/index.d.ts @@ -35,3 +35,4 @@ export * from './types/validation-mixin'; export * from './types/inclusion-mixin'; export * from './types/connector'; export * from './types/geo'; +export * from './types/kvao'; diff --git a/types/kvao.d.ts b/types/kvao.d.ts new file mode 100644 index 000000000..69e9ab056 --- /dev/null +++ b/types/kvao.d.ts @@ -0,0 +1,35 @@ +import { AnyObject, Callback, Options } from "./common"; +import { Connector } from "./connector"; + +export interface MatchFilter { + match?: string; +} + +export declare interface KeyValueAccessObject { + delete(key: string, callback?: Callback): Promise | undefined; + delete(key: string, options?: Options, callback?: Callback): Promise | undefined; + + deleteAll(callback?: Callback): Promise | undefined; + deleteAll(options?: Options, callback?: Callback): Promise | undefined; + + get(key: string, callback?: Callback): Promise | undefined; + get(key: string, options?: Options, callback?: Callback): Promise | undefined; + + set(key: string, value: unknown, callback?: Callback): Promise | undefined; + set(key: string, value: unknown, options?: Options, callback?: Callback): Promise | undefined; + + expire(key: string, ttl: number, callback?: Callback): Promise | undefined; + expire(key: string, ttl: number, options?: Options, callback?: Callback): Promise | undefined; + + ttl(key: string, callback?: Callback): Promise | undefined; + ttl(key: string, options?: Options, callback?: Callback): Promise | undefined; + + iterateKeys(options: Options): Promise | undefined; + iterateKeys(filter: MatchFilter, options: Options): Promise | undefined; + + keys(callback?: Callback): Promise | undefined; + keys(options?: Options, callback?: Callback): Promise | undefined; + keys(filter?: MatchFilter, options?: Options, callback?: Callback): Promise | undefined; + + getConnector(): CT; +} From 8b5631097dd6b6def1237b6bf542255ab68e6f1a Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Thu, 30 Sep 2021 23:24:54 +0800 Subject: [PATCH 19/26] feat: misc. updates Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/connector.d.ts | 146 +++++++++++++++++++++-------------------- types/dao.d.ts | 14 ++++ types/model-utils.d.ts | 22 +++++++ types/model.d.ts | 100 ++++++++++++++++++++++++++-- 4 files changed, 206 insertions(+), 76 deletions(-) create mode 100644 types/dao.d.ts create mode 100644 types/model-utils.d.ts diff --git a/types/connector.d.ts b/types/connector.d.ts index 71641de05..28cd82c55 100644 --- a/types/connector.d.ts +++ b/types/connector.d.ts @@ -6,47 +6,51 @@ import { AnyObject } from 'strong-globalize/lib/config'; import {Callback, DataSource, Filter, ModelBase, ModelBaseClass, ModelDefinition, ModelProperties, Options, PromiseOrVoid, PropertyDefinition, PropertyType, Schema, Where} from '..'; -export type ConnectorSettings = Options & { - name?: string, +export interface ConnectorSettings extends Options { + name?: string; /** * Overrides {@link ConnectorSettings.adapter} if defined. */ - connector?: ConnectorStatic | string, + connector?: ConnectorStatic | string; /** * @deprecated Use {@link ConnectorSettings.connector} instead. */ - adapter?: ConnectorStatic | string, - connectionTimeout?: number, - maxOfflineRequests?: number, - lazyConnect?: boolean, - debug?: boolean, + adapter?: ConnectorStatic | string; + connectionTimeout?: number; + maxOfflineRequests?: number; + lazyConnect?: boolean; + debug?: boolean; + + // Postgres-specific + defaultIdSort?: boolean | 'numericIdOnly'; + onError?: (err: Error | unknown) => unknown | 'ignore'; + // END Postgres-specific } -export type IDPropertiesDiscoveryOptions = { - owner?: string, -} | { - schema?: string +export interface IDPropertiesDiscoveryOptions { + owner?: string; + schema?: string; } -export type DiscoveryScopeOptions = { - owner?: string, - all?: boolean, - views?: boolean, - limit?: number, - offset?: number, +export interface DiscoveryScopeOptions { + owner?: string; + all?: boolean; + views?: boolean; + limit?: number; + offset?: number; } -export type SchemaDiscoveryOptions = { +export interface SchemaDiscoveryOptions { /** * Sets if all owners are included. */ - all?: boolean, + all?: boolean; /** * Sets if views are included. */ - views?: boolean, + views?: boolean; /** * Sets if the database foreign key column names should be transformed. @@ -58,7 +62,7 @@ export type SchemaDiscoveryOptions = { * * @defaultValue `false` */ - disableCamelCase?: boolean, + disableCamelCase?: boolean; /** * A custom database table name, model, and foreign key transformer. @@ -67,9 +71,7 @@ export type SchemaDiscoveryOptions = { * If `null`, no transform is performed. * If `undefined`, default built-in transformer is used. */ - nameMapper?: NameMapper | null, -} -& ({ + nameMapper?: NameMapper | null; /** * Sets if associations/relations should be navigated. @@ -77,74 +79,76 @@ export type SchemaDiscoveryOptions = { * @remarks * Alias of {@link SchemaDiscoveryOptions.relations} */ - associations?: boolean, -} | { + associations?: boolean; + /** * Sets if associations/relations should be navigated. * * @remarks * Alias of {@link SchemaDiscoveryOptions.associations}. */ - relations?: boolean, -}) -& ({owner?: string} | {schema?: string}) + relations?: boolean; + + owner?: string; + schema?: string; +} export type NameMapper = (type: 'table' | 'model' | 'fk' | string, name: string) => string | null; -export type DiscoveredPrimaryKeys = { - owner: string | null, - tableName: string, - columnName: string, - keySeq: number, - pkName: string | null, +export interface DiscoveredPrimaryKeys { + owner: string | null; + tableName: string; + columnName: string; + keySeq: number; + pkName: string | null; } -export type DiscoveredForeignKeys = { - fkOwner: string | null, - fkName: string | null, - fkTableName: string, - fkColumnName: string, - keySeq: number, - - pkOwner: string | null, - pkName: string | null, - pkTableName: string, - pkColumnName: string, +export interface DiscoveredForeignKeys { + fkOwner: string | null; + fkName: string | null; + fkTableName: string; + fkColumnName: string; + keySeq: number; + + pkOwner: string | null; + pkName: string | null; + pkTableName: string; + pkColumnName: string; } -export type DiscoveredModelProperties = { - owner?: string, - tableName?: string, - columnName?: string, - dataType?: string, - dataLength?: number, - dataPrecision?: number, - dataScale?: number, - nullable?: boolean, +export interface DiscoveredModelProperties { + owner?: string; + tableName?: string; + columnName?: string; + dataType?: string; + dataLength?: number; + dataPrecision?: number; + dataScale?: number; + nullable?: boolean; } // #TODO(achrinza): The codebase suggets that `context` differs // depending on the situation, and that there's no cohesive interface. // Hence, we'll need to identify all the possible contexts. -export type Context = { - Model: ModelBaseClass, - instance?: object, - query?: Filter, - where?: Where, - data?: AnyObject, - hookState: object, - options: Options, - isNewInstance?: boolean, - currentInstance?: Readonly, +export interface Context { + Model: ModelBaseClass; + instance?: object; + query?: Filter; + where?: Where; + data?: AnyObject; + hookState: object; + options: Options; + isNewInstance?: boolean; + currentInstance?: Readonly; } -export type ConnectorHookBeforeExecuteContext = { - req: Record, - end: Callback, +export interface ConnectorHookBeforeExecuteContext { + req: Record; + end: Callback; } -export type ConnectorHookAfterExecuteContext = ConnectorHookBeforeExecuteContext & { - res: Record, +export interface ConnectorHookAfterExecuteContext extends ConnectorHookBeforeExecuteContext { + res: Record; } /** * Connector from `loopback-connector` module diff --git a/types/dao.d.ts b/types/dao.d.ts new file mode 100644 index 000000000..49352348c --- /dev/null +++ b/types/dao.d.ts @@ -0,0 +1,14 @@ +import { Options } from ".."; + +export interface DaoDmlOptions extends Options { + validate?: boolean; + notify?: boolean; +} + +export interface DaoUpsertOptions extends DaoDmlOptions { + validateUpsert?: boolean; +} + +export interface DaoUpdateOptions extends DaoDmlOptions { + validateUpdate?: boolean; +} diff --git a/types/model-utils.d.ts b/types/model-utils.d.ts new file mode 100644 index 000000000..9e20a5630 --- /dev/null +++ b/types/model-utils.d.ts @@ -0,0 +1,22 @@ +import type {ConnectorSettings} from './connector' +// import type {DataAccessObject} from './dao'; +import type {ModelSettings} from './model'; + +/** + * Settings made available by {@link ModelUtils} mixin. + * + * @remarks + * When the mixin is applied, these options can be set in + * {@link DataAccessObject} query method-level options, {@link ModelSettings}, + * and {@link ConnectorSettings} in descending precedence. + */ +export interface ModelUtilsOptions { + /** + * Sets if non-standard {@link Where} operators are permitted in passed + * {@link Filter}. + */ + allowExtendedOperators?: boolean; + maxDepthOfQuery?: number; + maxDepthOfData?: number; + prohibitHiddenPropertiesInQuery?: boolean; +} diff --git a/types/model.d.ts b/types/model.d.ts index 8a5553962..898a95843 100644 --- a/types/model.d.ts +++ b/types/model.d.ts @@ -7,23 +7,30 @@ import {EventEmitter} from 'events'; import {AnyObject, Options} from './common'; import {DataSource} from './datasource'; import {Listener, OperationHookContext} from './observer-mixin'; +import {ModelUtilsOptions} from './model-utils'; /** * Property types */ export type PropertyType = + | 'GeoPoint' + | 'Point' | string | Function | {[property: string]: PropertyType}; +export type DefaultFns = 'guid' | 'uuid' | 'uuidv4' | 'now' | 'shortid' | 'nanoid' | string; + /** * Property definition */ export interface PropertyDefinition extends AnyObject { type?: PropertyType; id?: boolean | number; - column?: string; + defaultFn?: DefaultFns; + useDefaultIdType?: boolean; columnName?: string; + column?: string; dataType?: string; dataLength?: number; dataPrecision?: number; @@ -81,21 +88,76 @@ export interface ModelProperties { * } * ``` */ -export interface ModelSettings extends AnyObject { +export interface ModelSettings extends AnyObject, ModelUtilsOptions { strict?: boolean; + + /** + * Set if manual assignment of auto-generated ID values should be blocked. + */ forceId?: boolean; + indexes?: { + [indexJugglerName: string]: { + name: string; + /** + * Comma-separated column names + * + * @remarks + * Handled by {@link Connector}s directly by default. + * + * Overriden by {@link ModelSettings.indexes.keys}. + */ + columns: string; + + /** + * Array of column names to create an index. + * + * @remarks + * Handled by {@link Connector}s directly by default. + * + * Overrides {@link ModelSettings.indexes.columns}. + */ + keys: string[]; + + // Postgresql-specific + type: string; + kind: string; + } + }; + + foreignKeys?: { + [fkJugglerName: string]: { + name: string, + entity: ModelBase | string, + entityKey: string, + foreignKey: string, + onDelete?: string, + onUpdate?: string, + } + } + + /** + * {@inheritDoc ModelSettings.tableName} + */ tableName?: string + + /** + * Mapped table name for the model. + */ table?: string; + /** + * Sets if JavaScript {@link undefined} as an attribute value should be + * persisted as database `NULL`. + */ persistUndefinedAsNull?: boolean; /** * Model properties to be set as protected. * * @remarks - * Protected properties are properties that are not serialised to JSON or - * {@link object} when the model is a nested child. + * Protected properties are not serialised to JSON or {@link object} when the + * model is a nested child. * * Mostly used by {@link ModelBase.toObject}. */ @@ -103,22 +165,50 @@ export interface ModelSettings extends AnyObject { /** * {@inheritDoc ModelSettings.protectedProperties} + * + * @remarks + * Overriden by {@link ModelSettings.protectedProperties}. + * * @deprecated Use {@link ModelSettings.protectedProperties} instead. */ protected?: string[]; + /** + * Model properties to be set as hidden. + * + * @remarks + * Hidden properties are + */ hiddenProperties?: string[]; /** + * {@inheritDoc ModelSettings.hiddenProperties} + * + * @remarks + * Overriden by {@link ModelSettings.hiddenProperties}. + * * @deprecated Use {@link ModelSettings.hiddenProperties} instead. */ hidden?: string[]; automaticValidation?: boolean, updateOnLoad?: boolean, validateUpsert?: boolean, + + /** + * Sets if an {@link Error} should be thrown when no instance(s) were found + * for delete operation. + * + * @remarks + * + * This setting is used by these operations: + * + * - {@link DataAccessObject.removeById}/{@link DataAccessObject.destroyById}/{@link DataAccessObject.deleteById} + * - {@link DataAccessObject.remove}/{@link DataAccessObject.delete}/{@link DataAccessObject.destroy} + */ strictDelete?: boolean, - allowExtendedOperators?: boolean, + // Postgres-specific + defaultIdSort?: boolean | 'numericIdOnly'; } /** From 055efd3e89e2326bc81e348cbb1c6ef973ab6428 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Fri, 3 Dec 2021 21:26:35 +0800 Subject: [PATCH 20/26] feat: misc updates Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/connector.d.ts | 36 +++++++++++++++++ types/datasource.d.ts | 50 ++++++++++++++++++++++-- types/model.d.ts | 90 +++++++++++++++++++++++++++++++++++++++---- types/test-suite.d.ts | 34 ++++++++++++++++ 4 files changed, 199 insertions(+), 11 deletions(-) create mode 100644 types/test-suite.d.ts diff --git a/types/connector.d.ts b/types/connector.d.ts index 28cd82c55..120e079d6 100644 --- a/types/connector.d.ts +++ b/types/connector.d.ts @@ -16,6 +16,7 @@ export interface ConnectorSettings extends Options { * @deprecated Use {@link ConnectorSettings.connector} instead. */ adapter?: ConnectorStatic | string; + database: string; connectionTimeout?: number; maxOfflineRequests?: number; lazyConnect?: boolean; @@ -95,6 +96,30 @@ export interface SchemaDiscoveryOptions { export type NameMapper = (type: 'table' | 'model' | 'fk' | string, name: string) => string | null; +export interface BuildQueryOptions { + /** + * Build a query to search tables/views/schemas from any owner. + * + * @remarks + * Ignored when {@link BuildQueryOptions.owner} or + * {@link BuildQueryOptions.schema} is defined. + */ + all?: boolean, + /** + * Filter query to a certain table/view/schema owner. + * + * @remarks + * Overrides {@link BuildQueryOptions.all} when defined. + * Alias of {@link BuildQueryOptions.schema} with higher precedence. + */ + owner?: string, + /** + * @remarks + * Alias of {@link BuildQueryOptions.owner} with lower precedence. + */ + schema?: string, +} + export interface DiscoveredPrimaryKeys { owner: string | null; tableName: string; @@ -205,6 +230,17 @@ export interface Connector { getDefaultIdType(): object; isRelational(): boolean; + buildQuerySchemas?(options: BuildQueryOptions): string; + buildQueryTables?(options: BuildQueryOptions): string; + buildQueryViews?(options: BuildQueryOptions): string; + buildQueryColumns?(owner: string | null, table: string): string; + buildQueryForeignKeys?(owner?: string, table?: string): string; + buildQueryExportedForeignKeys?(owner?: string, table?: string): string; + + setDefaultOptions?(options: Options): Options; + setNullableProperty?(property: PropertyDefinition): void; + getDefaultSchema?(options?: Options): string | undefined; + /** * Discover existing database tables. * diff --git a/types/datasource.d.ts b/types/datasource.d.ts index a60ddae62..c7e879e2e 100644 --- a/types/datasource.d.ts +++ b/types/datasource.d.ts @@ -121,8 +121,8 @@ export declare class DataSource extends EventEmitter { * Log benchmarked message. * * @remarks - * This property is assigned to the defined to the attached connector's - * {@link Connector.log | log} class member. + * This class method is defined as the attached connector's + * {@link Connector.log | log} class method. * * @param sql * @param t Start time @@ -137,8 +137,14 @@ export declare class DataSource extends EventEmitter { defineOperation(name: string, options: OperationOptions, fn: Function): void; + /** + * @deprecated For LoopBack 3 only. + */ enableRemote(operation: string): void; + /** + * @deprecated For LoopBack 3 only. + */ disableRemote(operation: string): void; /** @@ -202,6 +208,38 @@ export declare class DataSource extends EventEmitter { */ isRelational(): boolean | undefined; + /** + * Freeze the DataSource. + * + * @remarks + * Behaviour is connector-dependent. + * + * This may be used to continuously add artifacts to datasource until it is + * frozen, but historically it is not really used in LoopBack. + * + * If implemented by the connector, the following connector methods are + * called: + * + * - {@link Connector.freezeDataSource} + * - {@link Connector.freezeSchema} + * + * This is typically called by other DataSource methods (including but not + * limited to): + * + * - {@link DataSource.automigrate} + * - {@link DataSource.autoupdate} + * - {@link DataSource.discoverModelDefinitions} + * - {@link DataSource.discoverModelDefinitionsSync} + * - {@link DataSource.discoverModelProperties} + * - {@link DataSource.discoverModelPropertiesSync} + * - {@link DataSource.discoverPrimaryKeys} + * - {@link DataSource.discoverPrimaryKeysSync} + * - {@link DataSource.discoverForeignKeys} + * - {@link DataSource.discoverForeignKeysSync} + * - {@link DataSource.discoverExportedForeignKeys} + * - {@link DataSource.discoverExportedForeignKeysSync} + * - {@link DataSource.isActual} + */ freeze(): void; /** @@ -241,6 +279,10 @@ export declare class DataSource extends EventEmitter { /** * Retrieve the ID property name for a model. * + * @remarks + * This method will only return the first ID from models with multiple IDs. + * Use {@link DataSource.idNames} instead. + * * @param modelName Target model name * @returns ID property name */ @@ -266,7 +308,7 @@ export declare class DataSource extends EventEmitter { * Define a foreign key to another model. * * @remarks - * If the attached {@link Connector} did not implement + * If the attached {@link Connector} does not implement * {@link Connector.defineForeignKey}, this function will fallback to defining * a regular property. Furthermore, responsibility of properly implementing * this feature depends on the connector's implementation. This means a @@ -288,7 +330,7 @@ export declare class DataSource extends EventEmitter { */ createModel( name: string, - properties?: AnyObject, + properties?: PropertyDefinition, options?: ModelSettings, ): T; diff --git a/types/model.d.ts b/types/model.d.ts index 898a95843..b67707259 100644 --- a/types/model.d.ts +++ b/types/model.d.ts @@ -14,7 +14,7 @@ import {ModelUtilsOptions} from './model-utils'; */ export type PropertyType = | 'GeoPoint' - | 'Point' + | 'Point' // Legacy carry-over from JugglingDB. Alias of `GeoPoint`.` | string | Function | {[property: string]: PropertyType}; @@ -35,7 +35,9 @@ export interface PropertyDefinition extends AnyObject { dataLength?: number; dataPrecision?: number; dataScale?: number; - nullable?: boolean; + nullable?: 'Y' | 'N'; + // PostgreSQL-specific? + autoIncrement?: boolean; } /** @@ -96,6 +98,36 @@ export interface ModelSettings extends AnyObject, ModelUtilsOptions { */ forceId?: boolean; + /** + * @remarks + * Defaults to `true`. + */ + idInjection?: boolean; + + plural?: string; + + http?: { + path?: string; + } + + /** + * @remarks + * Alias of {@link ModelSettings.super}. Takes lower precedence. + */ + base?: ModelBaseClass; + /** + * @remarks + * Alias of {@link ModelSettings.base}. Takes lower precedence. + */ + super?: ModelBaseClass; + excludeBaseProperties?: string[]; + + /** + * Indicates if the {@link ModelBaseClass | Model} is attached to the DataS + * @internal + */ + unresolved?: boolean; + indexes?: { [indexJugglerName: string]: { name: string; @@ -190,9 +222,9 @@ export interface ModelSettings extends AnyObject, ModelUtilsOptions { * @deprecated Use {@link ModelSettings.hiddenProperties} instead. */ hidden?: string[]; - automaticValidation?: boolean, - updateOnLoad?: boolean, - validateUpsert?: boolean, + automaticValidation?: boolean; + updateOnLoad?: boolean; + validateUpsert?: boolean; /** * Sets if an {@link Error} should be thrown when no instance(s) were found @@ -205,7 +237,9 @@ export interface ModelSettings extends AnyObject, ModelUtilsOptions { * - {@link DataAccessObject.removeById}/{@link DataAccessObject.destroyById}/{@link DataAccessObject.deleteById} * - {@link DataAccessObject.remove}/{@link DataAccessObject.delete}/{@link DataAccessObject.destroy} */ - strictDelete?: boolean, + strictDelete?: boolean; + + updatOnly?: boolean; // Postgres-specific defaultIdSort?: boolean | 'numericIdOnly'; @@ -247,6 +281,12 @@ export declare class ModelDefinition extends EventEmitter implements Schema { toJSON(forceRebuild?: boolean): AnyObject; } +export interface ModelBaseClassOptions { + applySetters?: boolean; + applyDefaultValues?: boolean; + strict?: boolean; +} + /** * Base class for LoopBack 3.x models */ @@ -254,8 +294,17 @@ export declare class ModelBase { static dataSource?: DataSource; static modelName: string; static definition: ModelDefinition; + static hideInternalProperties?: boolean; static readonly base: typeof ModelBase; + /** + * Initializes the model instance with a list of properties. + * + * @param data The data object + * @param options Instation options + */ + private _initProperties(data: object, options: ModelBaseClass): void; + /** * Extend the model with the specified model, properties, and other settings. * For example, to extend an existing model: @@ -292,6 +341,11 @@ export declare class ModelBase { */ static getPropertyType(propName: string): string | null; + /** + * {@inheritDoc ModelBaseClass.getPropertyType} + */ + getPropertyType: ModelBaseClass['getPropertyType']; + /** * Checks if property is hidden. * @param {string} propertyName Property name @@ -351,7 +405,11 @@ export declare class ModelBase { * If true, then protected properties should not be brought out. * @returns {object} returns Plain JSON object */ - toObject(options?: Options): AnyObject; + toObject(options?: { + onlySchema?: boolean; + removeHidden?: boolean; + removeProtected?: boolean; + }): AnyObject; /** * Define a property on the model. @@ -462,6 +520,24 @@ export declare class ModelBase { * @end */ static clearObservers(operation: string): void; + + getMergePolicy(options: { + configureModelMerge: boolean | object + }): { + description?: {replace: boolean}; + properties?: {patch: boolean}; // Only referenced in "legacy built-in merge policy" + options?: {path: boolean}; + hidden?: {replace: boolean}; + protected?: {replace: boolean}; + indexes?: {patch: boolean}; + methods?: {patch: boolean}; + mixins?: {patch: boolean}; + relations?: {patch: boolean}; + scope?: {replace: boolean}; + acls?: {rank: boolean}; + __delete?: boolean | null; + __default?: {replace: boolean}; + } } export type ModelBaseClass = typeof ModelBase; diff --git a/types/test-suite.d.ts b/types/test-suite.d.ts new file mode 100644 index 000000000..450ef21e5 --- /dev/null +++ b/types/test-suite.d.ts @@ -0,0 +1,34 @@ +/** + * Opt-out unless stated otherwise + */ +export interface ConnectorCapabilities { + ilike?: boolean; + nilike?: boolean; + supportsArrays?: boolean; + supportInq?: boolean; + geoPoint?: boolean; + supportForceId?: boolean; + nestedProperty?: boolean; + nullDataValueExists?: boolean; + supportPagination?: boolean; + adhocSort?: boolean; // Mostly opt-out + supportOrOperator?: boolean; + cloudantCompatible?: boolean; + ignoreUndefinedConditionValue?: boolean; + supportTwoOrMoreInq?: boolean; + refuseDuplicateInsert?: boolean; + reportDeletedCount?: boolean; + deleteWithOtherThanId?: boolean; + updateWithOtherThanId?: boolean; + supportStrictDelete?: boolean; + atomicUpsertWithWhere?: boolean; + replaceOrCreateReportsNewInstance?: boolean; + updateWithoutId?: boolean; // This is a separate entry from `supportUpdateWithoutId` + supportUpdateWithoutId?: boolean; + supportInclude?: boolean; // Opt-in + canExpire?: boolean; // KVAO test suite + canIterateKeys?: boolean; // KAVO test suite + canIterateLargeKeySets?: boolean // KVAO test suite + canQueryTtl?: boolean // KVAO test suite + ttlPrecision?: number; // KVAO test suite; Defaults to `10`. +} From 884776b5e55836be87824597333104e7a19589dc Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Sat, 9 Apr 2022 22:36:57 +0800 Subject: [PATCH 21/26] feat: misc updates Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/connector.d.ts | 315 ++++++++++++++++++++++++-------- types/connectors/transient.d.ts | 24 +++ types/dao.d.ts | 2 + types/datasource.d.ts | 144 +++++++++------ types/kvao.d.ts | 6 +- types/model-utils.d.ts | 6 +- types/model.d.ts | 152 +++++++++------ types/observer-mixin.d.ts | 38 ++-- 8 files changed, 477 insertions(+), 210 deletions(-) create mode 100644 types/connectors/transient.d.ts diff --git a/types/connector.d.ts b/types/connector.d.ts index 120e079d6..b021ef907 100644 --- a/types/connector.d.ts +++ b/types/connector.d.ts @@ -3,29 +3,78 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import { AnyObject } from 'strong-globalize/lib/config'; +import { AnyObject } from './common'; import {Callback, DataSource, Filter, ModelBase, ModelBaseClass, ModelDefinition, ModelProperties, Options, PromiseOrVoid, PropertyDefinition, PropertyType, Schema, Where} from '..'; +import { DataAccessObject } from './dao'; +import { ObserverMixin, OperationHookContext } from './observer-mixin'; +import { IndexDefinition, ModelData } from './model'; +import { ModelUtilsOptions } from './model-utils'; export interface ConnectorSettings extends Options { name?: string; /** * Overrides {@link ConnectorSettings.adapter} if defined. */ - connector?: ConnectorStatic | string; + connector?: ConnectorExport | string; /** * @deprecated Use {@link ConnectorSettings.connector} instead. */ - adapter?: ConnectorStatic | string; - database: string; + adapter?: ConnectorExport | string; + database?: string; connectionTimeout?: number; maxOfflineRequests?: number; lazyConnect?: boolean; debug?: boolean; + url?: string; + username?: string; + password?: string; + // Postgres-specific defaultIdSort?: boolean | 'numericIdOnly'; onError?: (err: Error | unknown) => unknown | 'ignore'; // END Postgres-specific + + // MySQL-specific + createDatabase?: boolean; + /** + * @defaultValue `'utf8_general_ci'` + **/ + charset?: string; + collation?: string; + /** + * @defaultValue `false` + **/ + supportBigNumbers?: boolean; + /** + * @defaultValue `'local'` + **/ + timezone?: string; + /** + * @defaultValue `10` + **/ + connectionLimit?: number; + // END MySQL-specific + + // MSSQL-specific + tableNameID?: string + // END MSSQL-specific + + // CouchDB2-specific + Driver?: object // CouchDB driver + // END CouchDB2-specific + + // Cassandra-specific + keyspace?: string; + createKeyspace?: boolean; + replication?: { + /** + * @example `'SimpleStrategy'` + */ + class: string, + replication_factor: number, + } + // END Cassandra-specific } export interface IDPropertiesDiscoveryOptions { @@ -42,7 +91,6 @@ export interface DiscoveryScopeOptions { } export interface SchemaDiscoveryOptions { - /** * Sets if all owners are included. */ @@ -55,19 +103,19 @@ export interface SchemaDiscoveryOptions { /** * Sets if the database foreign key column names should be transformed. - * + * * @remarks * Used by the default built-in {@link NameMapper} implementation to transform * the database column names to use camel-case, LoopBack's default naming * conventions. - * + * * @defaultValue `false` */ disableCamelCase?: boolean; /** * A custom database table name, model, and foreign key transformer. - * + * * @remarks * If `null`, no transform is performed. * If `undefined`, default built-in transformer is used. @@ -76,7 +124,7 @@ export interface SchemaDiscoveryOptions { /** * Sets if associations/relations should be navigated. - * + * * @remarks * Alias of {@link SchemaDiscoveryOptions.relations} */ @@ -84,7 +132,7 @@ export interface SchemaDiscoveryOptions { /** * Sets if associations/relations should be navigated. - * + * * @remarks * Alias of {@link SchemaDiscoveryOptions.associations}. */ @@ -99,7 +147,7 @@ export type NameMapper = (type: 'table' | 'model' | 'fk' | string, name: string) export interface BuildQueryOptions { /** * Build a query to search tables/views/schemas from any owner. - * + * * @remarks * Ignored when {@link BuildQueryOptions.owner} or * {@link BuildQueryOptions.schema} is defined. @@ -107,7 +155,7 @@ export interface BuildQueryOptions { all?: boolean, /** * Filter query to a certain table/view/schema owner. - * + * * @remarks * Overrides {@link BuildQueryOptions.all} when defined. * Alias of {@link BuildQueryOptions.schema} with higher precedence. @@ -175,50 +223,86 @@ export interface ConnectorHookBeforeExecuteContext { export interface ConnectorHookAfterExecuteContext extends ConnectorHookBeforeExecuteContext { res: Record; } + +interface ConnectorMetadata { + types: string[]; + defaultIdType: object; + isRelational: boolean; + schemaForSettings: Record; +} + /** * Connector from `loopback-connector` module */ export interface Connector { name: string; // Name/type of the connector + DataAccessObject?: DataAccessObject; + /** - * @internal + * The {@link DataSource} which the Connector is/will attach to. + * + * @remarks + * In most cases, the Connector does not need to pre-populate this and this + * will be set by the {@link DataSource} itself. + * + * By pre-populating this field before initialisation, it is assumed that the + * Connector would handle setting up the DataSource. */ - _models?: Record; + dataSource?: DataSource; + + relational: boolean; + + // private _models?: Record; + // private _metadata?: ConnectorMetadata; + connect?(callback?: Callback): PromiseOrVoid; // Connect to the underlying system disconnect?(callback?: Callback): PromiseOrVoid; // Disconnect from the underlying system /** * Ping the underlying connector to test the connections. - * + * * @remarks * Unlike {@link DataSource.ping}, if no callback is provided, a * {@link Promise} return value is not guaranteed. - * + * * @param callback Callback function - * @returns a {@link Promise} or `void` + * @returns `Promise` if no callback is passed. Otherwise, `void` */ ping?(callback?: Callback): PromiseOrVoid; // Ping the underlying system execute?(...args: any[]): Promise; /** * Get the connector's types collection. - * + * * @remarks * For example, ['db', 'nosql', 'mongodb'] would be represent a datasource of * type 'db', with a subtype of 'nosql', and would use the 'mongodb' connector. * * Alternatively, ['rest'] would be a different type altogether, and would have * no subtype. - * + * * @returns The connector's type collection. */ getTypes?(): string[]; define?(def: {model: ModelBaseClass, properties: PropertyDefinition, settings: ModelDefinition['settings']}): void; + all?(model: string, filter: Filter, options: ModelUtilsOptions | null, cb: Callback): void; + findAll?: Connector['all']; + find?(model: string, value: unknown, options: ModelUtilsOptions, cb: Callback): void; + count?(model: string, where: Where, options: ModelUtilsOptions, cb: Callback): void; + + serializeObject?(obj: object): unknown; + escapeObject?(obj: object): unknown; + escapeValue?(obj: object): unknown | null; + getTableStatus?(model: string, cb: (err: any, fields?: PropertyDefinition, indexes?: IndexDefinition) => void): void; + + fromDatabase?(model: string, rowData: object): ModelData; + fromRow?: Connector['fromDatabase']; + /** * Define a property on the target model. - * + * * @param model Name of model * @param prop Name of property * @param params Property settings @@ -226,7 +310,7 @@ export interface Connector { defineProperty?(model: string, prop: string, params: PropertyDefinition): void; defineForeignKey?(modelName: string, key: string, foreignModelName: string, cb: Callback): void; defineForeignKey?(modelName: string, key: string, cb: Callback): void; - + getDefaultIdType(): object; isRelational(): boolean; @@ -236,14 +320,102 @@ export interface Connector { buildQueryColumns?(owner: string | null, table: string): string; buildQueryForeignKeys?(owner?: string, table?: string): string; buildQueryExportedForeignKeys?(owner?: string, table?: string): string; + buildColumnDefinitions?(model: string): string; + propertiesSQL: Connector['buildColumnDefinitions']; // Postgresql-specific - setDefaultOptions?(options: Options): Options; + createTable?(model: string, cb: Callback): void; + alterTable?(model: string, actualFields: PropertyDefinition[], actualIndexes: IndexDefinition[], cb: Callback): void; + dropTable?(model: string, cb: Callback): void; + /** + * Generate SQL statement to add and remove indexes to sync with database. + * + * @param model + * @param actualIndexes + */ + addIndexes?(model: string, actualIndexes: IndexDefinition[]): void; + showIndexes?(model: string, cb: Callback): void; + showFields?(model: string, cb: Callback): void; + /** + * + * @param model + * @param actualFields + * @returns Array of SQL statement + */ + getColumnsToAdd?(model: string, actualFields: PropertyDefinition[]): string[]; + getDropColumns?(model: string, actualFields: PropertyDefinition[]): string; + getColumnsToDrop?(model: string, actualFields: PropertyDefinition[]): string[]; + + /** + * + * @remarks + * This is a thin wrapper around {@link Connector.getColumnsToAdd}. + * + * @param model + * @param actualFields + * @returns SQL statement + */ + getAddModifyColumns?(model: string, actualFields: PropertyDefinition[]): string; + + /** + * Mutate `options` parameter to populate missing options with default values + * + * @param options Connector-specific options to be populated + */ + setDefaultOptions?(options: Options): Options | void; setNullableProperty?(property: PropertyDefinition): void; - getDefaultSchema?(options?: Options): string | undefined; + + /** + * Normalize the arguments + * + * @returns Normalized arguments + */ + getArgs?(table: string, options?: Options, cb?: Callback): { + schema: string, + owner: string, + table: string, + options?: Options, + cb: Callback, + }; + + getArgs?(table: string, cb?: Callback): { + schema: string, + owner: string, + table: string, + options?: Options, + cb: Callback, + }; + + /** + * Modify the SQL statement to include pagination clauses. + * + * @param sql The SQL statement to be modified + * @param orderBy The property name by which results are ordered + * @param options Options for pagination + */ + paginateSQL?(sql: string, orderBy: string, options: Pick): string; + + /** + * Discover the default target database schema. + * + * @param options + * @returns Name of the database schema that will be targeted by default + */ + getDefaultSchema?(options?: Options): string | undefined | ''; + + /** + * Retrieve the default target database schema. + * + * @remarks + * This is based on locally-available information, such as + * {@link ConnectorSettings['database']}. + * + * @returns Name of the database schema that will be targeted by default + */ + getDefaultSchemaName?(): string | ''; /** * Discover existing database tables. - * + * * @param options Discovery options * @param cb Callback function */ @@ -257,12 +429,13 @@ export interface Connector { /** * Discover properties for a given model. - * + * * @param modelName Target table/view name * @param options Discovery options * @param cb Callback function */ discoverModelProperties?(modelName: string, options: DiscoveryScopeOptions, cb: Callback): Promise; + /** * {@inheritDoc Connector.discoverModelProperties} * @deprecated @@ -271,7 +444,7 @@ export interface Connector { /** * Discover primary keys for a given owner/model name. - * + * * @param modelName Target model name * @param options Discovery options * @param cb Callback function @@ -286,7 +459,7 @@ export interface Connector { /** * Discover foreign keys for a given owner/model name. - * + * * @param modelName Target model name * @param options Discovery options * @param cb Callback function @@ -302,7 +475,7 @@ export interface Connector { * Retrieve a description of the foreign key columns that reference the given * table's primary key columns (i.e. The foreign keys exported by a table), * ordered by `fkOwner`, `fkTableName`, and `keySeq`. - * + * * @param modelName Target model name * @param options Discovery options * @param cb Callback function @@ -317,19 +490,19 @@ export interface Connector { /** * Discover schema from a given table name / view name. - * + * * @param tableName Target table name * @param options Discovery options * @param cb Callback function */ - discoverSchemas(tableName: string, options: SchemaDiscoveryOptions, cb: Callback): Promise; + discoverSchemas?(tableName: string, options: SchemaDiscoveryOptions, cb: Callback): Promise; /** * Check whether or not migrations are required for the database schema to match * the model definitions attached to the {@link DataSource}. - * + * * @param models Name of models to check. If not defined, all models are checked. - * @param cb + * @param cb */ isActual?(models?: string | string[], cb?: Callback): void; @@ -340,7 +513,7 @@ export interface Connector { /** * {@inheritDoc Connector.freezeDataSource} - * + * * @remarks * This is kept for backwards-compatibility with JugglingDB connectors. * Connectors should implement {@link Connector.freezeDataSource}. @@ -350,69 +523,59 @@ export interface Connector { /** * Normalize connector-specific return data into standardised Juggler context * data. - * + * * @remarks * Depending on the connector, the database response can contain information * about the updated record(s). This object usually has a database-specific * structure and does not match model properties. For example, MySQL returns * `OkPacket: {fieldCount, affectedRows, insertId, ... , changedRows}`. - * + * * The return value is normalised data. - * + * * If the connector DDL and DML functions map directly to a hash-map of * model properties and their values, this function does not need to be * implemented. - * - * @param context - * @param dbResponse + * + * @param context + * @param dbResponse */ generateContextData?(context: Context, dbResponse: unknown): Context; - [property: string]: any; // Other properties that vary by connectors + generateUniqueId?(modelName?: string): unknown | null; + generateValueByColumnType?(idType: object): unknown; + getMetadata(): ConnectorMetadata; } -export interface BuiltConnector extends Connector { +export declare class SQLConnector { + +} + +/** + * A {@link Connector} after being attached to a {@link DataSource}. + * + * @remarks + * The {@link DataSource} manipulates the {@link Connector} during attachment + * (e.g. when passed in {@link DataSource.constructor}). This type defines those + * mutations. + */ +export interface BuiltConnector extends ObserverMixin { + // DataSource dataSource: DataSource; log: DataSource['log']; logger(query: string, start: number): (query: string) => void; } /** - * Base connector class - * - * @internal + * The interface that a connector must implement. + * + * @remarks + * As {@link Connector} is an interface and not a class, it's not possible to + * represent static methods. Hence, this is a workaround that has been generally + * accepted by the TypeScript community to define the interface of a + * prototype-based class. */ -export declare class ConnectorBase implements Connector { - name: string; // Name/type of the connector; - dataSource?: DataSource; - connect(callback?: Callback): PromiseOrVoid; // Connect to the underlying system - disconnect(callback?: Callback): PromiseOrVoid; // Disconnect from the underlying system - ping(callback?: Callback): PromiseOrVoid; // Ping the underlying system - execute?(...args: any[]): Promise; - - /** - * Initialize the connector against the given data source - * - * @param {DataSource} dataSource The dataSource - * @param {Function} [callback] The callback function - */ - static initialize(dataSource: DataSource, callback?: Callback): void; - - constructor(settings?: Options); - [property: string]: any; - _models?: Record; - getDefaultIdType(): object; - isRelational(): boolean; - discoverSchemas(tableName: string, options: SchemaDiscoveryOptions, cb: Callback): Promise; -} - -export declare interface ConnectorStatic { - initialize(this: DataSource, callback: Callback): void; - new (settings: ConnectorSettings): Connector; +export declare interface ConnectorExport { + initialize: ConnectorInitialize; } -export declare class Memory extends ConnectorBase {} - -export declare class KeyValueMemoryConnector extends ConnectorBase {} - -export declare class Transient extends ConnectorBase {} +export type ConnectorInitialize = (this: DataSource, callback: Callback) => void; diff --git a/types/connectors/transient.d.ts b/types/connectors/transient.d.ts new file mode 100644 index 000000000..634f52c95 --- /dev/null +++ b/types/connectors/transient.d.ts @@ -0,0 +1,24 @@ +import { Callback } from '../common'; +import {ConnectorInitialize, Connector, SchemaDiscoveryOptions, ConnectorSettings} from '../connector' +import { DataAccessObject } from '../dao'; +import { DataSource } from '../datasource'; +import { ModelBase, Schema } from '../model'; + +export let initialize: ConnectorInitialize; + +export interface TransientConnectorSettings extends ConnectorSettings { + generateId?: TransientConnectorGenerateId, + defaultIdType?: object, +} + +export type TransientConnectorGenerateId = (model: string, data?: unknown, idName?: string) => string; + +// export declare class Transient implements Connector { +// isTransaction: boolean; +// constructor(m: Transient | null, settings?: ConnectorSettings); +// onTransactionExec?: Callback; +// generateId: TransientConnectorGenerateId; +// flush(action: unknown, result?: T, callback?: Callback): void; +// exec(callback: Callback): void; +// transaction(): Transient; +// } diff --git a/types/dao.d.ts b/types/dao.d.ts index 49352348c..6942fb58f 100644 --- a/types/dao.d.ts +++ b/types/dao.d.ts @@ -1,5 +1,7 @@ import { Options } from ".."; +export declare class DataAccessObject {} + export interface DaoDmlOptions extends Options { validate?: boolean; notify?: boolean; diff --git a/types/datasource.d.ts b/types/datasource.d.ts index c7e879e2e..2b263a174 100644 --- a/types/datasource.d.ts +++ b/types/datasource.d.ts @@ -4,7 +4,7 @@ // License text available at https://opensource.org/licenses/MIT import {AnyObject, Callback, Options} from './common'; -import {Connector} from './connector'; +import {BuiltConnector, Connector} from './connector'; import { ModelBaseClass, ModelBuilder, @@ -13,7 +13,8 @@ import { } from './model'; import {EventEmitter} from 'events'; import {IsolationLevel, Transaction} from './transaction-mixin'; -import { ColumnMetadata, ConnectorSettings, ModelBase, ModelSettings } from '..'; +import { ColumnMetadata, ConnectorSettings, ModelBase, ModelSettings, PersistedModel } from '..'; +import { PersistedModelClass } from './persisted-model'; export type OperationOptions = { accepts: string[], @@ -28,8 +29,20 @@ export type DiscoverAndBuildModelsOptions = Options & { base: ModelBaseClass, } +export function DataSource(name: string, settings?: ConnectorSettings, modelBuilder?: ModelBuilder): DataSource; + +export function DataSource(settings?: ConnectorSettings, modelBuilder?: ModelBuilder): DataSource; + +export function DataSource( + connectorModule: CT, + settings?: Omit, + modelBuilder?: ModelBuilder, +): DataSource; + /** * LoopBack models can manipulate data via the DataSource object. + * + * @remarks * Attaching a `DataSource` to a `Model` adds instance methods and static methods to the `Model`. * * Define a data source to persist model data. @@ -57,21 +70,21 @@ export type DiscoverAndBuildModelsOptions = Options & { * }); * ``` * @class DataSource - * @param {String} [name] Optional name for datasource. + * @param name Optional name for datasource. * @options {Object} settings Database-specific settings to establish connection (settings depend on specific connector). * The table below lists a typical set for a relational database. - * @property {String} connector Database connector to use. For any supported connector, can be any of: + * @property connector Database connector to use. For any supported connector, can be any of: * * - The connector module from `require(connectorName)`. * - The full name of the connector module, such as 'loopback-connector-oracle'. * - The short name of the connector module, such as 'oracle'. * - A local module under `./connectors/` folder. - * @property {String} host Database server host name. - * @property {String} port Database server port number. - * @property {String} username Database user name. - * @property {String} password Database password. - * @property {String} database Name of the database to use. - * @property {Boolean} debug Display debugging information. Default is false. + * @property host Database server host name. + * @property port Database server port number. + * @property username Database user name. + * @property password Database password. + * @property database Name of the database to use. + * @property debug Display debugging information. Default is false. * * The constructor allows the following styles: * @@ -88,44 +101,44 @@ export type DiscoverAndBuildModelsOptions = Options & { * - new DataSource(connectorModule, {name: 'myDataSource}) * - new DataSource(connectorModule) */ -export declare class DataSource extends EventEmitter { +export declare class DataSource extends EventEmitter { name: string; settings: ConnectorSettings; initialized?: boolean; connected?: boolean; connecting?: boolean; - + /** * {@inheritDoc DataSource.connector} * @deprecated Use {@link DataSource.connector} instead. */ - adapter?: Connector; + adapter?: BuiltConnector & CT; /** * Connector instance. */ - connector?: Connector; + connector?: BuiltConnector & CT; modelBuilder: ModelBuilder; - models: Record; - + models: Record; + definitions: {[modelName: string]: ModelDefinition}; - + DataAccessObject: AnyObject & {prototype: AnyObject}; - + pendingConnectCallbacks?: Callback[]; /** * Log benchmarked message. - * + * * @remarks * This class method is defined as the attached connector's * {@link Connector.log | log} class method. - * - * @param sql - * @param t Start time + * + * @param sql + * @param t Start time */ private log(sql: string, t: number): void; @@ -136,7 +149,7 @@ export declare class DataSource extends EventEmitter { operations(): Record; defineOperation(name: string, options: OperationOptions, fn: Function): void; - + /** * @deprecated For LoopBack 3 only. */ @@ -149,7 +162,7 @@ export declare class DataSource extends EventEmitter { /** * Default global maximum number of event listeners. - * + * * @remarks * This default can be overriden through * {@link ConnectorSettings.maxOfflineRequests}. @@ -157,7 +170,7 @@ export declare class DataSource extends EventEmitter { static DEFAULT_MAX_OFFLINE_REQUESTS: number; /** - * A hash-map of the different relation types. + * A map of the different relation types. */ static relationTypes: Record; @@ -167,7 +180,7 @@ export declare class DataSource extends EventEmitter { constructor( connectorModule: Connector, - settings?: ConnectorSettings, + settings?: Omit, modelBuilder?: ModelBuilder, ); @@ -177,10 +190,21 @@ export declare class DataSource extends EventEmitter { private _setupConnector(); private mixin(ModelCtor: T): T - + + /** + * Set up the data access functions from the data source. Each data source + * will expose a data access object (DAO), which will be mixed into the + * modelClass. + * + * @param modelClass The model class that will receive DAO mixins. + * @param settings The settings object; typically allows any settings that + * would be valid for a typical Model object. + */ + setupDataAccess(modelClass: ModelBaseClass, options?: ModelSettings): asserts modelClass is PersistedModelClass; + /** * Get the maximum number of event listeners - * + * * @remarks * Defaults to {@link DataSource.DEFAULT_MAX_OFFLINE_REQUESTS} if not explicitly * configured in {@link ConnectorSettings.maxOfflineRequests}. @@ -190,7 +214,7 @@ export declare class DataSource extends EventEmitter { // Reason for deprecation is not clear. /** * {@inheritDoc Connector.getTypes} - * @deprecated + * @deprecated Use {@link DataSource.supportTypes} instead. */ getTypes(): string[]; @@ -202,7 +226,7 @@ export declare class DataSource extends EventEmitter { /** * Returns if the attached connector is relational. - * + * * @returns If the attached connector is relational; `undefined` if no * connector is attached. */ @@ -210,22 +234,22 @@ export declare class DataSource extends EventEmitter { /** * Freeze the DataSource. - * + * * @remarks * Behaviour is connector-dependent. - * + * * This may be used to continuously add artifacts to datasource until it is * frozen, but historically it is not really used in LoopBack. - * + * * If implemented by the connector, the following connector methods are * called: - * + * * - {@link Connector.freezeDataSource} * - {@link Connector.freezeSchema} - * + * * This is typically called by other DataSource methods (including but not * limited to): - * + * * - {@link DataSource.automigrate} * - {@link DataSource.autoupdate} * - {@link DataSource.discoverModelDefinitions} @@ -244,7 +268,7 @@ export declare class DataSource extends EventEmitter { /** * Return the table name for the specified `modelName`. - * + * * @param modelName Target model name * @returns The table name */ @@ -252,7 +276,7 @@ export declare class DataSource extends EventEmitter { /** * Retrieve the column name for the specified `modelName` and `propertyName`. - * + * * @param modelName Target model name * @param propertyName Target property name * @returns The column name @@ -261,15 +285,15 @@ export declare class DataSource extends EventEmitter { /** * Retrieve the column names for the specified `modelName`. - * + * * @param modelName Target model name * @returns Column names */ - columnNames(modelName: string): string; + columnNames(modelName: string): string[]; /** * Retrieve coulmn metadata for the specified `modelName` and `propertyName`. - * + * * @param modelName Target model name * @param propertyName Target property name * @returns Column metadata @@ -278,11 +302,11 @@ export declare class DataSource extends EventEmitter { /** * Retrieve the ID property name for a model. - * + * * @remarks * This method will only return the first ID from models with multiple IDs. * Use {@link DataSource.idNames} instead. - * + * * @param modelName Target model name * @returns ID property name */ @@ -290,7 +314,7 @@ export declare class DataSource extends EventEmitter { /** * Retrieve the ID property names sorted by their index. - * + * * @param modelName Target model name * @returns Property names for IDs */ @@ -298,7 +322,7 @@ export declare class DataSource extends EventEmitter { /** * Retrieve the ID property definition. - * + * * @param modelName Target model name * @returns The ID property definition */ @@ -306,7 +330,7 @@ export declare class DataSource extends EventEmitter { /** * Define a foreign key to another model. - * + * * @remarks * If the attached {@link Connector} does not implement * {@link Connector.defineForeignKey}, this function will fallback to defining @@ -314,7 +338,7 @@ export declare class DataSource extends EventEmitter { * this feature depends on the connector's implementation. This means a * database-level foreign key is not guaranteed. Please refer to the * respective connector's documentation. - * + * * @param className The model name that owns the key * @param key Name of key field * @param foreignClassName Foreign model name @@ -344,12 +368,20 @@ export declare class DataSource extends EventEmitter { * Look up a model class by name * @param modelName Model name * @param forceCreate A flag to force creation of a model if not found + * + * @deprecated */ getModel( modelName: string, forceCreate?: boolean, ): ModelBaseClass | undefined; + /** + * @param name Model name + * @returns Definition for the Model of that name + */ + getModelDefinition(name: string): ModelDefinition; + /** * Remove a model from the registry. * @@ -365,11 +397,11 @@ export declare class DataSource extends EventEmitter { /** * Attach an existing model to a data source. - * + * * @remarks * This will mixin all of the data access object functions (DAO) into your * modelClass definition. - * + * * @param modelClass The model constructor that will be * enhanced by DataAccessObject mixins. */ @@ -524,8 +556,16 @@ export declare class DataSource extends EventEmitter { tableName: string, options: Options, callback: Callback, - ): void; + ): void; + /** + * Introspect a JSON object and build a model class. + * + * @param name Name of the model + * @param json The JSON object representing a model instance + * @param options Options + * @returns A {@link Model} class + */ buildModelFromInstance( modelName: string, jsonObject: AnyObject, @@ -553,7 +593,7 @@ export declare class DataSource extends EventEmitter { /** * An alias for {@link DataSource.disconnect} to allow this datasource to be * an LB4 life-cycle observer - * + * * @remarks * A `.start()` equivalent was deliberately not provided as the logic for * establishing connection(s) is more complex and is usually statred @@ -563,7 +603,7 @@ export declare class DataSource extends EventEmitter { /** * Ping the underlying connector to test the connections. - * + * * @remarks * If {@link Connector.ping} is not implemented, * {@link Connector.discoverModelProperties} is used instead. Otherwise, an diff --git a/types/kvao.d.ts b/types/kvao.d.ts index 69e9ab056..90932b21a 100644 --- a/types/kvao.d.ts +++ b/types/kvao.d.ts @@ -17,19 +17,19 @@ export declare interface KeyValueAccessObject set(key: string, value: unknown, callback?: Callback): Promise | undefined; set(key: string, value: unknown, options?: Options, callback?: Callback): Promise | undefined; - + expire(key: string, ttl: number, callback?: Callback): Promise | undefined; expire(key: string, ttl: number, options?: Options, callback?: Callback): Promise | undefined; ttl(key: string, callback?: Callback): Promise | undefined; ttl(key: string, options?: Options, callback?: Callback): Promise | undefined; - + iterateKeys(options: Options): Promise | undefined; iterateKeys(filter: MatchFilter, options: Options): Promise | undefined; keys(callback?: Callback): Promise | undefined; keys(options?: Options, callback?: Callback): Promise | undefined; keys(filter?: MatchFilter, options?: Options, callback?: Callback): Promise | undefined; - + getConnector(): CT; } diff --git a/types/model-utils.d.ts b/types/model-utils.d.ts index 9e20a5630..60afb7384 100644 --- a/types/model-utils.d.ts +++ b/types/model-utils.d.ts @@ -4,7 +4,7 @@ import type {ModelSettings} from './model'; /** * Settings made available by {@link ModelUtils} mixin. - * + * * @remarks * When the mixin is applied, these options can be set in * {@link DataAccessObject} query method-level options, {@link ModelSettings}, @@ -19,4 +19,8 @@ export interface ModelUtilsOptions { maxDepthOfQuery?: number; maxDepthOfData?: number; prohibitHiddenPropertiesInQuery?: boolean; + + // Cassandra-specific + clusteringKeys?: string[] + // END Cassandra-specific } diff --git a/types/model.d.ts b/types/model.d.ts index b67707259..fce8e6126 100644 --- a/types/model.d.ts +++ b/types/model.d.ts @@ -14,7 +14,7 @@ import {ModelUtilsOptions} from './model-utils'; */ export type PropertyType = | 'GeoPoint' - | 'Point' // Legacy carry-over from JugglingDB. Alias of `GeoPoint`.` + | 'Point' // Legacy carry-over from JugglingDB. Alias of `GeoPoint`. | string | Function | {[property: string]: PropertyType}; @@ -82,6 +82,31 @@ export interface ModelProperties { [name: string]: PropertyDefinition } +export interface IndexDefinition { + name: string; + /** + * Comma-separated column names + * + * @remarks + * Handled by {@link Connector}s directly by default. + * + * Overriden by {@link ModelSettings.indexes.keys}. + */ + columns: string; + /** + * Array of column names to create an index. + * + * @remarks + * Handled by {@link Connector}s directly by default. + * + * Overrides {@link ModelSettings.indexes.columns}. + */ + keys: string[]; + // Postgresql-specific + type: string; + kind: string; +} + /** * Model settings, for example * ```ts @@ -103,7 +128,7 @@ export interface ModelSettings extends AnyObject, ModelUtilsOptions { * Defaults to `true`. */ idInjection?: boolean; - + plural?: string; http?: { @@ -123,38 +148,14 @@ export interface ModelSettings extends AnyObject, ModelUtilsOptions { excludeBaseProperties?: string[]; /** - * Indicates if the {@link ModelBaseClass | Model} is attached to the DataS + * Indicates if the {@link ModelBaseClass | Model} is attached to the + * DataSource * @internal */ unresolved?: boolean; indexes?: { - [indexJugglerName: string]: { - name: string; - /** - * Comma-separated column names - * - * @remarks - * Handled by {@link Connector}s directly by default. - * - * Overriden by {@link ModelSettings.indexes.keys}. - */ - columns: string; - - /** - * Array of column names to create an index. - * - * @remarks - * Handled by {@link Connector}s directly by default. - * - * Overrides {@link ModelSettings.indexes.columns}. - */ - keys: string[]; - - // Postgresql-specific - type: string; - kind: string; - } + [indexJugglerName: string]: IndexDefinition }; foreignKeys?: { @@ -186,39 +187,39 @@ export interface ModelSettings extends AnyObject, ModelUtilsOptions { /** * Model properties to be set as protected. - * + * * @remarks * Protected properties are not serialised to JSON or {@link object} when the * model is a nested child. - * + * * Mostly used by {@link ModelBase.toObject}. */ protectedProperties?: string[]; /** * {@inheritDoc ModelSettings.protectedProperties} - * + * * @remarks * Overriden by {@link ModelSettings.protectedProperties}. - * + * * @deprecated Use {@link ModelSettings.protectedProperties} instead. */ protected?: string[]; /** * Model properties to be set as hidden. - * + * * @remarks * Hidden properties are */ hiddenProperties?: string[]; - /** + /** * {@inheritDoc ModelSettings.hiddenProperties} - * + * * @remarks * Overriden by {@link ModelSettings.hiddenProperties}. - * + * * @deprecated Use {@link ModelSettings.hiddenProperties} instead. */ hidden?: string[]; @@ -229,11 +230,11 @@ export interface ModelSettings extends AnyObject, ModelUtilsOptions { /** * Sets if an {@link Error} should be thrown when no instance(s) were found * for delete operation. - * + * * @remarks - * + * * This setting is used by these operations: - * + * * - {@link DataAccessObject.removeById}/{@link DataAccessObject.destroyById}/{@link DataAccessObject.deleteById} * - {@link DataAccessObject.remove}/{@link DataAccessObject.delete}/{@link DataAccessObject.destroy} */ @@ -285,6 +286,47 @@ export interface ModelBaseClassOptions { applySetters?: boolean; applyDefaultValues?: boolean; strict?: boolean; + persisted?: boolean; +} + +interface ModelMergePolicy { + description?: { + replace: boolean; + }; + properties?: { + patch: boolean; + }; // Only referenced in "legacy built-in merge policy" + options?: { + path: boolean; + }; + hidden?: { + replace: boolean; + }; + protected?: { + replace: boolean; + }; + indexes?: { + patch: boolean; + }; + methods?: { + patch: boolean; + }; + mixins?: { + patch: boolean; + }; + relations?: { + patch: boolean; + }; + scope?: { + replace: boolean; + }; + acls?: { + rank: boolean; + }; + __delete?: boolean | null; + __default?: { + replace: boolean; + }; } /** @@ -299,7 +341,7 @@ export declare class ModelBase { /** * Initializes the model instance with a list of properties. - * + * * @param data The data object * @param options Instation options */ @@ -348,15 +390,15 @@ export declare class ModelBase { /** * Checks if property is hidden. - * @param {string} propertyName Property name - * @returns {Boolean} true or false if hidden or not. + * @param propertyName Property name + * @returns true or false if hidden or not. */ static isHiddenProperty(propertyName: string): boolean; /** * Checks if property is protected. - * @param {string} propertyName Property name - * @returns {Boolean} true or false if protected or not. + * @param propertyName Property name + * @returns true or false if protected or not. */ static isProtectedProperty(propertyName: string): boolean; @@ -423,6 +465,10 @@ export declare class ModelBase { getDataSource(): DataSource; + /** + * Set instance-level strict mode. + * @param strict Strict mode + */ setStrict(strict: boolean): void; /** @@ -523,21 +569,7 @@ export declare class ModelBase { getMergePolicy(options: { configureModelMerge: boolean | object - }): { - description?: {replace: boolean}; - properties?: {patch: boolean}; // Only referenced in "legacy built-in merge policy" - options?: {path: boolean}; - hidden?: {replace: boolean}; - protected?: {replace: boolean}; - indexes?: {patch: boolean}; - methods?: {patch: boolean}; - mixins?: {patch: boolean}; - relations?: {patch: boolean}; - scope?: {replace: boolean}; - acls?: {rank: boolean}; - __delete?: boolean | null; - __default?: {replace: boolean}; - } + }): ModelMergePolicy } export type ModelBaseClass = typeof ModelBase; diff --git a/types/observer-mixin.d.ts b/types/observer-mixin.d.ts index b39c61605..fd1640986 100644 --- a/types/observer-mixin.d.ts +++ b/types/observer-mixin.d.ts @@ -24,6 +24,11 @@ export type Listener> = ( ) => PromiseOrVoid; export interface ObserverMixin { + /** + * @private + */ + _observers?: {[listenerName: string]: Listener[]} + /** * Register an asynchronous observer for the given operation (event). * @@ -70,15 +75,13 @@ export interface ObserverMixin { * }); * ``` * - * @param {String} operation The operation name. - * @callback {function} listener The listener function. - * @end + * @param operation The operation name. + * @param listener The listener function. */ - removeObserver( - this: T, + removeObserver>>( operation: string, - listener: Listener>, - ): Listener> | undefined; + listener: LT, + ): LT | undefined; /** * Unregister all asynchronous observers for the given operation (event). @@ -91,8 +94,7 @@ export interface ObserverMixin { * MyModel.clearObservers('before save'); * ``` * - * @param {String} operation The operation name. - * @end + * @param operation The operation name. */ clearObservers(operation: string): void; @@ -122,17 +124,17 @@ export interface ObserverMixin { * @callback {function(Error=)} callback The callback to call when all observers * have finished. */ - notifyObserversOf( - operation: string, - context: object, + notifyObserversOf( + operation: string | string[], + context: OperationHookContext, callback?: Callback, ): PromiseOrVoid; - _notifyBaseObservers( - operation: string, - context: object, + _notifyBaseObservers( + operation: string | string[], + context: OperationHookContext, callback?: Callback, - ): PromiseOrVoid; + ): PromiseOrVoid; /** * Run the given function with before/after observers. @@ -172,9 +174,9 @@ export interface ObserverMixin { * @callback {Function} callback The callback function * @returns {*} */ - notifyObserversAround( + notifyObserversAround( operation: string, - context: object, + context: OperationHookContext, fn: Function, callback?: Callback, ): PromiseOrVoid; From db342694d428711a63263fc1f249d58f807b8b46 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Sat, 9 Apr 2022 22:46:42 +0800 Subject: [PATCH 22/26] feat: apply misc review suggestions Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- types/model-utils.d.ts | 1 + types/model.d.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/types/model-utils.d.ts b/types/model-utils.d.ts index 60afb7384..aa4184b2e 100644 --- a/types/model-utils.d.ts +++ b/types/model-utils.d.ts @@ -19,6 +19,7 @@ export interface ModelUtilsOptions { maxDepthOfQuery?: number; maxDepthOfData?: number; prohibitHiddenPropertiesInQuery?: boolean; + normalizeUndefinedInQuery?: boolean; // Cassandra-specific clusteringKeys?: string[] diff --git a/types/model.d.ts b/types/model.d.ts index fce8e6126..aca83caa2 100644 --- a/types/model.d.ts +++ b/types/model.d.ts @@ -137,7 +137,7 @@ export interface ModelSettings extends AnyObject, ModelUtilsOptions { /** * @remarks - * Alias of {@link ModelSettings.super}. Takes lower precedence. + * Alias of {@link ModelSettings.super}. Takes higher precedence. */ base?: ModelBaseClass; /** @@ -240,7 +240,7 @@ export interface ModelSettings extends AnyObject, ModelUtilsOptions { */ strictDelete?: boolean; - updatOnly?: boolean; + updateOnly?: boolean; // Postgres-specific defaultIdSort?: boolean | 'numericIdOnly'; From e1bb7a1baa8ba3f8af602102e63a1b7fc8f7f1ab Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Fri, 1 Jul 2022 00:37:37 +0800 Subject: [PATCH 23/26] feat: misc. changes Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- index.d.ts | 2 +- types/connector.d.ts | 294 ++++++++++++++----- types/dao.d.ts | 16 +- types/datasource.d.ts | 112 ++++--- types/model.d.ts | 124 ++++++-- types/{observer-mixin.d.ts => observer.d.ts} | 15 +- types/relation.d.ts | 22 +- 7 files changed, 439 insertions(+), 146 deletions(-) rename types/{observer-mixin.d.ts => observer.d.ts} (92%) diff --git a/index.d.ts b/index.d.ts index 106f63337..809993b60 100644 --- a/index.d.ts +++ b/index.d.ts @@ -30,7 +30,7 @@ export * from './types/persisted-model'; export * from './types/scope'; export * from './types/transaction-mixin'; export * from './types/relation-mixin'; -export * from './types/observer-mixin'; +export * from './types/observer'; export * from './types/validation-mixin'; export * from './types/inclusion-mixin'; export * from './types/connector'; diff --git a/types/connector.d.ts b/types/connector.d.ts index b021ef907..54c6fa46a 100644 --- a/types/connector.d.ts +++ b/types/connector.d.ts @@ -3,12 +3,26 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import { AnyObject } from './common'; -import {Callback, DataSource, Filter, ModelBase, ModelBaseClass, ModelDefinition, ModelProperties, Options, PromiseOrVoid, PropertyDefinition, PropertyType, Schema, Where} from '..'; -import { DataAccessObject } from './dao'; -import { ObserverMixin, OperationHookContext } from './observer-mixin'; -import { IndexDefinition, ModelData } from './model'; -import { ModelUtilsOptions } from './model-utils'; +import {AnyObject} from './common'; +import { + Callback, + DataSource, + Filter, + ModelBase, + ModelBaseClass, + ModelDefinition, + ModelProperties, + Options, + PromiseOrVoid, + PropertyDefinition, + PropertyType, + Schema, + Where, +} from '..'; +import {DataAccessObject} from './dao'; +import {ObserverMixin, OperationHookContext} from './observer'; +import {IndexDefinition, ModelData} from './model'; +import {ModelUtilsOptions} from './model-utils'; export interface ConnectorSettings extends Options { name?: string; @@ -20,15 +34,19 @@ export interface ConnectorSettings extends Options { * @deprecated Use {@link ConnectorSettings.connector} instead. */ adapter?: ConnectorExport | string; - database?: string; connectionTimeout?: number; maxOfflineRequests?: number; lazyConnect?: boolean; debug?: boolean; - url?: string; - username?: string; + url?: string | null; + hostname?: string | ''; + host?: string | ''; + port?: string | null; + username?: string | null; + user?: string | null; password?: string; + database?: string; // Postgres-specific defaultIdSort?: boolean | 'numericIdOnly'; @@ -38,30 +56,30 @@ export interface ConnectorSettings extends Options { // MySQL-specific createDatabase?: boolean; /** - * @defaultValue `'utf8_general_ci'` - **/ + * @defaultValue `'utf8_general_ci'` + **/ charset?: string; collation?: string; /** - * @defaultValue `false` - **/ + * @defaultValue `false` + **/ supportBigNumbers?: boolean; /** - * @defaultValue `'local'` - **/ + * @defaultValue `'local'` + **/ timezone?: string; /** - * @defaultValue `10` - **/ + * @defaultValue `10` + **/ connectionLimit?: number; // END MySQL-specific // MSSQL-specific - tableNameID?: string + tableNameID?: string; // END MSSQL-specific // CouchDB2-specific - Driver?: object // CouchDB driver + Driver?: object; // CouchDB driver // END CouchDB2-specific // Cassandra-specific @@ -71,9 +89,9 @@ export interface ConnectorSettings extends Options { /** * @example `'SimpleStrategy'` */ - class: string, - replication_factor: number, - } + class: string; + replication_factor: number; + }; // END Cassandra-specific } @@ -142,7 +160,10 @@ export interface SchemaDiscoveryOptions { schema?: string; } -export type NameMapper = (type: 'table' | 'model' | 'fk' | string, name: string) => string | null; +export type NameMapper = ( + type: 'table' | 'model' | 'fk' | string, + name: string, +) => string | null; export interface BuildQueryOptions { /** @@ -152,7 +173,7 @@ export interface BuildQueryOptions { * Ignored when {@link BuildQueryOptions.owner} or * {@link BuildQueryOptions.schema} is defined. */ - all?: boolean, + all?: boolean; /** * Filter query to a certain table/view/schema owner. * @@ -160,12 +181,12 @@ export interface BuildQueryOptions { * Overrides {@link BuildQueryOptions.all} when defined. * Alias of {@link BuildQueryOptions.schema} with higher precedence. */ - owner?: string, + owner?: string; /** * @remarks * Alias of {@link BuildQueryOptions.owner} with lower precedence. */ - schema?: string, + schema?: string; } export interface DiscoveredPrimaryKeys { @@ -220,7 +241,8 @@ export interface ConnectorHookBeforeExecuteContext { end: Callback; } -export interface ConnectorHookAfterExecuteContext extends ConnectorHookBeforeExecuteContext { +export interface ConnectorHookAfterExecuteContext + extends ConnectorHookBeforeExecuteContext { res: Record; } @@ -253,7 +275,7 @@ export interface Connector { relational: boolean; - // private _models?: Record; + // private _models?: Record; // private _metadata?: ConnectorMetadata; connect?(callback?: Callback): PromiseOrVoid; // Connect to the underlying system @@ -285,17 +307,43 @@ export interface Connector { * @returns The connector's type collection. */ getTypes?(): string[]; - define?(def: {model: ModelBaseClass, properties: PropertyDefinition, settings: ModelDefinition['settings']}): void; - - all?(model: string, filter: Filter, options: ModelUtilsOptions | null, cb: Callback): void; + define?(def: { + model: ModelBaseClass; + properties: PropertyDefinition; + settings: ModelDefinition['settings']; + }): void; + + all?( + model: string, + filter: Filter, + options: ModelUtilsOptions | null, + cb: Callback, + ): void; findAll?: Connector['all']; - find?(model: string, value: unknown, options: ModelUtilsOptions, cb: Callback): void; - count?(model: string, where: Where, options: ModelUtilsOptions, cb: Callback): void; + find?( + model: string, + value: unknown, + options: ModelUtilsOptions, + cb: Callback, + ): void; + count?( + model: string, + where: Where, + options: ModelUtilsOptions, + cb: Callback, + ): void; serializeObject?(obj: object): unknown; escapeObject?(obj: object): unknown; escapeValue?(obj: object): unknown | null; - getTableStatus?(model: string, cb: (err: any, fields?: PropertyDefinition, indexes?: IndexDefinition) => void): void; + getTableStatus?( + model: string, + cb: ( + err: any, + fields?: PropertyDefinition, + indexes?: IndexDefinition, + ) => void, + ): void; fromDatabase?(model: string, rowData: object): ModelData; fromRow?: Connector['fromDatabase']; @@ -307,9 +355,22 @@ export interface Connector { * @param prop Name of property * @param params Property settings */ - defineProperty?(model: string, prop: string, params: PropertyDefinition): void; - defineForeignKey?(modelName: string, key: string, foreignModelName: string, cb: Callback): void; - defineForeignKey?(modelName: string, key: string, cb: Callback): void; + defineProperty?( + model: string, + prop: string, + params: PropertyDefinition, + ): void; + defineForeignKey?( + modelName: string, + key: string, + foreignModelName: string, + cb: Callback, + ): void; + defineForeignKey?( + modelName: string, + key: string, + cb: Callback, + ): void; getDefaultIdType(): object; isRelational(): boolean; @@ -324,7 +385,20 @@ export interface Connector { propertiesSQL: Connector['buildColumnDefinitions']; // Postgresql-specific createTable?(model: string, cb: Callback): void; - alterTable?(model: string, actualFields: PropertyDefinition[], actualIndexes: IndexDefinition[], cb: Callback): void; + alterTable?( + model: string, + actualFields: PropertyDefinition[], + actualIndexes: IndexDefinition[], + cb: Callback, + ): void; + alterTable?( + model: string, + actualFields: PropertyDefinition[], + actualIndexes: IndexDefinition[], + actualFks: unknown, + cb: Callback, + checkOnly?: boolean, + ): void; dropTable?(model: string, cb: Callback): void; /** * Generate SQL statement to add and remove indexes to sync with database. @@ -343,7 +417,10 @@ export interface Connector { */ getColumnsToAdd?(model: string, actualFields: PropertyDefinition[]): string[]; getDropColumns?(model: string, actualFields: PropertyDefinition[]): string; - getColumnsToDrop?(model: string, actualFields: PropertyDefinition[]): string[]; + getColumnsToDrop?( + model: string, + actualFields: PropertyDefinition[], + ): string[]; /** * @@ -354,7 +431,10 @@ export interface Connector { * @param actualFields * @returns SQL statement */ - getAddModifyColumns?(model: string, actualFields: PropertyDefinition[]): string; + getAddModifyColumns?( + model: string, + actualFields: PropertyDefinition[], + ): string; /** * Mutate `options` parameter to populate missing options with default values @@ -369,22 +449,42 @@ export interface Connector { * * @returns Normalized arguments */ - getArgs?(table: string, options?: Options, cb?: Callback): { - schema: string, - owner: string, + getArgs?( table: string, options?: Options, - cb: Callback, + cb?: Callback, + ): { + schema: string; + owner: string; + table: string; + options?: Options; + cb: Callback; }; - getArgs?(table: string, cb?: Callback): { - schema: string, - owner: string, + getArgs?( table: string, - options?: Options, - cb: Callback, + cb?: Callback, + ): { + schema: string; + owner: string; + table: string; + options?: Options; + cb: Callback; }; + /** + * Convert the property name to a database-specific name. + * + * @remarks + * This function is intended to be used for general conversion of the property + * name from the Juggler {@link PropertyDefinition} to one that is compatible + * with the {@link Connector} database. + * + * This is useful if the database has stricter requirements than JavaScript + * {@link string}. + */ + dbName?(mappingName: string): string + /** * Modify the SQL statement to include pagination clauses. * @@ -392,7 +492,11 @@ export interface Connector { * @param orderBy The property name by which results are ordered * @param options Options for pagination */ - paginateSQL?(sql: string, orderBy: string, options: Pick): string; + paginateSQL?( + sql: string, + orderBy: string, + options: Pick, + ): string; /** * Discover the default target database schema. @@ -413,19 +517,44 @@ export interface Connector { */ getDefaultSchemaName?(): string | ''; + /** + * @remarks + * This is retrieved from `_models[modelName]`. + */ + getModelDefinition?(modelName: string): ModelDefinition | undefined; + + /** + * Retrieve non-standard settings that's recognised by the {@link Connector}. + * + * @remarks + * For some connectors, this may return {@link ModelSettings} as-is. This is + * the default behaviour implemented in the base + * {@link loopback-connector#Connector} class which most Connectors inherit + * from. + * + * It is important to deep-clone as needed before manipulating the returned + * object. + */ + getConnectorSpecificSettings?(modelName: string): Record; + /** * Discover existing database tables. * * @param options Discovery options * @param cb Callback function */ - discoverModelDefinitions?(options: DiscoveryScopeOptions, cb: Callback): Promise; + discoverModelDefinitions?( + options: DiscoveryScopeOptions, + cb: Callback, + ): Promise; /** * {@inheritDoc Connector.discoverModelDefinitions} * @deprecated */ - discoverModelDefinitionsSync?(options: DiscoveryScopeOptions): ModelDefinition[] + discoverModelDefinitionsSync?( + options: DiscoveryScopeOptions, + ): ModelDefinition[]; /** * Discover properties for a given model. @@ -434,13 +563,20 @@ export interface Connector { * @param options Discovery options * @param cb Callback function */ - discoverModelProperties?(modelName: string, options: DiscoveryScopeOptions, cb: Callback): Promise; + discoverModelProperties?( + modelName: string, + options: DiscoveryScopeOptions, + cb: Callback, + ): Promise; /** * {@inheritDoc Connector.discoverModelProperties} * @deprecated */ - discoverModelPropertiesSync?(modelName: string, options: DiscoveryScopeOptions): DiscoveredModelProperties; + discoverModelPropertiesSync?( + modelName: string, + options: DiscoveryScopeOptions, + ): DiscoveredModelProperties; /** * Discover primary keys for a given owner/model name. @@ -449,27 +585,41 @@ export interface Connector { * @param options Discovery options * @param cb Callback function */ - discoverPrimaryKeys?(modelName: string, options: IDPropertiesDiscoveryOptions, cb: Callback): Promise; + discoverPrimaryKeys?( + modelName: string, + options: IDPropertiesDiscoveryOptions, + cb: Callback, + ): Promise; /** * {@inheritDoc Connector.discoverPrimaryKeys} * @deprecated */ - discoverPrimaryKeysSync?(modelName: string, options: IDPropertiesDiscoveryOptions): DiscoveredPrimaryKeys; + discoverPrimaryKeysSync?( + modelName: string, + options: IDPropertiesDiscoveryOptions, + ): DiscoveredPrimaryKeys; - /** + /**discover. * Discover foreign keys for a given owner/model name. * * @param modelName Target model name * @param options Discovery options * @param cb Callback function */ - discoverForeignKeys?(modelName: string[], options: IDPropertiesDiscoveryOptions, cb: Callback): Promise; + discoverForeignKeys?( + modelName: string[], + options: IDPropertiesDiscoveryOptions, + cb: Callback, + ): Promise; /** * {@inheritDoc Connector.discoverForeignKeys} * @deprecated */ - discoverForeignKeysSync?(modelName: string[], options: IDPropertiesDiscoveryOptions): DiscoveredForeignKeys; + discoverForeignKeysSync?( + modelName: string[], + options: IDPropertiesDiscoveryOptions, + ): DiscoveredForeignKeys; /** * Retrieve a description of the foreign key columns that reference the given @@ -480,13 +630,20 @@ export interface Connector { * @param options Discovery options * @param cb Callback function */ - discoverExportedForeignKeys?(modelName: string, options: IDPropertiesDiscoveryOptions, cb: Callback): Promise; + discoverExportedForeignKeys?( + modelName: string, + options: IDPropertiesDiscoveryOptions, + cb: Callback, + ): Promise; /** * {@inheritDoc Connector/discoverExportedForeignKeys} * @deprecated */ - discoverExportedForeignKeysSync?(modelName: string, options?: {owner?: string}): DiscoveredForeignKeys; + discoverExportedForeignKeysSync?( + modelName: string, + options?: {owner?: string}, + ): DiscoveredForeignKeys; /** * Discover schema from a given table name / view name. @@ -495,7 +652,11 @@ export interface Connector { * @param options Discovery options * @param cb Callback function */ - discoverSchemas?(tableName: string, options: SchemaDiscoveryOptions, cb: Callback): Promise; + discoverSchemas?( + tableName: string, + options: SchemaDiscoveryOptions, + cb: Callback, + ): Promise; /** * Check whether or not migrations are required for the database schema to match @@ -546,9 +707,7 @@ export interface Connector { getMetadata(): ConnectorMetadata; } -export declare class SQLConnector { - -} +export declare class SQLConnector {} /** * A {@link Connector} after being attached to a {@link DataSource}. @@ -578,4 +737,7 @@ export declare interface ConnectorExport { initialize: ConnectorInitialize; } -export type ConnectorInitialize = (this: DataSource, callback: Callback) => void; +export type ConnectorInitialize = ( + this: DataSource, + callback: Callback, +) => void; diff --git a/types/dao.d.ts b/types/dao.d.ts index 6942fb58f..d63526a82 100644 --- a/types/dao.d.ts +++ b/types/dao.d.ts @@ -1,6 +1,7 @@ +import { AnyObject } from "strong-globalize/lib/config"; import { Options } from ".."; - -export declare class DataAccessObject {} +import { Connector } from "./connector"; +import { Filter } from "./query"; export interface DaoDmlOptions extends Options { validate?: boolean; @@ -14,3 +15,14 @@ export interface DaoUpsertOptions extends DaoDmlOptions { export interface DaoUpdateOptions extends DaoDmlOptions { validateUpdate?: boolean; } + +export declare class DataAccessObject { + private __persisted: boolean; + private static _forDB(data: AnyObject): unknown; + static defaultScope(target: AnyObject, inst: AnyObject): AnyObject; + static applyScope(query: Filter, inst: AnyObject): AnyObject; + // static applyProperties() + static lookupModel(data?: unknown): DataAccessObject; + static getConnector(): T; + isNewRecord(): boolean; +} diff --git a/types/datasource.d.ts b/types/datasource.d.ts index 2b263a174..690c4dd40 100644 --- a/types/datasource.d.ts +++ b/types/datasource.d.ts @@ -9,29 +9,42 @@ import { ModelBaseClass, ModelBuilder, ModelDefinition, - PropertyDefinition + PropertyDefinition, } from './model'; import {EventEmitter} from 'events'; import {IsolationLevel, Transaction} from './transaction-mixin'; -import { ColumnMetadata, ConnectorSettings, ModelBase, ModelSettings, PersistedModel } from '..'; -import { PersistedModelClass } from './persisted-model'; +import { + ColumnMetadata, + ConnectorSettings, + ModelBase, + ModelSettings, + PersistedModel, +} from '..'; +import {PersistedModelClass} from './persisted-model'; export type OperationOptions = { - accepts: string[], - returns: string[], - http: object, - remoteEnabled: boolean, - scope: unknown, - fnName: string, -} + accepts: string[]; + returns: string[]; + http: object; + remoteEnabled: boolean; + scope: unknown; + fnName: string; +}; export type DiscoverAndBuildModelsOptions = Options & { - base: ModelBaseClass, -} + base: ModelBaseClass; +}; -export function DataSource(name: string, settings?: ConnectorSettings, modelBuilder?: ModelBuilder): DataSource; +export function DataSource( + name: string, + settings?: ConnectorSettings, + modelBuilder?: ModelBuilder, +): DataSource; -export function DataSource(settings?: ConnectorSettings, modelBuilder?: ModelBuilder): DataSource; +export function DataSource( + settings?: ConnectorSettings, + modelBuilder?: ModelBuilder, +): DataSource; export function DataSource( connectorModule: CT, @@ -101,7 +114,9 @@ export function DataSource( * - new DataSource(connectorModule, {name: 'myDataSource}) * - new DataSource(connectorModule) */ -export declare class DataSource extends EventEmitter { +export declare class DataSource< + CT extends Connector = Connector, +> extends EventEmitter { name: string; settings: ConnectorSettings; @@ -111,6 +126,7 @@ export declare class DataSource extends EventE /** * {@inheritDoc DataSource.connector} + * * @deprecated Use {@link DataSource.connector} instead. */ adapter?: BuiltConnector & CT; @@ -146,8 +162,18 @@ export declare class DataSource extends EventE private _operations: Record; + /** + * Retrieve a list of operations defined. + */ operations(): Record; + /** + * Define a new operation. + * + * @param name Operation name + * @param options Operation options + * @param fn Function to be executed for the operation + */ defineOperation(name: string, options: OperationOptions, fn: Function): void; /** @@ -174,9 +200,13 @@ export declare class DataSource extends EventE */ static relationTypes: Record; - constructor(name: string, settings?: ConnectorSettings, modelBuilder?: ModelBuilder); + constructor( + name: string, + settings?: ConnectorSettings, + modelBuilder?: ModelBuilder, + ); - constructor(settings?: ConnectorSettings, modelBuilder?: ModelBuilder); + constructor(settings: ConnectorSettings, modelBuilder?: ModelBuilder); constructor( connectorModule: Connector, @@ -189,7 +219,7 @@ export declare class DataSource extends EventE private _setupConnector(); - private mixin(ModelCtor: T): T + private mixin(ModelCtor: T): T; /** * Set up the data access functions from the data source. Each data source @@ -200,7 +230,10 @@ export declare class DataSource extends EventE * @param settings The settings object; typically allows any settings that * would be valid for a typical Model object. */ - setupDataAccess(modelClass: ModelBaseClass, options?: ModelSettings): asserts modelClass is PersistedModelClass; + setupDataAccess( + modelClass: ModelBaseClass, + options?: ModelSettings, + ): asserts modelClass is PersistedModelClass; /** * Get the maximum number of event listeners @@ -292,7 +325,7 @@ export declare class DataSource extends EventE columnNames(modelName: string): string[]; /** - * Retrieve coulmn metadata for the specified `modelName` and `propertyName`. + * Retrieve column metadata for the specified `modelName` and `propertyName`. * * @param modelName Target model name * @param propertyName Target property name @@ -344,7 +377,12 @@ export declare class DataSource extends EventE * @param foreignClassName Foreign model name * @param pkName Primary key used for foreign key */ - defineForeignKey(className: string, key: string, foreignClassName: string, pkName?: string): undefined | void; + defineForeignKey( + className: string, + key: string, + foreignClassName: string, + pkName?: string, + ): undefined | void; /** * Create a model class @@ -354,7 +392,7 @@ export declare class DataSource extends EventE */ createModel( name: string, - properties?: PropertyDefinition, + properties?: PropertyDefinition[], options?: ModelSettings, ): T; @@ -418,13 +456,9 @@ export declare class DataSource extends EventE /** * {@inheritDoc Connector.discoverModelDefinitions} */ - discoverModelDefinitions( - options?: Options, - ): Promise; + discoverModelDefinitions(options?: Options): Promise; // legacy callback style (no options) - discoverModelDefinitions( - callback: Callback, - ): void; + discoverModelDefinitions(callback: Callback): void; // legacy callback style (with options) discoverModelDefinitions( options: Options, @@ -523,15 +557,9 @@ export declare class DataSource extends EventE callback: Callback<{[name: string]: ModelBaseClass}>, ): void; - discoverSchema( - tableName: string, - options?: Options, - ): Promise; + discoverSchema(tableName: string, options?: Options): Promise; // legacy callback style (no options) - discoverSchema( - tableName: string, - callback: Callback, - ): void; + discoverSchema(tableName: string, callback: Callback): void; // legacy callback style (with options) discoverSchema( tableName: string, @@ -542,15 +570,9 @@ export declare class DataSource extends EventE /** * {@inheritDoc Connector.discoverSchemas} */ - discoverSchemas( - tableName: string, - options?: Options, - ): Promise; + discoverSchemas(tableName: string, options?: Options): Promise; // legacy callback style (no options) - discoverSchemas( - tableName: string, - callback: Callback, - ): void; + discoverSchemas(tableName: string, callback: Callback): void; // legacy callback style (with options) discoverSchemas( tableName: string, @@ -684,7 +706,7 @@ export declare class DataSource extends EventE execute( collectionName: string, command: string, - ...parameters: any[], + ...parameters: any[] ): Promise; /** diff --git a/types/model.d.ts b/types/model.d.ts index aca83caa2..5bb3acb2a 100644 --- a/types/model.d.ts +++ b/types/model.d.ts @@ -6,7 +6,7 @@ import {EventEmitter} from 'events'; import {AnyObject, Options} from './common'; import {DataSource} from './datasource'; -import {Listener, OperationHookContext} from './observer-mixin'; +import {Listener, OperationHookContext} from './observer'; import {ModelUtilsOptions} from './model-utils'; /** @@ -29,13 +29,48 @@ export interface PropertyDefinition extends AnyObject { id?: boolean | number; defaultFn?: DefaultFns; useDefaultIdType?: boolean; - columnName?: string; + /** + * Sets the column/field name in the database. + * + * @remarks + * Precedence: + * - {@link PropertyDefinition.name} + * - {@link PropertyDefinition.column} + * - {@link PropertyDefinition.columnName} + * - {@link PropertyDefinition.field} + * - {@link PropertyDefinition.fieldName} + */ + name?: string; + /** + * {@inheritDoc PropertyDefinition.name} + */ column?: string; + /** + * {@inheritDoc PropertyDefinition.name} + */ + columnName?: string; + /** + * {@inheritDoc PropertyDefinition.name} + */ + field?: string; + /** + * {@inheritDoc PropertyDefinition.name} + */ + fieldName?: string dataType?: string; dataLength?: number; dataPrecision?: number; dataScale?: number; + index?: boolean | string | IndexDefinition; nullable?: 'Y' | 'N'; + /** + * @deprecated + */ + null?: boolean; + /** + * @deprecated + */ + allowNull?: boolean // PostgreSQL-specific? autoIncrement?: boolean; } @@ -57,12 +92,6 @@ export interface IdDefinition { id: number; property: AnyObject; } - -/** - * Index definition - */ -export interface IndexDefinition extends AnyObject {} - /** * Column metadata */ @@ -83,14 +112,23 @@ export interface ModelProperties { } export interface IndexDefinition { - name: string; /** - * Comma-separated column names + * @remarks + * This should not end with a trailing '_index' as this may lead to collisions + * with indexes defined through {@link PropertyDefinition.index}. + * + * If not set, the index name may be inferred from the object key which this + * index definition is stored in. + */ + name?: string; + + /** + * Comma-separated column names: * * @remarks * Handled by {@link Connector}s directly by default. * - * Overriden by {@link ModelSettings.indexes.keys}. + * Overriden by {@link IndexDefinition.keys}. */ columns: string; /** @@ -101,10 +139,13 @@ export interface IndexDefinition { * * Overrides {@link ModelSettings.indexes.columns}. */ - keys: string[]; - // Postgresql-specific - type: string; - kind: string; + keys?: string[]; + + type?: string; + kind?: string; + unique?: string; + + options?: Record; } /** @@ -121,7 +162,14 @@ export interface ModelSettings extends AnyObject, ModelUtilsOptions { /** * Set if manual assignment of auto-generated ID values should be blocked. */ - forceId?: boolean; + forceId?: boolean | 'auto'; + + properties?: ModelProperties; + + /** + * @deprecated Use {@link ModelSettings.properties} instead. + */ + attributes?: ModelProperties; /** * @remarks @@ -135,14 +183,19 @@ export interface ModelSettings extends AnyObject, ModelUtilsOptions { path?: string; } + scope?: AnyObject | Function; + /** * @remarks * Alias of {@link ModelSettings.super}. Takes higher precedence. */ base?: ModelBaseClass; + /** * @remarks * Alias of {@link ModelSettings.base}. Takes lower precedence. + * + * This is taken from the behaviour of {@link util#inherits}. */ super?: ModelBaseClass; excludeBaseProperties?: string[]; @@ -277,7 +330,7 @@ export declare class ModelDefinition extends EventEmitter implements Schema { propertyName: string, propertyDefinition: PropertyDefinition, ): void; - indexes(): {[name: string]: IndexDefinition}; + indexes(): {[name: string | `${string}_index`]: IndexDefinition | boolean}; build(forceRebuild?: boolean): AnyObject; toJSON(forceRebuild?: boolean): AnyObject; } @@ -323,22 +376,44 @@ interface ModelMergePolicy { acls?: { rank: boolean; }; + __delete?: boolean | null; + + /** + * Default merge policy to be applied when merge policy is `null` + */ __default?: { replace: boolean; }; } +interface ModelMergePolicyOptions { + configureModelMerge: boolean | object; +} + /** * Base class for LoopBack 3.x models */ export declare class ModelBase { + /** + * @deprecated + */ static dataSource?: DataSource; static modelName: string; static definition: ModelDefinition; static hideInternalProperties?: boolean; static readonly base: typeof ModelBase; + /** + * Model inheritance rank + * + * @remarks + * This is used by {@link ModelBuilder}. + * + * @internal + */ + static __rank?: number; + /** * Initializes the model instance with a list of properties. * @@ -567,9 +642,7 @@ export declare class ModelBase { */ static clearObservers(operation: string): void; - getMergePolicy(options: { - configureModelMerge: boolean | object - }): ModelMergePolicy + getMergePolicy(options: ModelMergePolicyOptions): ModelMergePolicy } export type ModelBaseClass = typeof ModelBase; @@ -577,13 +650,18 @@ export type ModelBaseClass = typeof ModelBase; export declare class ModelBuilder extends EventEmitter { static defaultInstance: ModelBuilder; - models: {[name: string]: ModelBaseClass}; + models: {[name: string]: typeof ModelBase}; definitions: {[name: string]: ModelDefinition}; - settings: ModelSettings; + settings: { + /** + * @defaultValue `false` + */ + strictEmbeddedModels?: boolean; + }; defaultModelBaseClass: typeof ModelBase; - getModel(name: string, forceCreate?: boolean): ModelBaseClass; + getModel(name: string, forceCreate?: boolean): typeof ModelBase; getModelDefinition(name: string): ModelDefinition | undefined; diff --git a/types/observer-mixin.d.ts b/types/observer.d.ts similarity index 92% rename from types/observer-mixin.d.ts rename to types/observer.d.ts index fd1640986..0d121b2ea 100644 --- a/types/observer-mixin.d.ts +++ b/types/observer.d.ts @@ -23,7 +23,7 @@ export type Listener> = ( ctx: Ctx, next: (err?: any) => void ) => PromiseOrVoid; -export interface ObserverMixin { +export declare class ObserverMixin { /** * @private */ @@ -57,8 +57,7 @@ export interface ObserverMixin { * `this` set to the model constructor, e.g. `User`. * @end */ - observe( - this: T, + static observe( operation: string, listener: Listener>, ): void; @@ -78,7 +77,7 @@ export interface ObserverMixin { * @param operation The operation name. * @param listener The listener function. */ - removeObserver>>( + static removeObserver>>( operation: string, listener: LT, ): LT | undefined; @@ -96,7 +95,7 @@ export interface ObserverMixin { * * @param operation The operation name. */ - clearObservers(operation: string): void; + static clearObservers(operation: string): void; /** * Invoke all async observers for the given operation(s). @@ -124,13 +123,13 @@ export interface ObserverMixin { * @callback {function(Error=)} callback The callback to call when all observers * have finished. */ - notifyObserversOf( + static notifyObserversOf( operation: string | string[], context: OperationHookContext, callback?: Callback, ): PromiseOrVoid; - _notifyBaseObservers( + static _notifyBaseObservers( operation: string | string[], context: OperationHookContext, callback?: Callback, @@ -174,7 +173,7 @@ export interface ObserverMixin { * @callback {Function} callback The callback function * @returns {*} */ - notifyObserversAround( + static notifyObserversAround( operation: string, context: OperationHookContext, fn: Function, diff --git a/types/relation.d.ts b/types/relation.d.ts index f29ae471b..c98a24853 100644 --- a/types/relation.d.ts +++ b/types/relation.d.ts @@ -26,6 +26,24 @@ export enum RelationType { embedsMany = 'embedsMany', } +export interface RelationDefinition { + name: string; + type: RelationType; + modelFrom: PersistedModelClass | string; + keyFrom: string; + modelTo: PersistedModelClass | string; + keyTo: string; + polymorphic: PersistedModelClass | boolean; + modelThrough?: PersistedModelClass | string; + keyThrough?: string; + multiple: boolean; + properties: AnyObject; + options: Options; + scope: AnyObject; + embed?: boolean; + methods?: AnyObject; +} + /** * Relation definition */ @@ -36,7 +54,7 @@ export declare class RelationDefinition { keyFrom: string; modelTo: PersistedModelClass | string; keyTo: string; - polymorphic: AnyObject | boolean; + polymorphic: PersistedModelClass | boolean; modelThrough?: PersistedModelClass | string; keyThrough?: string; multiple: boolean; @@ -46,6 +64,8 @@ export declare class RelationDefinition { embed?: boolean; methods?: AnyObject; + constructor(definition: AnyObject); + toJSON(): AnyObject; defineMethod(name: string, fn: Function): Function; applyScope(modelInstance: PersistedData, filter: Filter): void; From de9ce11407d0c81e40b308ac2af9b9d1ae47ddfd Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Mon, 22 Aug 2022 15:42:55 +0800 Subject: [PATCH 24/26] feat: misc changes Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- tsconfig.json | 1 + types/__tests__/model-builder.spec.ts | 10 + types/connector.d.ts | 22 ++- types/datasource.d.ts | 7 +- types/model-builder.d.ts | 66 +++++++ types/model.d.ts | 256 +++++++++++++++----------- types/types.d.ts | 12 +- 7 files changed, 255 insertions(+), 119 deletions(-) create mode 100644 types/__tests__/model-builder.spec.ts create mode 100644 types/model-builder.d.ts diff --git a/tsconfig.json b/tsconfig.json index e3cfa08b7..0e1879f47 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "experimentalDecorators": true, "noImplicitAny": true, "strictNullChecks": true, + "esModuleInterop": true, "lib": ["es2018", "dom"], "module": "commonjs", diff --git a/types/__tests__/model-builder.spec.ts b/types/__tests__/model-builder.spec.ts new file mode 100644 index 000000000..b0489f0a1 --- /dev/null +++ b/types/__tests__/model-builder.spec.ts @@ -0,0 +1,10 @@ +import { ModelBuilder } from "../model-builder"; +import {BuiltModelTypes} from '../types'; + +let modelBuilderTypeGuard: typeof ModelBuilder = ModelBuilder; +let typesTypeGuard: BuiltModelTypes; + +// Test: Ensure that ModelBuilder is compliant with Types interface as +// ...ModelBuilder inherits from Types. This is to workaround TypeScript's +// ...inability to represent unorthodox "extending" from multiple classes. +typesTypeGuard = modelBuilderTypeGuard; diff --git a/types/connector.d.ts b/types/connector.d.ts index 54c6fa46a..1343a18fc 100644 --- a/types/connector.d.ts +++ b/types/connector.d.ts @@ -78,9 +78,9 @@ export interface ConnectorSettings extends Options { tableNameID?: string; // END MSSQL-specific - // CouchDB2-specific + // CouchDB2 & Cloudant-specific Driver?: object; // CouchDB driver - // END CouchDB2-specific + // END CouchDB2 & Cloudant-specific // Cassandra-specific keyspace?: string; @@ -160,6 +160,13 @@ export interface SchemaDiscoveryOptions { schema?: string; } +/** + * Transform database discovery results + * + * @reamrks + * See also {@link Connector.dbName} which is for converting the other + * direction. + */ export type NameMapper = ( type: 'table' | 'model' | 'fk' | string, name: string, @@ -218,7 +225,10 @@ export interface DiscoveredModelProperties { dataLength?: number; dataPrecision?: number; dataScale?: number; - nullable?: boolean; + /** + * {@inheritDoc ConnectorSpecificPropertyDefinition.nullable} + */ + nullable?: 0 | 'N' | 'NO' | 1 | 'Y' | 'YES' | boolean; } // #TODO(achrinza): The codebase suggets that `context` differs @@ -304,9 +314,13 @@ export interface Connector { * Alternatively, ['rest'] would be a different type altogether, and would have * no subtype. * + * Note that returning a comma-delimeted string (e.g. `'db,nosql,mongodb'`) is + * deprecated and strongly not recommended. It is kept for + * backwards-compatibility only. + * * @returns The connector's type collection. */ - getTypes?(): string[]; + getTypes?(): string[] | string; define?(def: { model: ModelBaseClass; properties: PropertyDefinition; diff --git a/types/datasource.d.ts b/types/datasource.d.ts index 690c4dd40..a5dd33c0a 100644 --- a/types/datasource.d.ts +++ b/types/datasource.d.ts @@ -7,10 +7,10 @@ import {AnyObject, Callback, Options} from './common'; import {BuiltConnector, Connector} from './connector'; import { ModelBaseClass, - ModelBuilder, ModelDefinition, PropertyDefinition, } from './model'; +import { ModelBuilder } from "./model-builder"; import {EventEmitter} from 'events'; import {IsolationLevel, Transaction} from './transaction-mixin'; import { @@ -247,6 +247,11 @@ export declare class DataSource< // Reason for deprecation is not clear. /** * {@inheritDoc Connector.getTypes} + * + * @remarks + * Unlike {@link Connector.getTypes}, this function will normalize the return + * value into an array. + * * @deprecated Use {@link DataSource.supportTypes} instead. */ getTypes(): string[]; diff --git a/types/model-builder.d.ts b/types/model-builder.d.ts new file mode 100644 index 000000000..00be42757 --- /dev/null +++ b/types/model-builder.d.ts @@ -0,0 +1,66 @@ +import { EventEmitter } from 'events'; +import { AnyObject, Options } from './common'; +import { BuiltModelTypes, Types } from './types'; +import { ModelBase, ModelDefinition, ModelProperties, ModelSettings, ModelBaseClass } from './model'; + +export declare class ModelClass extends ModelBase { + +} + +export declare class ModelBuilder extends EventEmitter { + static defaultInstance: ModelBuilder; + + models: { [name: string]: typeof ModelBase; }; + definitions: { [name: string]: ModelDefinition; }; + settings: { + /** + * @defaultValue `false` + */ + strictEmbeddedModels?: boolean; + }; + + defaultModelBaseClass: typeof ModelBase; + + getModel(name: string, forceCreate?: boolean): typeof ModelBase; + + getModelDefinition(name: string): ModelDefinition | undefined; + + define( + className: string, + properties?: ModelProperties, + settings?: ModelSettings, + parent?: ModelBaseClass + ): ModelBaseClass; + + defineProperty( + modelName: string, + propertyName: string, + propertyDefinition: AnyObject + ): void; + + defineValueType(type: string, aliases?: string[]): void; + + extendModel(modelName: string, properties: AnyObject): void; + + getSchemaName(name?: string): string; + + resolveType(type: any): any; + + buildModels( + schemas: AnyObject, + createModel?: Function + ): { [name: string]: ModelBaseClass; }; + + buildModelFromInstance( + name: string, + json: AnyObject, + options: Options + ): ModelBaseClass; + + // START mixin-ed extensions from BuiltModelTypes + static Text: Types.Text; + static JSON: Types.JSON; + static Any: Types.Any; + static registerType: BuiltModelTypes['registerType']; + static schemaTypes: BuiltModelTypes['schemaTypes']; +} diff --git a/types/model.d.ts b/types/model.d.ts index 5bb3acb2a..a838305da 100644 --- a/types/model.d.ts +++ b/types/model.d.ts @@ -8,6 +8,8 @@ import {AnyObject, Options} from './common'; import {DataSource} from './datasource'; import {Listener, OperationHookContext} from './observer'; import {ModelUtilsOptions} from './model-utils'; +import registerModelTypes = require('./types'); +import { ModelBuilder } from './model-builder'; /** * Property types @@ -19,34 +21,23 @@ export type PropertyType = | Function | {[property: string]: PropertyType}; -export type DefaultFns = 'guid' | 'uuid' | 'uuidv4' | 'now' | 'shortid' | 'nanoid' | string; +export type DefaultFns = + | 'guid' + | 'uuid' + | 'uuidv4' + | 'now' + | 'shortid' + | 'nanoid' + | string; -/** - * Property definition - */ -export interface PropertyDefinition extends AnyObject { - type?: PropertyType; - id?: boolean | number; - defaultFn?: DefaultFns; - useDefaultIdType?: boolean; - /** - * Sets the column/field name in the database. - * - * @remarks - * Precedence: - * - {@link PropertyDefinition.name} - * - {@link PropertyDefinition.column} - * - {@link PropertyDefinition.columnName} - * - {@link PropertyDefinition.field} - * - {@link PropertyDefinition.fieldName} - */ - name?: string; +interface ConnectorSpecificPropertyDefinition { /** * {@inheritDoc PropertyDefinition.name} */ column?: string; /** * {@inheritDoc PropertyDefinition.name} + * @deprecated Use {@link ConnectorSpecificPropertyDefinition.column} instead. */ columnName?: string; /** @@ -55,26 +46,65 @@ export interface PropertyDefinition extends AnyObject { field?: string; /** * {@inheritDoc PropertyDefinition.name} + * @deprecated Use {@link ConnectorSpecificPropertyDefinition.field} instead. */ - fieldName?: string + fieldName?: string; dataType?: string; dataLength?: number; dataPrecision?: number; dataScale?: number; index?: boolean | string | IndexDefinition; - nullable?: 'Y' | 'N'; /** - * @deprecated + * @remarks + * Support for the different representations is dependent on the + * {@link Connector}. For best compatibility, use `true` and `false`. Consult + * the respective Connectors' documentation for more information. + */ + nullable?: 1 | 'Y' | 'YES' | 0 | 'N' | 'NO' | boolean; + /** + * @deprecated Use {@link ConnectorSpecificPropertyDefinition.nullable} instead. */ null?: boolean; /** - * @deprecated + * @deprecated Use {@link ConnectorSpecificPropertyDefinition.nullable} instead. */ - allowNull?: boolean + allowNull?: boolean; // PostgreSQL-specific? autoIncrement?: boolean; } +interface ConnectorSpecificPropertyDefinitionIndex { + [connectorNameOrType: string]: ConnectorSpecificPropertyDefinition; +} + +/** + * Property definition + */ +export interface PropertyDefinition extends AnyObject, ConnectorSpecificPropertyDefinitionIndex { + type?: PropertyType; + id?: boolean | number; + defaultFn?: DefaultFns; + /** + * @defaultValue `true` + */ + useDefaultIdType?: boolean; + /** + * Sets the column/field name in the database. + * + * @remarks + * Precedence: + * - {@link PropertyDefinition.name} + * - {@link PropertyDefinition[connectorName].column} + * - {@link PropertyDefinition[connectorName].columnName} + * - {@link PropertyDefinition[connectorName].field} + * - {@link PropertyDefinition[connectorName].fieldName} + */ + name?: string; + nullable?: boolean; + + [key: string]: any; +} + /** * Schema definition */ @@ -90,7 +120,7 @@ export interface Schema { export interface IdDefinition { name: string; id: number; - property: AnyObject; + property: PropertyDefinition; } /** * Column metadata @@ -108,7 +138,7 @@ export interface ColumnMetadata extends AnyObject { * ``` */ export interface ModelProperties { - [name: string]: PropertyDefinition + [name: string]: PropertyDefinition; } export interface IndexDefinition { @@ -148,6 +178,39 @@ export interface IndexDefinition { options?: Record; } +export interface ConnectorSpecificModelSettingsIndex { + [connectorName: string]: ConnectorSpecificModelSettings; +} + +export interface ConnectorSpecificModelSettings { + /** + * The database schema which the table is located in. + */ + schema?: string; + + /** + * @remarks + * Overriden by `schema`. + * + * @deprecated Use `schema` instead. + */ + schemaName?: string; + + /** + * Mapped table name for the model. + */ + table?: string; + + /** + * + * @remarks + * Overriden by `table`. + * + * @deprecated Use `table` instead. + */ + tableName?: string; +} + /** * Model settings, for example * ```ts @@ -156,7 +219,11 @@ export interface IndexDefinition { * } * ``` */ -export interface ModelSettings extends AnyObject, ModelUtilsOptions { +export interface ModelSettings + extends AnyObject, + ModelUtilsOptions, + Pick, + ConnectorSpecificModelSettingsIndex { strict?: boolean; /** @@ -181,7 +248,13 @@ export interface ModelSettings extends AnyObject, ModelUtilsOptions { http?: { path?: string; - } + }; + + /** + * @remarks + * Used by {@link ModelBuilder.define}. + */ + models?: (string | ModelBaseClass)[]; scope?: AnyObject | Function; @@ -202,39 +275,38 @@ export interface ModelSettings extends AnyObject, ModelUtilsOptions { /** * Indicates if the {@link ModelBaseClass | Model} is attached to the - * DataSource + * DataSource. + * + * @remarks + * This is managed by {@link ModelBuilder}. + * * @internal */ unresolved?: boolean; indexes?: { - [indexJugglerName: string]: IndexDefinition + [indexJugglerName: string]: IndexDefinition; }; foreignKeys?: { [fkJugglerName: string]: { - name: string, - entity: ModelBase | string, - entityKey: string, - foreignKey: string, - onDelete?: string, - onUpdate?: string, - } - } - - /** - * {@inheritDoc ModelSettings.tableName} - */ - tableName?: string - - /** - * Mapped table name for the model. - */ - table?: string; + name: string; + entity: ModelBase | string; + entityKey: string; + foreignKey: string; + onDelete?: 'RESTRICT' | 'CASCADE' | 'SET NULL' | 'NO ACTION' | 'SET DEFAULT' | string; + onUpdate?: 'RESTRICT' | 'CASCADE' | 'SET NULL' | 'NO ACTION' | 'SET DEFAULT' | string; + }; + }; /** * Sets if JavaScript {@link undefined} as an attribute value should be - * persisted as database `NULL`. + * normalized and persisted as database `NULL`. + * + * @remarks + * This setting applies towards functions that set a {@link ModelBase} + * instance's properties, which is a separate step from saving the instance to + * the database through the attached {@link DataSource}. */ persistUndefinedAsNull?: boolean; @@ -285,7 +357,6 @@ export interface ModelSettings extends AnyObject, ModelUtilsOptions { * for delete operation. * * @remarks - * * This setting is used by these operations: * * - {@link DataAccessObject.removeById}/{@link DataAccessObject.destroyById}/{@link DataAccessObject.deleteById} @@ -302,10 +373,25 @@ export interface ModelSettings extends AnyObject, ModelUtilsOptions { /** * Model definition */ -export declare class ModelDefinition extends EventEmitter implements Schema { +export declare class ModelDefinition + extends EventEmitter + implements Omit +{ name: string; - properties: ModelProperties; - rawProperties: AnyObject; + /** + * A map of {@link PropertyDefinition}s + * + * @remarks + * This is only populated after {@link ModelDefinition.build} is called. + */ + properties: ModelProperties | null; + /** + * A map of {@link PropertyDefinition}s + * + * @remarks + * Unlike {@link ModelDefinition.properties}, this may + */ + rawProperties: ModelProperties; settings?: ModelSettings; relations?: AnyObject[]; @@ -323,6 +409,9 @@ export declare class ModelDefinition extends EventEmitter implements Schema { columnMetadata(connectorType: string, propertyName: string): ColumnMetadata; ids(): IdDefinition[]; + /** + * @deprecated Use {@link ModelDefinition.idNames} instead. + */ idName(): string; idNames(): string[]; @@ -420,7 +509,7 @@ export declare class ModelBase { * @param data The data object * @param options Instation options */ - private _initProperties(data: object, options: ModelBaseClass): void; + private _initProperties(data: object, options: typeof ModelBase): void; /** * Extend the model with the specified model, properties, and other settings. @@ -642,67 +731,16 @@ export declare class ModelBase { */ static clearObservers(operation: string): void; - getMergePolicy(options: ModelMergePolicyOptions): ModelMergePolicy + getMergePolicy(options: ModelMergePolicyOptions): ModelMergePolicy; } export type ModelBaseClass = typeof ModelBase; -export declare class ModelBuilder extends EventEmitter { - static defaultInstance: ModelBuilder; - - models: {[name: string]: typeof ModelBase}; - definitions: {[name: string]: ModelDefinition}; - settings: { - /** - * @defaultValue `false` - */ - strictEmbeddedModels?: boolean; - }; - - defaultModelBaseClass: typeof ModelBase; - - getModel(name: string, forceCreate?: boolean): typeof ModelBase; - - getModelDefinition(name: string): ModelDefinition | undefined; - - define( - className: string, - properties?: ModelProperties, - settings?: ModelSettings, - parent?: ModelBaseClass, - ): ModelBaseClass; - - defineProperty( - modelName: string, - propertyName: string, - propertyDefinition: AnyObject, - ): void; - - defineValueType(type: string, aliases?: string[]): void; - - extendModel(modelName: string, properties: AnyObject): void; - - getSchemaName(name?: string): string; - - resolveType(type: any): any; - - buildModels( - schemas: AnyObject, - createModel?: Function, - ): {[name: string]: ModelBaseClass}; - - buildModelFromInstance( - name: string, - json: AnyObject, - options: Options, - ): ModelBaseClass; -} - /** * An extension of the built-in Partial type which allows partial values * in deeply nested properties too. */ -export type DeepPartial = { [P in keyof T]?: DeepPartial; }; +export type DeepPartial = {[P in keyof T]?: DeepPartial}; /** * Union export type for model instance or plain object representing the model diff --git a/types/types.d.ts b/types/types.d.ts index 5ac8fc399..2253d88e3 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -37,16 +37,18 @@ declare namespace registerModelTypes { } } - interface ModelTypes { - [type: string]: Type | unknown; - } - interface Type { value: unknown; toJSON(): unknown; toObject(): unknown; } + interface ModelTypes { + Text: Types.Text; + JSON: Types.JSON; + Any: Types.Any; + } + interface BuiltModelTypes extends ModelTypes { schemaTypes: Record & { 'String': String; @@ -61,6 +63,6 @@ declare namespace registerModelTypes { }; registerType: (type: Type, names?: string[]) => void; } - } + export = registerModelTypes; From 5a52edbbfa37a2173bbc00bb8c65f71258777beb Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Sun, 23 Jul 2023 04:00:50 +0800 Subject: [PATCH 25/26] feat: misc typedef updates Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- lib/#model.js# | 874 ++++++++++++++++++++++++++++++++++ types/.model-builder.d.ts.swp | Bin 0 -> 12288 bytes types/connector.d.ts | 32 +- types/model.d.ts | 63 ++- types/validation-mixin.d.ts | 4 +- 5 files changed, 962 insertions(+), 11 deletions(-) create mode 100644 lib/#model.js# create mode 100644 types/.model-builder.d.ts.swp diff --git a/lib/#model.js# b/lib/#model.js# new file mode 100644 index 000000000..d974ad0dd --- /dev/null +++ b/lib/#model.js# @@ -0,0 +1,874 @@ +x// Copyright IBM Corp. 2013,2019. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +// Turning on strict for this file breaks lots of test cases; +// disabling strict for this file +/* eslint-disable strict */ + +/*! + * Module exports class Model + */ +module.exports = ModelBaseClass; + +/*! + * Module dependencies + */ + +const g = require('strong-globalize')(); +const util = require('util'); +const jutil = require('./jutil'); +const List = require('./list'); +const DataAccessUtils = require('./model-utils'); +const Observer = require('./observer'); +const Hookable = require('./hooks'); +const validations = require('./validations'); +const _extend = util._extend; +const utils = require('./utils'); +const fieldsToArray = utils.fieldsToArray; +const uuid = require('uuid'); +const {nanoid} = require('nanoid'); + +// Set up an object for quick lookup +const BASE_TYPES = { + 'String': true, + 'Boolean': true, + 'Number': true, + 'Date': true, + 'Text': true, + 'ObjectID': true, +}; + +/** + * Model class: base class for all persistent objects. + * + * `ModelBaseClass` mixes `Validatable` and `Hookable` classes methods + * + * @class + * @param {Object} data Initial object data + * @param {Object} options An object to control the instantiation + * @returns {ModelBaseClass} an instance of the ModelBaseClass + */ +function ModelBaseClass(data, options) { + options = options || {}; + if (!('applySetters' in options)) { + // Default to true + options.applySetters = true; + } + if (!('applyDefaultValues' in options)) { + options.applyDefaultValues = true; + } + this._initProperties(data, options); +} + +/** + * Initialize the model instance with a list of properties + * @param {Object} data The data object + * @param {Object} options An object to control the instantiation + * @property {Boolean} applySetters Controls if the setters will be applied + * @property {Boolean} applyDefaultValues Default attributes and values will be applied + * @property {Boolean} strict Set the instance level strict mode + * @property {Boolean} persisted Whether the instance has been persisted + * @private + */ +ModelBaseClass.prototype._initProperties = function(data, options) { + const self = this; + const ctor = this.constructor; + + if (typeof data !== 'undefined' && data !== null && data.constructor && + typeof (data.constructor) !== 'function') { + throw new Error(g.f('Property name "{{constructor}}" is not allowed in %s data', ctor.modelName)); + } + + if (data instanceof ctor) { + // Convert the data to be plain object to avoid pollutions + data = data.toObject(false); + } + const properties = _extend({}, ctor.definition.properties); + data = data || {}; + + if (typeof ctor.applyProperties === 'function') { + ctor.applyProperties(data); + } + + options = options || {}; + const applySetters = options.applySetters; + const applyDefaultValues = options.applyDefaultValues; + let strict = options.strict; + + if (strict === undefined) { + strict = ctor.definition.settings.strict; + } else if (strict === 'throw') { + g.warn('Warning: Model %s, {{strict mode: `throw`}} has been removed, ' + + 'please use {{`strict: true`}} instead, which returns' + + '{{`Validation Error`}} for unknown properties,', ctor.modelName); + } + + const persistUndefinedAsNull = ctor.definition.settings.persistUndefinedAsNull; + + if (ctor.hideInternalProperties) { + // Object.defineProperty() is expensive. We only try to make the internal + // properties hidden (non-enumerable) if the model class has the + // `hideInternalProperties` set to true + Object.defineProperties(this, { + __cachedRelations: { + writable: true, + enumerable: false, + configurable: true, + value: {}, + }, + + __data: { + writable: true, + enumerable: false, + configurable: true, + value: {}, + }, + + // Instance level data source + __dataSource: { + writable: true, + enumerable: false, + configurable: true, + value: options.dataSource, + }, + + // Instance level strict mode + __strict: { + writable: true, + enumerable: false, + configurable: true, + value: strict, + }, + + __persisted: { + writable: true, + enumerable: false, + configurable: true, + value: false, + }, + }); + + if (strict) { + Object.defineProperty(this, '__unknownProperties', { + writable: true, + enumerable: false, + configrable: true, + value: [], + }); + } + } else { + this.__cachedRelations = {}; + this.__data = {}; + this.__dataSource = options.dataSource; + this.__strict = strict; + this.__persisted = false; + if (strict) { + this.__unknownProperties = []; + } + } + + if (options.persisted !== undefined) { + this.__persisted = options.persisted === true; + } + + if (data.__cachedRelations) { + this.__cachedRelations = data.__cachedRelations; + } + + let keys = Object.keys(data); + + if (Array.isArray(options.fields)) { + keys = keys.filter(function(k) { + return (options.fields.indexOf(k) != -1); + }); + } + + let size = keys.length; + let p, propVal; + for (let k = 0; k < size; k++) { + p = keys[k]; + propVal = data[p]; + if (typeof propVal === 'function') { + continue; + } + + if (propVal === undefined && persistUndefinedAsNull) { + propVal = null; + } + + if (properties[p]) { + // Managed property + if (applySetters || properties[p].id) { + self[p] = propVal; + } else { + self.__data[p] = propVal; + } + } else if (ctor.relations[p]) { + const relationType = ctor.relations[p].type; + + let modelTo; + if (!properties[p]) { + modelTo = ctor.relations[p].modelTo || ModelBaseClass; + const multiple = ctor.relations[p].multiple; + const typeName = multiple ? 'Array' : modelTo.modelName; + const propType = multiple ? [modelTo] : modelTo; + properties[p] = {name: typeName, type: propType}; + /* Issue #1252 + this.setStrict(false); + */ + } + + // Relation + if (relationType === 'belongsTo' && propVal != null) { + // If the related model is populated + self.__data[ctor.relations[p].keyFrom] = propVal[ctor.relations[p].keyTo]; + + if (ctor.relations[p].options.embedsProperties) { + const fields = fieldsToArray(ctor.relations[p].properties, + modelTo.definition.properties, modelTo.settings.strict); + if (!~fields.indexOf(ctor.relations[p].keyTo)) { + fields.push(ctor.relations[p].keyTo); + } + self.__data[p] = new modelTo(propVal, { + fields: fields, + applySetters: false, + persisted: options.persisted, + }); + } + } + + self.__cachedRelations[p] = propVal; + } else { + // Un-managed property + if (strict === false || self.__cachedRelations[p]) { + self[p] = self.__data[p] = + (propVal !== undefined) ? propVal : self.__cachedRelations[p]; + + // Throw error for properties with unsupported names + if (/\./.test(p)) { + throw new Error(g.f( + 'Property names containing dot(s) are not supported. ' + + 'Model: %s, dynamic property: %s', + this.constructor.modelName, p, + )); + } + } else { + if (strict !== 'filter') { + this.__unknownProperties.push(p); + } + } + } + } + + keys = Object.keys(properties); + + if (Array.isArray(options.fields)) { + keys = keys.filter(function(k) { + return (options.fields.indexOf(k) != -1); + }); + } + + size = keys.length; + + for (let k = 0; k < size; k++) { + p = keys[k]; + propVal = self.__data[p]; + const type = properties[p].type; + + // Set default values + if (applyDefaultValues && propVal === undefined && appliesDefaultsOnWrites(properties[p])) { + let def = properties[p]['default']; + if (def !== undefined) { + if (typeof def === 'function') { + if (def === Date) { + // FIXME: We should coerce the value in general + // This is a work around to {default: Date} + // Date() will return a string instead of Date + def = new Date(); + } else { + def = def(); + } + } else if (type.name === 'Date' && def === '$now') { + def = new Date(); + } + // FIXME: We should coerce the value + // will implement it after we refactor the PropertyDefinition + self.__data[p] = propVal = def; + } + } + + if (ignoresMatchedDefault(properties[p]) && properties[p].default === propVal) { + delete self.__data[p]; + } + + // Set default value using a named function + if (applyDefaultValues && propVal === undefined) { + const defn = properties[p].defaultFn; + switch (defn) { + case undefined: + break; + case 'guid': + case 'uuid': + // Generate a v1 (time-based) id + propVal = uuid.v1(); + break; + case 'uuidv4': + // Generate a RFC4122 v4 UUID + propVal = uuid.v4(); + break; + case 'now': + propVal = new Date(); + break; + case 'shortid': + case 'nanoid': + propVal = nanoid(9); + break; + default: + // TODO Support user-provided functions via a registry of functions + g.warn('Unknown default value provider %s', defn); + } + // FIXME: We should coerce the value + // will implement it after we refactor the PropertyDefinition + if (propVal !== undefined) + self.__data[p] = propVal; + } + + if (propVal === undefined && persistUndefinedAsNull) { + self.__data[p] = propVal = null; + } + + // Handle complex types (JSON/Object) + if (!BASE_TYPES[type.name]) { + if (typeof self.__data[p] !== 'object' && self.__data[p]) { + try { + self.__data[p] = JSON.parse(self.__data[p] + ''); + } catch (e) { + self.__data[p] = String(self.__data[p]); + } + } + + if (type.prototype instanceof ModelBaseClass) { + if (!(self.__data[p] instanceof type) && + typeof self.__data[p] === 'object' && + self.__data[p] !== null) { + self.__data[p] = new type(self.__data[p]); + utils.applyParentProperty(self.__data[p], this); + } + } else if (type.name === 'Array' || Array.isArray(type)) { + if (!(self.__data[p] instanceof List) && + self.__data[p] !== undefined && + self.__data[p] !== null) { + self.__data[p] = List(self.__data[p], type, self); + } + } + } + } + this.trigger('initialize'); +}; + +// Implementation of persistDefaultValues property +function ignoresMatchedDefault(property) { + if (property && property.persistDefaultValues === false) { + return true; + } +} + +// Helper function for determing the applyDefaultOnWrites value of a property +function appliesDefaultsOnWrites(property) { + if (property && ('applyDefaultOnWrites' in property)) { + return property.applyDefaultOnWrites; + } + return true; +} + +/** + * Define a property on the model. + * @param {String} prop Property name + * @param {Object} params Various property configuration + */ +ModelBaseClass.defineProperty = function(prop, params) { + if (this.dataSource) { + this.dataSource.defineProperty(this.modelName, prop, params); + } else { + this.modelBuilder.defineProperty(this.modelName, prop, params); + } +}; + +/** + * Get model property type. + * @param {String} propName Property name + * @returns {String} Name of property type + */ +ModelBaseClass.getPropertyType = function(propName) { + const prop = this.definition.properties[propName]; + if (!prop) { + // The property is not part of the definition + return null; + } + if (!prop.type) { + throw new Error(g.f('Type not defined for property %s.%s', this.modelName, propName)); + // return null; + } + return prop.type.name; +}; + +/** + * Get model property type. + * @param {String} propName Property name + * @returns {String} Name of property type + */ +ModelBaseClass.prototype.getPropertyType = function(propName) { + return this.constructor.getPropertyType(propName); +}; + +/** + * Return string representation of class + * This overrides the default `toString()` method + */ +ModelBaseClass.toString = function() { + return '[Model ' + this.modelName + ']'; +}; + +/** + * Convert model instance to a plain JSON object. + * Returns a canonical object representation (no getters and setters). + * + * @param {Boolean} onlySchema Restrict properties to dataSource only. Default is false. If true, the function returns only properties defined in the schema; Otherwise it returns all enumerable properties. + * @param {Boolean} removeHidden Boolean flag as part of the transformation. If true, then hidden properties should not be brought out. + * @param {Boolean} removeProtected Boolean flag as part of the transformation. If true, then protected properties should not be brought out. + * @returns {Object} returns Plain JSON object + */ +ModelBaseClass.prototype.toObject = function(onlySchema, removeHidden, removeProtected) { + if (typeof onlySchema === 'object' && onlySchema != null) { + const options = onlySchema; + onlySchema = options.onlySchema; + removeHidden = options.removeHidden; + removeProtected = options.removeProtected; + } + if (onlySchema === undefined) { + onlySchema = true; + } + const data = {}; + const self = this; + const Model = this.constructor; + + // if it is already an Object + if (Model === Object) { + return self; + } + + const strict = this.__strict; + const schemaLess = (strict === false) || !onlySchema; + const persistUndefinedAsNull = Model.definition.settings.persistUndefinedAsNull; + + const props = Model.definition.properties; + let keys = Object.keys(props); + let propertyName, val; + + for (let i = 0; i < keys.length; i++) { + propertyName = keys[i]; + val = self[propertyName]; + + // Exclude functions + if (typeof val === 'function') { + continue; + } + // Exclude hidden properties + if (removeHidden && Model.isHiddenProperty(propertyName)) { + continue; + } + + if (removeProtected && Model.isProtectedProperty(propertyName)) { + continue; + } + + if (val instanceof List) { + data[propertyName] = val.toObject(!schemaLess, removeHidden, true); + } else { + if (val !== undefined && val !== null && val.toObject) { + data[propertyName] = val.toObject(!schemaLess, removeHidden, true); + } else { + if (val === undefined && persistUndefinedAsNull) { + val = null; + } + data[propertyName] = val; + } + } + } + + if (schemaLess) { + // Find its own properties which can be set via myModel.myProperty = 'myValue'. + // If the property is not declared in the model definition, no setter will be + // triggered to add it to __data + keys = Object.keys(self); + let size = keys.length; + for (let i = 0; i < size; i++) { + propertyName = keys[i]; + if (props[propertyName]) { + continue; + } + if (propertyName.indexOf('__') === 0) { + continue; + } + if (removeHidden && Model.isHiddenProperty(propertyName)) { + continue; + } + if (removeProtected && Model.isProtectedProperty(propertyName)) { + continue; + } + if (data[propertyName] !== undefined) { + continue; + } + val = self[propertyName]; + if (val !== undefined) { + if (typeof val === 'function') { + continue; + } + if (val !== null && val.toObject) { + data[propertyName] = val.toObject(!schemaLess, removeHidden, true); + } else { + data[propertyName] = val; + } + } else if (persistUndefinedAsNull) { + data[propertyName] = null; + } + } + // Now continue to check __data + keys = Object.keys(self.__data); + size = keys.length; + for (let i = 0; i < size; i++) { + propertyName = keys[i]; + if (propertyName.indexOf('__') === 0) { + continue; + } + if (data[propertyName] === undefined) { + if (removeHidden && Model.isHiddenProperty(propertyName)) { + continue; + } + if (removeProtected && Model.isProtectedProperty(propertyName)) { + continue; + } + const ownVal = self[propertyName]; + // The ownVal can be a relation function + val = (ownVal !== undefined && (typeof ownVal !== 'function')) ? ownVal : self.__data[propertyName]; + if (typeof val === 'function') { + continue; + } + + if (val !== undefined && val !== null && val.toObject) { + data[propertyName] = val.toObject(!schemaLess, removeHidden, true); + } else if (val === undefined && persistUndefinedAsNull) { + data[propertyName] = null; + } else { + data[propertyName] = val; + } + } + } + } + + return data; +}; + +/** + * Convert an array of strings into an object as the map + * @param {string[]} arr An array of strings + */ +function asObjectMap(arr) { + const obj = {}; + if (Array.isArray(arr)) { + for (let i = 0; i < arr.length; i++) { + obj[arr[i]] = true; + } + return obj; + } + return arr || obj; +} +/** + * Checks if property is protected. + * @param {String} propertyName Property name + * @returns {Boolean} true or false if protected or not. + */ +ModelBaseClass.isProtectedProperty = function(propertyName) { + const settings = (this.definition && this.definition.settings) || {}; + const protectedProperties = settings.protectedProperties || settings.protected; + settings.protectedProperties = asObjectMap(protectedProperties); + return settings.protectedProperties[propertyName]; +}; + +/** + * Checks if property is hidden. + * @param {String} propertyName Property name + * @returns {Boolean} true or false if hidden or not. + */ +ModelBaseClass.isHiddenProperty = function(propertyName) { + const settings = (this.definition && this.definition.settings) || {}; + const hiddenProperties = settings.hiddenProperties || settings.hidden; + settings.hiddenProperties = asObjectMap(hiddenProperties); + return settings.hiddenProperties[propertyName]; +}; + +ModelBaseClass.prototype.toJSON = function() { + return this.toObject(false, true, false); +}; + +ModelBaseClass.prototype.fromObject = function(obj) { + for (const key in obj) { + this[key] = obj[key]; + } +}; + +/** + * Reset dirty attributes. + * This method does not perform any database operations; it just resets the object to its + * initial state. + */ +ModelBaseClass.prototype.reset = function() { + const obj = this; + for (const k in obj) { + if (k !== 'id' && !obj.constructor.dataSource.definitions[obj.constructor.modelName].properties[k]) { + delete obj[k]; + } + } +}; + +// Node v0.11+ allows custom inspect functions to return an object +// instead of string. That way options like `showHidden` and `colors` +// can be preserved. +const versionParts = process.versions && process.versions.node ? + process.versions.node.split(/\./g).map(function(v) { return +v; }) : + [1, 0, 0]; // browserify ships 1.0-compatible version of util.inspect + +const INSPECT_SUPPORTS_OBJECT_RETVAL = + versionParts[0] > 0 || + versionParts[1] > 11 || + (versionParts[0] === 11 && versionParts[1] >= 14); + +ModelBaseClass.prototype.inspect = function(depth) { + if (INSPECT_SUPPORTS_OBJECT_RETVAL) + return this.__data; + + // Workaround for older versions + // See also https://github.com/joyent/node/commit/66280de133 + return util.inspect(this.__data, { + showHidden: false, + depth: depth, + colors: false, + }); +}; + +if (util.inspect.custom) { + // Node.js 12+ no longer recognizes "inspect" method, + // it uses "inspect.custom" symbol as the key instead + // TODO(semver-major) always use the symbol key only (requires Node.js 8+). + ModelBaseClass.prototype[util.inspect.custom] = ModelBaseClass.prototype.inspect; +} + +/** + * + * @param {String} anotherClass could be string or class. Name of the class or the class itself + * @param {Object} options An object to control the instantiation + * @returns {ModelClass} + */ +ModelBaseClass.mixin = function(anotherClass, options) { + if (typeof anotherClass === 'string') { + this.modelBuilder.mixins.applyMixin(this, anotherClass, options); + } else { + if (anotherClass.prototype instanceof ModelBaseClass) { + const props = anotherClass.definition.properties; + for (const i in props) { + if (this.definition.properties[i]) { + continue; + } + this.defineProperty(i, props[i]); + } + } + return jutil.mixin(this, anotherClass, options); + } +}; + +ModelBaseClass.prototype.getDataSource = function() { + return this.__dataSource || this.constructor.dataSource; +}; + +ModelBaseClass.getDataSource = function() { + return this.dataSource; +}; + +ModelBaseClass.prototype.setStrict = function(strict) { + this.__strict = strict; +}; + +/** + * + * `getMergePolicy()` provides model merge policies to apply when extending + * a child model from a base model. Such a policy drives the way parent/child model + * properties/settings are merged/mixed-in together. + * + * Below is presented the expected merge behaviour for each option. + * NOTE: This applies to top-level settings properties + * + * + * - Any + * - `{replace: true}` (default): child replaces the value from parent + * - assignin `null` on child setting deletes the inherited setting + * + * - Arrays: + * - `{replace: false}`: unique elements of parent and child cumulate + * - `{rank: true}` adds the model inheritance rank to array + * elements of type Object {} as internal property `__rank` + * + * - Object {}: + * - `{replace: false}`: deep merges parent and child objects + * - `{patch: true}`: child replaces inner properties from parent + * + * + * The recommended built-in merge policy is as follows. It is returned by getMergePolicy() + * when calling the method with option `{configureModelMerge: true}`. + * + * ``` + * { + * description: {replace: true}, // string or array + * options: {patch: true}, // object + * hidden: {replace: false}, // array + * protected: {replace: false}, // array + * indexes: {patch: true}, // object + * methods: {patch: true}, // object + * mixins: {patch: true}, // object + * relations: {patch: true}, // object + * scope: {replace: true}, // object + * scopes: {patch: true}, // object + * acls: {rank: true}, // array + * // this setting controls which child model property's value allows deleting + * // a base model's property + * __delete: null, + * // this setting controls the default merge behaviour for settings not defined + * // in the mergePolicy specification + * __default: {replace: true}, + * } + * ``` + * + * The legacy built-in merge policy is as follows, it is retuned by `getMergePolicy()` + * when avoiding option `configureModelMerge`. + * NOTE: it also provides the ACLs ranking in addition to the legacy behaviour, as well + * as fixes for settings 'description' and 'relations': matching relations from child + * replace relations from parents. + * + * ``` + * { + * description: {replace: true}, // string or array + * properties: {patch: true}, // object + * hidden: {replace: false}, // array + * protected: {replace: false}, // array + * relations: {acls: true}, // object + * acls: {rank: true}, // array + * } + * ``` + * + * + * `getMergePolicy()` can be customized using model's setting `configureModelMerge` as follows: + * + * ``` json + * { + * // .. + * options: { + * configureModelMerge: { + * // merge options + * } + * } + * // .. + * } + * ``` + * + * NOTE: mergePolicy parameter can also defined at JSON model definition root + * + * `getMergePolicy()` method can also be extended programmatically as follows: + * + * ``` js + * myModel.getMergePolicy = function(options) { + * const origin = myModel.base.getMergePolicy(options); + * return Object.assign({}, origin, { + * // new/overriding options + * }); + * }; + * ``` + * + * @param {Object} options option `configureModelMerge` can be used to alter the + * returned merge policy: + * - `configureModelMerge: true` will have the method return the recommended merge policy. + * - `configureModelMerge: {..}` will actually have the method return the provided object. + * - not providing this options will have the method return a merge policy emulating the + * the model merge behaviour up to datasource-juggler v3.6.1, as well as the ACLs ranking. + * @returns {Object} mergePolicy The model merge policy to apply when using the + * current model as base class for a child model + */ +ModelBaseClass.getMergePolicy = function(options) { + // NOTE: merge policy equivalent to datasource-juggler behaviour up to v3.6.1 + // + fix for description arrays that should not be merged + // + fix for relations that should patch matching relations + // + ranking of ACLs + let mergePolicy = { + description: {replace: true}, // string or array + properties: {patch: true}, // object + hidden: {replace: false}, // array + protected: {replace: false}, // array + relations: {patch: true}, // object + acls: {rank: true}, // array + }; + + const config = (options || {}).configureModelMerge; + + if (config === true) { + // NOTE: recommended merge policy from datasource-juggler v3.6.2 + mergePolicy = { + description: {replace: true}, // string or array + options: {patch: true}, // object + // properties: {patch: true}, // object // NOTE: not part of configurable merge + hidden: {replace: false}, // array + protected: {replace: false}, // array + indexes: {patch: true}, // object + methods: {patch: true}, // object + mixins: {patch: true}, // object + // validations: {patch: true}, // object // NOTE: not implemented + relations: {patch: true}, // object + scope: {replace: true}, // object + scopes: {patch: true}, // object + acls: {rank: true}, // array + // this option controls which value assigned on child model allows deleting + // a base model's setting + __delete: null, + // this option controls the default merge behaviour for settings not defined + // in the mergePolicy specification + __default: {replace: true}, + }; + } + + // override mergePolicy with provided model setting if required + if (config && typeof config === 'object' && !Array.isArray(config)) { + // config is an object + mergePolicy = config; + } + + return mergePolicy; +}; + +/** + * Gets properties defined with 'updateOnly' flag set to true from the model. This flag is also set to true + * internally for the id property, if this property is generated and IdInjection is true. + * @returns {updateOnlyProps} List of properties with updateOnly set to true. + */ + +ModelBaseClass.getUpdateOnlyProperties = function() { + const props = this.definition.properties; + return Object.keys(props).filter(key => props[key].updateOnly); +}; + +// Mix in utils +jutil.mixin(ModelBaseClass, DataAccessUtils); + +// Mixin observer +jutil.mixin(ModelBaseClass, Observer); + +jutil.mixin(ModelBaseClass, Hookable); +jutil.mixin(ModelBaseClass, validations.Validatable); diff --git a/types/.model-builder.d.ts.swp b/types/.model-builder.d.ts.swp new file mode 100644 index 0000000000000000000000000000000000000000..681c4f615501707c8f7f51d1d2c832e9eef91214 GIT binary patch literal 12288 zcmeHNO>f*p7@oEqXrVyMf$LBzB}Dtt6fWK6D`^v{NNu1=D^L`vUVC=qV0$djIP4OZ z4{_ojzyXPK1s5(zaD^KZCy;`Gp8%Xdq8FZb#$J0jjarVa#?lkd`u%?1*Rxt>dgpFj zzQQlG&M{n1F!ub%$Na?~A7Vc|#aOOHMvw1y^`%mG(;@kL~}!61ScJxYaY$1)I6vzLcaAhK4_()X0d zz2xe290Ly?11H&Y=NHYZ{>+)D`ID!wKX@g&iW~!u0mp!2z%k$$a11yG90QJlLmANN z5%xZ$M+=SR;(lc2UVL;n$ADwNG2j?*3^)cH1C9a5fMdWh;23ZWI0o)R2E2f=rH2_4 z$B{h#|6l(8-+!F3Tfh)F4V(ggdyKJPfnR`q;B#OIybY`Zr+{O??URiC4*U#!1$+s7 z0elF20K5pi0Nj3*v44TTfPLU=;8S1>Yy$(}I`9&37B~w0^9W;q1K$GQ0DHhYz%}4? z;5@JhoB>V%f1Y6Md*D0Z7Vru15s(0H0&f6kf#bj*;NU0VGhhG^CjsCt$ADwNG2j?* z3^)cH1OJimPlrBN{Q+{n7=%iTjMVO6 zRu@m%EhVE4H@;g~)*QaNzIL^g(7Gnt5JO#xXicJao3C#yUEAPsI1H1f2si{HQK3u} z?`JZeErr48*$y|gEqh8_j69{t6X!D3WY*zpc3~dwD4BFPa$M`}2wykM!^DeGtS%Px$fAu%wS%*QkaWKe1} zc$&#nWICi#nIr@cyD|)neIWW_B5rt5UQebVjC)ZC=G7$j=2qQyvgWvoNTW;SFeEx^ z5e+k?##Dwzaxif`wwb+V3mBCxGL_DZL+WKX42&eFg~qiKTBG+=f|0HlpY3>5^px@< z9tg7;n^W4$&<}m|E=XmAEH@K-d8yl8Qif%=tpwkjNMKD@d6_m`UL` z`(C8PO(SeCEZ|=9+~IrpWa=|RfVn&5t*RL7+Se+k@x7z|fttyHg4zwSNUeOE%Q>(| zy1PsaQ<-VxgkENXn~oOzSrxkz1&XiiVvbhgP-CA&9c+vraEBV3{b*upx=hrQT^O5) zdsUYjvtv*^sEwP|n9OHoYXgn#COOx#C)Ve>Rg{_UfQH?!d1XQTsk+5vD`=EkXiXbO S=wq4G)u(tV-3lU$y#5F0+LE6D literal 0 HcmV?d00001 diff --git a/types/connector.d.ts b/types/connector.d.ts index 1343a18fc..16c13b141 100644 --- a/types/connector.d.ts +++ b/types/connector.d.ts @@ -27,10 +27,18 @@ import {ModelUtilsOptions} from './model-utils'; export interface ConnectorSettings extends Options { name?: string; /** + * The connector of which the {@Link DataSource} will attach. + * + * @remarks * Overrides {@link ConnectorSettings.adapter} if defined. */ connector?: ConnectorExport | string; /** + * {@inheritDoc ConnectorSettings.connector} + * + * @remarks + * This setting is kept for backwards-compatibility with JugglingDB. + * * @deprecated Use {@link ConnectorSettings.connector} instead. */ adapter?: ConnectorExport | string; @@ -308,15 +316,17 @@ export interface Connector { * Get the connector's types collection. * * @remarks - * For example, ['db', 'nosql', 'mongodb'] would be represent a datasource of - * type 'db', with a subtype of 'nosql', and would use the 'mongodb' connector. + * For example, `['db', 'nosql', 'mongodb']` would be represent a datasource + * of type 'db', with a subtype of 'nosql', and would use the 'mongodb' + * connector. * - * Alternatively, ['rest'] would be a different type altogether, and would have - * no subtype. + * Alternatively, `['rest']` would be a different type altogether, and would + * have no subtype. * * Note that returning a comma-delimeted string (e.g. `'db,nosql,mongodb'`) is * deprecated and strongly not recommended. It is kept for - * backwards-compatibility only. + * backwards-compatibility only. {@Link DataSource.getTypes} will convert such + * return values to an array. * * @returns The connector's type collection. */ @@ -614,12 +624,14 @@ export interface Connector { options: IDPropertiesDiscoveryOptions, ): DiscoveredPrimaryKeys; - /**discover. + /** * Discover foreign keys for a given owner/model name. * * @param modelName Target model name * @param options Discovery options * @param cb Callback function + * + * {@see Connector.discoverExportedForeignKeys} */ discoverForeignKeys?( modelName: string[], @@ -643,6 +655,8 @@ export interface Connector { * @param modelName Target model name * @param options Discovery options * @param cb Callback function + * + * {@see Connector.discoverForeignKeys} */ discoverExportedForeignKeys?( modelName: string, @@ -683,6 +697,12 @@ export interface Connector { /** * Freeze the datasource. Behaviour depends on the {@link Connector}. + * + * @remarks + * This is called by the {@Link DataSource} of which the Connector is attached + * to during a schema migration. + * + * {@see Connector.freezeDataSource} */ freezeDataSource?(): void; diff --git a/types/model.d.ts b/types/model.d.ts index a838305da..940851023 100644 --- a/types/model.d.ts +++ b/types/model.d.ts @@ -10,6 +10,8 @@ import {Listener, OperationHookContext} from './observer'; import {ModelUtilsOptions} from './model-utils'; import registerModelTypes = require('./types'); import { ModelBuilder } from './model-builder'; +import { PersistedModel } from './persisted-model'; +import { RelationDefinition } from './relation'; /** * Property types @@ -246,6 +248,10 @@ export interface ModelSettings plural?: string; + /** + * @remarks + * Non-enumerable property. + */ http?: { path?: string; }; @@ -256,6 +262,15 @@ export interface ModelSettings */ models?: (string | ModelBaseClass)[]; + relations?: { + [relationName: string]: RelationDefinition + }; + + /** + * @deprecaed Use {@Link ModelSettings.reations} instead. + */ + relationships?: ModelSettings['relations']; + scope?: AnyObject | Function; /** @@ -485,24 +500,66 @@ interface ModelMergePolicyOptions { */ export declare class ModelBase { /** + * @remarks + * Non-enumerable property. + * + * Handled by {@Link ModelBuilder} + * * @deprecated */ - static dataSource?: DataSource; + static dataSource?: DataSource | null; static modelName: string; static definition: ModelDefinition; static hideInternalProperties?: boolean; - static readonly base: typeof ModelBase; + + /** + * @remarks + * Non-enumerable property. + * + * Handled by {@Link ModelBuilder} + */ + static readonly base?: typeof ModelBase; + + /** + * @remarks + * Non-enumerable property. + * + * Handled by {@Link ModelBuilder} + */ + static pluralModelName?: string; + + /** + * @remarks + * Non-enumerable property. + * + * Handled by {@Link ModelBuilder} and {@Link DataSource}. + */ + static relations?: {[relationName: string]: RelationDefinition} + + /** + * @remarks + * Non-enumerable property. + * + * Handled by {@Link ModelBuilder} + */ + static modelBuilder?: ModelBuilder; /** * Model inheritance rank * * @remarks - * This is used by {@link ModelBuilder}. + * Non-enumerable property. + * + * Handled by {@link ModelBuilder}. * * @internal */ static __rank?: number; + static __cachedRelations?: { + [relationName: string]: PersistedModel[] + }; + /** * Initializes the model instance with a list of properties. * diff --git a/types/validation-mixin.d.ts b/types/validation-mixin.d.ts index 4fe65bceb..e0dbda452 100644 --- a/types/validation-mixin.d.ts +++ b/types/validation-mixin.d.ts @@ -274,8 +274,8 @@ export interface Validatable { /** * Validate if a value for a property is a Date. * - * Example - * ``` + * @example + * ```typescript * User.validatesDateOf('today', {message: 'today is not a date!'}); * ``` * From 7d59e16d6c7796516bed6efb63dffde8c1c61757 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Fri, 1 Sep 2023 23:52:15 +0800 Subject: [PATCH 26/26] chore: more misc changes Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- deleteme/__test__.d.ts | 1 + deleteme/__test__.js | 91 ++++++++++++++++++++ deleteme/__test__.js.map | 1 + deleteme/__tests__/date-string.spec.d.ts | 1 + deleteme/__tests__/date-string.spec.js | 13 +++ deleteme/__tests__/date-string.spec.js.map | 1 + deleteme/__tests__/geopoint.spec.d.ts | 1 + deleteme/__tests__/geopoint.spec.js | 22 +++++ deleteme/__tests__/geopoint.spec.js.map | 1 + deleteme/__tests__/model-builder.spec.d.ts | 1 + deleteme/__tests__/model-builder.spec.js | 10 +++ deleteme/__tests__/model-builder.spec.js.map | 1 + deleteme/__tests__/types.spec.d.ts | 1 + deleteme/__tests__/types.spec.js | 18 ++++ deleteme/__tests__/types.spec.js.map | 1 + graph.txt | 11 +++ package-lock.json | 33 +++++-- package.json | 3 +- test.js | 5 ++ tsconfig.compat.json | 9 ++ types/__tests__/types.spec.ts | 7 +- types/connectors/transient.d.ts | 21 ++--- types/datasource.d.ts | 21 +++-- types/jutil.d.ts | 49 +++++++++-- types/model.d.ts | 14 +-- 25 files changed, 294 insertions(+), 43 deletions(-) create mode 100644 deleteme/__test__.d.ts create mode 100644 deleteme/__test__.js create mode 100644 deleteme/__test__.js.map create mode 100644 deleteme/__tests__/date-string.spec.d.ts create mode 100644 deleteme/__tests__/date-string.spec.js create mode 100644 deleteme/__tests__/date-string.spec.js.map create mode 100644 deleteme/__tests__/geopoint.spec.d.ts create mode 100644 deleteme/__tests__/geopoint.spec.js create mode 100644 deleteme/__tests__/geopoint.spec.js.map create mode 100644 deleteme/__tests__/model-builder.spec.d.ts create mode 100644 deleteme/__tests__/model-builder.spec.js create mode 100644 deleteme/__tests__/model-builder.spec.js.map create mode 100644 deleteme/__tests__/types.spec.d.ts create mode 100644 deleteme/__tests__/types.spec.js create mode 100644 deleteme/__tests__/types.spec.js.map create mode 100644 graph.txt create mode 100644 test.js create mode 100644 tsconfig.compat.json diff --git a/deleteme/__test__.d.ts b/deleteme/__test__.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/deleteme/__test__.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/deleteme/__test__.js b/deleteme/__test__.js new file mode 100644 index 000000000..762d98f59 --- /dev/null +++ b/deleteme/__test__.js @@ -0,0 +1,91 @@ +"use strict"; +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT +Object.defineProperty(exports, "__esModule", { value: true }); +// A test file to verify types described by our .d.ts files. +// The code in this file is only compiled, we don't run it via Mocha. +const __1 = require(".."); +const db = new __1.DataSource('db', { connector: 'memory' }); +//------- +// ModelBase should provide ObserverMixin APIs as static methods +//------- +// +(function () { + const Data = db.createModel('Data'); + // An operation hook can be installed + Data.observe('before save', async (ctx) => { }); + // Context is typed and provides `Model` property + Data.observe('before save', async (ctx) => { + console.log(ctx.Model.modelName); + }); + // ModelBaseClass can be assigned to `typeof ModelBase` + // Please note that both `ModelBaseClass` and typeof ModelBase` + // are different ways how to describe a class constructor of a model. + // In this test we are verifying that the value returned by `createModel` + // can be assigned to both types. + const modelTypeof = Data; + const modelCls = modelTypeof; +}); +//------- +// PersistedModel should provide ObserverMixin APIs as static methods +//------- +(function () { + const Product = db.createModel('Product', { name: String }, { strict: true }); + // It accepts async function + Product.observe('before save', async (ctx) => { }); + // It accepts callback-based function + Product.observe('before save', (ctx, next) => { + next(new Error('test error')); + }); + // ctx.Model is a PersistedModel class constructor + Product.observe('before save', async (ctx) => { + await ctx.Model.findOne(); + }); + // PersistedModelClass can be assigned to `typeof PersistedModel` + // Please note that both `PersistedModelClass` and typeof PersistedModel` + // are different ways how to describe a class constructor of a model. + // In this test we are verifying that the value returned by `createModel` + // can be assigned to both types. + const modelTypeof = Product; + const modelCls = modelTypeof; +}); +//------- +// KeyValueModel should provide ObserverMixin APIs as static methods +//------- +(function () { + const kvdb = new __1.DataSource({ connector: 'kv-memory' }); + const CacheItem = kvdb.createModel('CacheItem'); + // An operation hook can be installed + CacheItem.observe('before save', async (ctx) => { }); + // ctx.Model is a KeyValueModel class constructor + CacheItem.observe('before save', async (ctx) => { + await ctx.Model.expire('key', 100); + }); +}); +//------- +// DataSource supports different `execute` styles +//------- +(async function () { + // SQL style + const tx = await db.beginTransaction(); + await db.execute('SELECT * FROM Product WHERE count > ?', [10], { + transaction: tx, + }); + await tx.commit(); + // MongoDB style + await db.execute('MyCollection', 'aggregate', [ + { $lookup: { /* ... */} }, + { $unwind: '$data' }, + { $out: 'tempData' } + ]); + // Neo4J style + await db.execute({ + query: 'MATCH (u:User {email: {email}}) RETURN u', + params: { + email: 'alice@example.com', + }, + }); +}); +//# sourceMappingURL=__test__.js.map \ No newline at end of file diff --git a/deleteme/__test__.js.map b/deleteme/__test__.js.map new file mode 100644 index 000000000..ea60f3f2c --- /dev/null +++ b/deleteme/__test__.js.map @@ -0,0 +1 @@ +{"version":3,"file":"__test__.js","sourceRoot":"","sources":["../types/__test__.ts"],"names":[],"mappings":";AAAA,iDAAiD;AACjD,2CAA2C;AAC3C,+CAA+C;AAC/C,gEAAgE;;AAEhE,4DAA4D;AAC5D,qEAAqE;AAErE,0BAOY;AAEZ,MAAM,EAAE,GAAG,IAAI,cAAU,CAAC,IAAI,EAAE,EAAC,SAAS,EAAE,QAAQ,EAAC,CAAC,CAAC;AAEvD,SAAS;AACT,gEAAgE;AAChE,SAAS;AACT,EAAE;AACF,CAAC;IACC,MAAM,IAAI,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAEpC,qCAAqC;IACrC,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,EAAC,GAAG,EAAC,EAAE,GAAE,CAAC,CAAC,CAAC;IAE7C,iDAAiD;IACjD,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,EAAC,GAAG,EAAC,EAAE;QACtC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,uDAAuD;IACvD,+DAA+D;IAC/D,qEAAqE;IACrE,yEAAyE;IACzE,iCAAiC;IACjC,MAAM,WAAW,GAAqB,IAAI,CAAC;IAC3C,MAAM,QAAQ,GAAmB,WAAW,CAAC;AAC/C,CAAC,CAAC,CAAC;AAEH,SAAS;AACT,qEAAqE;AACrE,SAAS;AACT,CAAC;IACC,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAC5B,SAAS,EACT,EAAC,IAAI,EAAE,MAAM,EAAC,EACd,EAAC,MAAM,EAAE,IAAI,EAAC,CACf,CAAC;IAEF,4BAA4B;IAC5B,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,EAAC,GAAG,EAAC,EAAE,GAAE,CAAC,CAAC,CAAC;IAEhD,qCAAqC;IACrC,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;QAC3C,IAAI,CAAC,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,kDAAkD;IAClD,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,EAAC,GAAG,EAAC,EAAE;QACzC,MAAM,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,iEAAiE;IACjE,yEAAyE;IACzE,qEAAqE;IACrE,yEAAyE;IACzE,iCAAiC;IACjC,MAAM,WAAW,GAA0B,OAAO,CAAC;IACnD,MAAM,QAAQ,GAAwB,WAAW,CAAC;AACpD,CAAC,CAAC,CAAC;AAEH,SAAS;AACT,oEAAoE;AACpE,SAAS;AACT,CAAC;IACC,MAAM,IAAI,GAAG,IAAI,cAAU,CAAC,EAAC,SAAS,EAAE,WAAW,EAAC,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAuB,WAAW,CAAC,CAAC;IAEtE,qCAAqC;IACrC,SAAS,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,EAAC,GAAG,EAAC,EAAE,GAAE,CAAC,CAAC,CAAC;IAElD,iDAAiD;IACjD,SAAS,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,EAAC,GAAG,EAAC,EAAE;QAC3C,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,SAAS;AACT,iDAAiD;AACjD,SAAS;AACT,CAAC,KAAK;IACJ,YAAY;IACZ,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,gBAAgB,EAAE,CAAC;IACvC,MAAM,EAAE,CAAC,OAAO,CAAC,uCAAuC,EAAE,CAAC,EAAE,CAAC,EAAE;QAC9D,WAAW,EAAE,EAAE;KAChB,CAAC,CAAC;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC;IAElB,gBAAgB;IAChB,MAAM,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,WAAW,EAAE;QAC5C,EAAC,OAAO,EAAE,EAAE,SAAS,CAAE,EAAC;QACxB,EAAC,OAAO,EAAE,OAAO,EAAC;QAClB,EAAC,IAAI,EAAE,UAAU,EAAC;KACnB,CAAC,CAAC;IAEH,cAAc;IACd,MAAM,EAAE,CAAC,OAAO,CAAC;QACf,KAAK,EAAE,0CAA0C;QACjD,MAAM,EAAE;YACN,KAAK,EAAE,mBAAmB;SAC3B;KACF,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/deleteme/__tests__/date-string.spec.d.ts b/deleteme/__tests__/date-string.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/deleteme/__tests__/date-string.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/deleteme/__tests__/date-string.spec.js b/deleteme/__tests__/date-string.spec.js new file mode 100644 index 000000000..cfd37a2b4 --- /dev/null +++ b/deleteme/__tests__/date-string.spec.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const util_1 = require("util"); +const date_string_1 = require("../date-string"); +let stringTypeGuard; +const dateString = new date_string_1.DateString('2020-01-01'); +date_string_1.DateString('2020-01-01'); +date_string_1.DateString(dateString); +stringTypeGuard = dateString.toJSON().when; +stringTypeGuard = dateString.toString(); +stringTypeGuard = dateString.inspect(); +stringTypeGuard = dateString[util_1.inspect.custom](); +//# sourceMappingURL=date-string.spec.js.map \ No newline at end of file diff --git a/deleteme/__tests__/date-string.spec.js.map b/deleteme/__tests__/date-string.spec.js.map new file mode 100644 index 000000000..b7b89028e --- /dev/null +++ b/deleteme/__tests__/date-string.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"date-string.spec.js","sourceRoot":"","sources":["../../types/__tests__/date-string.spec.ts"],"names":[],"mappings":";;AAAA,+BAA+B;AAC/B,gDAA0C;AAE1C,IAAI,eAAuB,CAAC;AAE5B,MAAM,UAAU,GAAG,IAAI,wBAAU,CAAC,YAAY,CAAC,CAAC;AAChD,wBAAU,CAAC,YAAY,CAAC,CAAC;AACzB,wBAAU,CAAC,UAAU,CAAC,CAAC;AACvB,eAAe,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC;AAC3C,eAAe,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAC;AACxC,eAAe,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC;AACvC,eAAe,GAAG,UAAU,CAAC,cAAO,CAAC,MAAM,CAAC,EAAE,CAAC"} \ No newline at end of file diff --git a/deleteme/__tests__/geopoint.spec.d.ts b/deleteme/__tests__/geopoint.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/deleteme/__tests__/geopoint.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/deleteme/__tests__/geopoint.spec.js b/deleteme/__tests__/geopoint.spec.js new file mode 100644 index 000000000..213fd8f92 --- /dev/null +++ b/deleteme/__tests__/geopoint.spec.js @@ -0,0 +1,22 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const geo_1 = require("../geo"); +let numberTypeGuard; +new geo_1.GeoPoint(123, 456); +new geo_1.GeoPoint('123', 456); +new geo_1.GeoPoint(123, '456'); +new geo_1.GeoPoint('123', '456'); +new geo_1.GeoPoint([123, 456]); +new geo_1.GeoPoint(['123', '456']); +new geo_1.GeoPoint(['123', 456]); +new geo_1.GeoPoint([123, '456']); +new geo_1.GeoPoint({ lat: 123, lng: 456 }); +new geo_1.GeoPoint({ lat: '123', lng: 456 }); +new geo_1.GeoPoint({ lat: 123, lng: '456' }); +new geo_1.GeoPoint({ lat: '123', lng: '456' }); +numberTypeGuard = geo_1.GeoPoint.distanceBetwen([123, 456], [123, 456]); +numberTypeGuard = geo_1.GeoPoint.distanceBetwen([123, 456], [123, 456], { type: geo_1.GeoDistanceUnit.degrees }); +const geoPoint = new geo_1.GeoPoint(123, 456); +numberTypeGuard = geoPoint.distanceTo([123, 456]); +numberTypeGuard = geoPoint.distanceTo([123, 456], { type: geo_1.GeoDistanceUnit.degrees }); +//# sourceMappingURL=geopoint.spec.js.map \ No newline at end of file diff --git a/deleteme/__tests__/geopoint.spec.js.map b/deleteme/__tests__/geopoint.spec.js.map new file mode 100644 index 000000000..d8d9a7e1c --- /dev/null +++ b/deleteme/__tests__/geopoint.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"geopoint.spec.js","sourceRoot":"","sources":["../../types/__tests__/geopoint.spec.ts"],"names":[],"mappings":";;AAAA,gCAAqE;AAErE,IAAI,eAAuB,CAAC;AAE5B,IAAI,cAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AACvB,IAAI,cAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACzB,IAAI,cAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;AACzB,IAAI,cAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAE3B,IAAI,cAAQ,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AACzB,IAAI,cAAQ,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAC7B,IAAI,cAAQ,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;AAC3B,IAAI,cAAQ,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;AAE3B,IAAI,cAAQ,CAAC,EAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAC,CAAC,CAAC;AACnC,IAAI,cAAQ,CAAC,EAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAC,CAAC,CAAA;AACpC,IAAI,cAAQ,CAAC,EAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAC,CAAC,CAAA;AACpC,IAAI,cAAQ,CAAC,EAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAC,CAAC,CAAC;AAEvC,eAAe,GAAG,cAAQ,CAAC,cAAc,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAClE,eAAe,GAAG,cAAQ,CAAC,cAAc,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAC,IAAI,EAAE,qBAAe,CAAC,OAAO,EAAC,CAAC,CAAC;AAEnG,MAAM,QAAQ,GAAG,IAAI,cAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AACxC,eAAe,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;AACjD,eAAe,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAC,IAAI,EAAE,qBAAe,CAAC,OAAO,EAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/deleteme/__tests__/model-builder.spec.d.ts b/deleteme/__tests__/model-builder.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/deleteme/__tests__/model-builder.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/deleteme/__tests__/model-builder.spec.js b/deleteme/__tests__/model-builder.spec.js new file mode 100644 index 000000000..c3f45cf36 --- /dev/null +++ b/deleteme/__tests__/model-builder.spec.js @@ -0,0 +1,10 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const model_builder_1 = require("../model-builder"); +let modelBuilderTypeGuard = model_builder_1.ModelBuilder; +let typesTypeGuard; +// Test: Ensure that ModelBuilder is compliant with Types interface as +// ...ModelBuilder inherits from Types. This is to workaround TypeScript's +// ...inability to represent unorthodox "extending" from multiple classes. +typesTypeGuard = modelBuilderTypeGuard; +//# sourceMappingURL=model-builder.spec.js.map \ No newline at end of file diff --git a/deleteme/__tests__/model-builder.spec.js.map b/deleteme/__tests__/model-builder.spec.js.map new file mode 100644 index 000000000..e155b825a --- /dev/null +++ b/deleteme/__tests__/model-builder.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"model-builder.spec.js","sourceRoot":"","sources":["../../types/__tests__/model-builder.spec.ts"],"names":[],"mappings":";;AAAA,oDAAgD;AAGhD,IAAI,qBAAqB,GAAwB,4BAAY,CAAC;AAC9D,IAAI,cAA+B,CAAC;AAEpC,sEAAsE;AACtE,gFAAgF;AAChF,gFAAgF;AAChF,cAAc,GAAG,qBAAqB,CAAC"} \ No newline at end of file diff --git a/deleteme/__tests__/types.spec.d.ts b/deleteme/__tests__/types.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/deleteme/__tests__/types.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/deleteme/__tests__/types.spec.js b/deleteme/__tests__/types.spec.js new file mode 100644 index 000000000..8ea20f0a0 --- /dev/null +++ b/deleteme/__tests__/types.spec.js @@ -0,0 +1,18 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const types_1 = __importDefault(require("../types")); +let stringTypeGuard; +let voidTypeGuard; +let jsonTypeGuard; +stringTypeGuard = types_1.Types.JSON('arbitrary value'); +voidTypeGuard = types_1.Types.JSON(new types_1.Types.JSON('test')); +jsonTypeGuard = new types_1.Types.JSON('test'); +const modelTypes = {}; +types_1.default(modelTypes); +voidTypeGuard = modelTypes.registerType({}); +voidTypeGuard = modelTypes.registerType({}, ['custom name 1']); +modelTypes.schemaTypes; +//# sourceMappingURL=types.spec.js.map \ No newline at end of file diff --git a/deleteme/__tests__/types.spec.js.map b/deleteme/__tests__/types.spec.js.map new file mode 100644 index 000000000..ae65a9b6c --- /dev/null +++ b/deleteme/__tests__/types.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"types.spec.js","sourceRoot":"","sources":["../../types/__tests__/types.spec.ts"],"names":[],"mappings":";;;;;AAAA,qDAAqE;AAErE,IAAI,eAAuB,CAAC;AAC5B,IAAI,aAAmB,CAAC;AACxB,IAAI,aAAyB,CAAC;AAE9B,eAAe,GAAG,aAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;AAChD,aAAa,GAAG,aAAK,CAAC,IAAI,CAAC,IAAI,aAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;AACnD,aAAa,GAAG,IAAI,aAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACvC,MAAM,UAAU,GAAe,EAAgB,CAAC;AAChD,eAAkB,CAAC,UAAU,CAAC,CAAC;AAC/B,aAAa,GAAG,UAAU,CAAC,YAAY,CAAC,EAAU,CAAC,CAAC;AACpD,aAAa,GAAG,UAAU,CAAC,YAAY,CAAC,EAAU,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;AACvE,UAAU,CAAC,WAAW,CAAC"} \ No newline at end of file diff --git a/graph.txt b/graph.txt new file mode 100644 index 000000000..63092b504 --- /dev/null +++ b/graph.txt @@ -0,0 +1,11 @@ + + +Connector------> DataAccessObject + ^ / + | /____>_____________________>_____________ + v / / \ +DataSource -> ModelBuilder -> ModelClass -> ModelDefinition -> ModelSettings + \_____________>_____________/ | \ + | \__> ModelProperties + V + ModelBaseClass diff --git a/package-lock.json b/package-lock.json index f8987b664..d2126373d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,8 @@ "mocha": "^8.4.0", "nyc": "^15.1.0", "should": "^13.2.3", - "typescript": "^4.0.3" + "typescript": "^5.1.6", + "typescript28": "npm:typescript@2.8" }, "engines": { "node": ">=10" @@ -4869,8 +4870,23 @@ } }, "node_modules/typescript": { - "version": "4.4.2", - "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript28": { + "name": "typescript", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.4.tgz", + "integrity": "sha512-IIU5cN1mR5J3z9jjdESJbnxikTrEz3lzAw/D0Tf45jHpBp55nY31UkUvmVHoffCfKHTqJs3fCLPDxknQTTFegQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -8864,8 +8880,15 @@ } }, "typescript": { - "version": "4.4.2", - "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true + }, + "typescript28": { + "version": "npm:typescript@2.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.4.tgz", + "integrity": "sha512-IIU5cN1mR5J3z9jjdESJbnxikTrEz3lzAw/D0Tf45jHpBp55nY31UkUvmVHoffCfKHTqJs3fCLPDxknQTTFegQ==", "dev": true }, "universalify": { diff --git a/package.json b/package.json index 44600252d..465456a9d 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,8 @@ "mocha": "^8.4.0", "nyc": "^15.1.0", "should": "^13.2.3", - "typescript": "^4.0.3" + "typescript": "^5.1.6", + "typescript28": "npm:typescript@2.8" }, "dependencies": { "async": "^3.1.0", diff --git a/test.js b/test.js new file mode 100644 index 000000000..36c1c0c5e --- /dev/null +++ b/test.js @@ -0,0 +1,5 @@ +'use strict'; +const juggler = require('.'); + +const ds = new juggler.DataSource('memory'); +ds.createModel('MyModel', {'MyProp': String}); diff --git a/tsconfig.compat.json b/tsconfig.compat.json new file mode 100644 index 000000000..9367d877d --- /dev/null +++ b/tsconfig.compat.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "tsconfig.json", + "compilerOptions": { + skip + }, + "include": ["types/", "index.d.ts"], + "exclude": ["node_modules/**"] +} diff --git a/types/__tests__/types.spec.ts b/types/__tests__/types.spec.ts index 9650a9445..e23eefc19 100644 --- a/types/__tests__/types.spec.ts +++ b/types/__tests__/types.spec.ts @@ -1,5 +1,4 @@ -import * as buildModelTypes from '../types'; -import {ModelTypes, Type, Types} from '../types'; +import registerModelTypes, {ModelTypes, Type, Types} from '../types'; let stringTypeGuard: string; let voidTypeGuard: void; @@ -8,8 +7,8 @@ let jsonTypeGuard: Types.JSON; stringTypeGuard = Types.JSON('arbitrary value'); voidTypeGuard = Types.JSON(new Types.JSON('test')); jsonTypeGuard = new Types.JSON('test'); -const modelTypes: ModelTypes = {} -buildModelTypes(modelTypes); +const modelTypes: ModelTypes = {} as ModelTypes; +registerModelTypes(modelTypes); voidTypeGuard = modelTypes.registerType({} as Type); voidTypeGuard = modelTypes.registerType({} as Type, ['custom name 1']); modelTypes.schemaTypes; diff --git a/types/connectors/transient.d.ts b/types/connectors/transient.d.ts index 634f52c95..30597c575 100644 --- a/types/connectors/transient.d.ts +++ b/types/connectors/transient.d.ts @@ -1,8 +1,9 @@ import { Callback } from '../common'; -import {ConnectorInitialize, Connector, SchemaDiscoveryOptions, ConnectorSettings} from '../connector' +import {ConnectorInitialize, SchemaDiscoveryOptions, ConnectorSettings} from '../connector' import { DataAccessObject } from '../dao'; import { DataSource } from '../datasource'; import { ModelBase, Schema } from '../model'; +import {Connector} from 'loopback-connector'; export let initialize: ConnectorInitialize; @@ -13,12 +14,12 @@ export interface TransientConnectorSettings extends ConnectorSettings { export type TransientConnectorGenerateId = (model: string, data?: unknown, idName?: string) => string; -// export declare class Transient implements Connector { -// isTransaction: boolean; -// constructor(m: Transient | null, settings?: ConnectorSettings); -// onTransactionExec?: Callback; -// generateId: TransientConnectorGenerateId; -// flush(action: unknown, result?: T, callback?: Callback): void; -// exec(callback: Callback): void; -// transaction(): Transient; -// } +export declare class Transient extends Connector { + isTransaction: boolean; + constructor(m: Transient | null, settings?: TransientConnectorSettings); + onTransactionExec?: Callback; + generateId: TransientConnectorGenerateId; + flush(action: unknown, result?: T, callback?: Callback): void; + exec(callback: Callback): void; + transaction(): Transient; +} diff --git a/types/datasource.d.ts b/types/datasource.d.ts index a5dd33c0a..fbdb076ce 100644 --- a/types/datasource.d.ts +++ b/types/datasource.d.ts @@ -9,6 +9,7 @@ import { ModelBaseClass, ModelDefinition, PropertyDefinition, + PropertyType, } from './model'; import { ModelBuilder } from "./model-builder"; import {EventEmitter} from 'events'; @@ -116,6 +117,7 @@ export function DataSource( */ export declare class DataSource< CT extends Connector = Connector, + MBT extends ModelBuilder = ModelBuilder, > extends EventEmitter { name: string; settings: ConnectorSettings; @@ -129,18 +131,18 @@ export declare class DataSource< * * @deprecated Use {@link DataSource.connector} instead. */ - adapter?: BuiltConnector & CT; + adapter?: DataSource['connector']; /** * Connector instance. */ connector?: BuiltConnector & CT; - modelBuilder: ModelBuilder; + modelBuilder: MBT; - models: Record; + models: MBT['models']; - definitions: {[modelName: string]: ModelDefinition}; + definitions: MBT['definitions']; DataAccessObject: AnyObject & {prototype: AnyObject}; @@ -200,18 +202,18 @@ export declare class DataSource< */ static relationTypes: Record; + constructor(settings: ConnectorSettings, modelBuilder?: MBT); + constructor( name: string, settings?: ConnectorSettings, - modelBuilder?: ModelBuilder, + modelBuilder?: MBT, ); - constructor(settings: ConnectorSettings, modelBuilder?: ModelBuilder); - constructor( connectorModule: Connector, settings?: Omit, - modelBuilder?: ModelBuilder, + modelBuilder?: MBT, ); private setup(dsName: string, settings: ConnectorSettings): void; @@ -397,7 +399,8 @@ export declare class DataSource< */ createModel( name: string, - properties?: PropertyDefinition[], + // properties?: PropertyDefinition[], + properties?: Record, options?: ModelSettings, ): T; diff --git a/types/jutil.d.ts b/types/jutil.d.ts index 13aa6dfda..5e87e08e4 100644 --- a/types/jutil.d.ts +++ b/types/jutil.d.ts @@ -1,13 +1,46 @@ -export type InheritsOptions = { - staticProperties?: boolean, - override?: boolean, +export interface InheritsOptions { + staticProperties?: boolean; + override?: boolean; } -export type MixinOptions = InheritsOptions & { - instanceProperties?: boolean, - proxyFunctions?: boolean, +export interface MixIntoOptions { + override?: boolean } -export function inherits(newClass: T, baseClass: object, options: InheritsOptions): T; +export interface MixinOptions extends InheritsOptions, MixIntoOptions { + instanceProperties?: boolean; + proxyFunctions?: boolean; +} + +export function inherits( + newClass: T, + baseClass: object, + options: InheritsOptions, +): T; + +export function mixin< + NT extends object & {prototype?: object}, + MT extends object & {prototype?: object}, + OT extends MixinOptions>( + newClass: NT, + mixinClass: MT, + options: OT, +): (OT['staticProperties'] extends undefined | false ? {} : + ReturnType) & + (OT['instanceProperties'] extends undefined | false ? {} : + NT['prototype'] extends undefined ? {} : + ReturnType>); + +declare function mixInto< + ST extends object | undefined, + TT extends object | undefined, + OT extends MixIntoOptions, +>( + sourceScope: ST, + targetScope: TT, + options: OT, +): OT['override'] extends true + ? ST & Exclude + : TT & Exclude; -export function mixin(newClass: T, mixinClass: object, options: MixinOptions): T; +declare function mergeMixins(source: object[], target: object[]): object[]; diff --git a/types/model.d.ts b/types/model.d.ts index 940851023..bcede1d2e 100644 --- a/types/model.d.ts +++ b/types/model.d.ts @@ -132,7 +132,9 @@ export interface ColumnMetadata extends AnyObject { } /** - * Definition of model properties, for example + * Definition of model properties. + * + * @remarks * ```ts * { * name: {type: String, required: true}, @@ -233,12 +235,12 @@ export interface ModelSettings */ forceId?: boolean | 'auto'; - properties?: ModelProperties; + // properties?: ModelProperties; - /** - * @deprecated Use {@link ModelSettings.properties} instead. - */ - attributes?: ModelProperties; + // /** + // * @deprecated Use {@link ModelSettings.properties} instead. + // */ + // attributes?: ModelProperties; /** * @remarks