From d813ec7d3342fbfd8c27a193112b05c607744d1d Mon Sep 17 00:00:00 2001 From: Anders Nilsson Date: Wed, 6 Sep 2023 10:20:28 +0200 Subject: [PATCH 1/2] feat: add types for enigma npm packages --- .gitignore | 7 +- enigma.d.ts | 207 +++++++++++++++++++++++++++++++++++++++++++ error-codes.d.ts | 79 +++++++++++++++++ package.json | 3 + sense-utilities.d.ts | 38 ++++++++ 5 files changed, 332 insertions(+), 2 deletions(-) create mode 100644 enigma.d.ts create mode 100644 error-codes.d.ts create mode 100644 sense-utilities.d.ts diff --git a/.gitignore b/.gitignore index 8050a84e..add61774 100644 --- a/.gitignore +++ b/.gitignore @@ -20,5 +20,8 @@ jsdoc/ examples/run.js /enigma.js* /enigma.min* -/sense-utilities.* -/error-codes.* +/sense-utilities.js +/sense-utilities.js.map +/sense-utilities.min.* +/error-codes.js +/error-codes.min.js diff --git a/enigma.d.ts b/enigma.d.ts new file mode 100644 index 00000000..37570f1e --- /dev/null +++ b/enigma.d.ts @@ -0,0 +1,207 @@ +declare module "enigma.js" { + const enigma: EnigmaJS.Enigma; + export default enigma; +} + +declare namespace EnigmaJS { + + interface Enigma { + /** + * Create a session object. + * @returns - Returns a session. + * Note: See Configuration for the configuration options. + */ + create(config: Configuration): Session; + } + + interface Session { + /** + * Establishes the websocket against the configured URL. Eventually resolved with the QIX global interface when the connection has been established. + * @return Promise. + */ + open(): Promise; + + /** + * Closes the websocket and cleans up internal caches, also triggers the closed event on all generated APIs. + * Eventually resolved when the websocket has been closed. + * + * @param code Code number for closing event https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent + * @param reason Description of the reason for closing. + * Note: you need to manually invoke this when you want to close a session and config.suspendOnClose is true. + * @return Promise. + */ + close(code?: number, reason?: string): Promise; + + /** + * Suspends the enigma.js session by closing the websocket and rejecting all method calls until it has been resumed again. + * @return Promise. + */ + suspend(): Promise; + + /** + * Resume a previously suspended enigma.js session by re-creating the websocket and, if possible, re-open the document + * as well as refreshing the internal caches. If successful, changed events will be triggered on all generated APIs, + * and on the ones it was unable to restore, the closed event will be triggered. + * @param onlyIfAttached onlyIfAttached can be used to only allow resuming if the QIX Engine session was reattached properly. + * @return Promise. + * Note: Eventually resolved when the websocket (and potentially the previously opened document, and generated APIs) has been restored, + * rejected when it fails any of those steps, or when onlyIfAttached is true and a new QIX Engine session was created. + */ + resume(onlyIfAttached?: boolean): Promise; + + /** + * Handle opened state. This event is triggered whenever the websocket is connected and ready for communication. + * + * Handle closed state. This event is triggered when the underlying websocket is closed and config.suspendOnClose is false. + * + * Handle suspended state. This event is triggered in two cases (listed below). It is useful in scenarios where you for example + * want to block interaction in your application until you are resumed again. + * If config.suspendOnClose is true and there was a network disconnect (socked closed) + * If you ran session.suspend() + * The evt.initiator value is a string indicating what triggered the suspended state. Possible values: network, manual. + * + * Handle resumed state. This event is triggered when the session was properly resumed. + * It is useful in scenarios where you for example can close blocking modal dialogs and allow the user to interact with your application again. + * + * notification:* + * @param event - Event that triggers the function + * @param func - Called function + */ + on( + event: "opened" | "closed" | "suspended" | "resumed" | string, + func: any, + ): void; + } + + type MixinType = + | "Doc" + | "GenericObject" + | "GenericDimension" + | "GenericMeasure" + | "GenericBookmark" + | "GenericVariable" + | "CurrentSelections" + | "FieldList" + | "Field" + | "UndoInfo"; + + interface Mixin { + // Defines which Qix types this mixin should be applied to + types?: MixinType[] | MixinType; + // Mixin on a single type + type?: MixinType; + + // Init method is run once for each mixin + init(args: { config: any; api: QixObject }): void; + + // Extend mehods + extend: Record; + + // Extend mehods + overrides: Record; + } + + /** + * Engine Session Configuration + */ + interface Configuration { + /** + * Object containing the specification for the API to generate. Corresponds to a specific version of the QIX Engine API. + */ + schema: object; + /** + * String containing a proper websocket URL to QIX Engine. + */ + url: string; + /** + * A function to use when instantiating the WebSocket, mandatory for Node.js. + */ + createSocket?: (url: string) => WebSocket; + /** + * ES6-compatible Promise library. + */ + Promise?: PromiseConstructor; + /** + * Set to true if the session should be suspended instead of closed when the websocket is closed. + */ + suspendOnClose?: boolean; + /** + * Mixins to extend/augment the QIX Engine API. + * Mixins are applied in the array order. + */ + mixins?: Mixin[]; + /** + * Interceptors for augmenting requests before they are sent to QIX Engine. + * See Interceptors section for more information how each entry in this array should look like. + * Interceptors are applied in the array order. + */ + requestInterceptors?: RequestInterceptors[]; + /** + * Interceptors for augmenting responses before they are sent to QIX Engine. + * See Interceptors section for more information how each entry in this array should look like. + * Interceptors are applied in the array order. + */ + responseInterceptors?: ResponseInterceptors[]; + /** + * An object containing additional JSON-RPC request parameters. + * protocol.delta : Set to false to disable the use of the bandwidth-reducing delta protocol. + */ + protocol?: Protocol; + } + + interface ResponseInterceptors { + /** + * This method is invoked when a previous interceptor has rejected the promise, use this to handle for example errors before they are sent into mixins. + * @param session refers to the session executing the interceptor. + * @param request is the JSON-RPC request resulting in this error. You may use .retry() to retry sending it to QIX Engine. + * @param error is whatever the previous interceptor rejected with. + */ + onRejected?(session: Session, request: any, error: any): Promise; + + /** + * This method is invoked when a promise has been successfully resolved, use this to modify the result or reject the promise chain before it is sent to mixins. + * @param session refers to the session executing the interceptor. + * @param request is the JSON-RPC request resulting in this error. You may use .retry() to retry sending it to QIX Engine. + * @param error is whatever the previous interceptor resolved with. + */ + onFulfilled?(session: Session, request: any, result: any): Promise; + } + + interface RequestInterceptors { + /** + * This method is invoked when a request is about to be sent to QIX Engine. + * @param session refers to the session executing the interceptor. + * @param request is the JSON-RPC request resulting in this error. You may use .retry() to retry sending it to QIX Engine. + * @returns request the new request + */ + onFulfilled?(session: Session, request: any, result: any): any; + } + + interface Protocol { + // Set to false to disable the use of the bandwidth-reducing delta protocol. + delta?: boolean | undefined; + } + + /** + * Represents an Qix Engine object, e.g. a chart, listbox or the app + */ + interface QixObject { + session: Session; + handle: number; + id: string; + type: string; + genericType: string; + /** + * register a function for events + * @param event - function called if this event occures + * @param func - function that is called + */ + on(event: "changed" | "closed", func: () => void): void; + + /** + * manual emit an events + * @param event - event that occures + */ + emit(event: "changed" | "closed"): void; + } +} diff --git a/error-codes.d.ts b/error-codes.d.ts new file mode 100644 index 00000000..13440f55 --- /dev/null +++ b/error-codes.d.ts @@ -0,0 +1,79 @@ +/** + * This is a list of error codes that can be thrown from enigma.js API calls. + * @entry + * @see EnigmaError + * @enum + * @example Handling an enigma.js error + * const { NOT_CONNECTED } = require('enigma.js/error-codes'); + * try { + * const layout = await model.getLayout(); + * } catch (err) { + * if (err.code === NOT_CONNECTED) { + * console.log('Tried to communicate on a session that is closed'); + * } + * } + */ +declare const errorCodes: { + /** + * You're trying to send data on a socket that's not connected. + * @type {number} + */ + NOT_CONNECTED: number; + /** + * The object you're trying to fetch does not exist. + * @type {number} + */ + OBJECT_NOT_FOUND: number; + /** + * Unexpected RPC response, expected array of patches. + * @type {number} + */ + EXPECTED_ARRAY_OF_PATCHES: number; + /** + * Not an object that can be patched. + * @type {number} + */ + PATCH_HAS_NO_PARENT: number; + /** + * This entry is already defined with another key. + * @type {number} + */ + ENTRY_ALREADY_DEFINED: number; + /** + * You need to supply a configuration. + * @type {number} + */ + NO_CONFIG_SUPPLIED: number; + /** + * There's no promise object available (polyfill required?). + * @type {number} + */ + PROMISE_REQUIRED: number; + /** + * The schema struct type you requested does not exist. + * @type {number} + */ + SCHEMA_STRUCT_TYPE_NOT_FOUND: number; + /** + * Can't override this function. + * @type {number} + */ + SCHEMA_MIXIN_CANT_OVERRIDE_FUNCTION: number; + /** + * Extend is not allowed for this mixin. + * @type {number} + */ + SCHEMA_MIXIN_EXTEND_NOT_ALLOWED: number; + /** + * Session suspended - no interaction allowed. + * @type {number} + */ + SESSION_SUSPENDED: number; + /** + * onlyIfAttached supplied, but you got SESSION_CREATED. + * @type {number} + */ + SESSION_NOT_ATTACHED: number; +}; + +export default errorCodes; diff --git a/package.json b/package.json index dc5a1861..5778a261 100644 --- a/package.json +++ b/package.json @@ -28,11 +28,14 @@ "/enigma.js.map", "/enigma.min.js", "/enigma.min.js.map", + "/enigma.d.ts", "/sense-utilities.js", "/sense-utilities.js.map", "/sense-utilities.min.js", "/sense-utilities.min.js.map", + "/sense-utilities.d.ts", "/error-codes.js", + "/error-codes.d.ts", "/schemas" ], "main": "enigma.js", diff --git a/sense-utilities.d.ts b/sense-utilities.d.ts new file mode 100644 index 00000000..11b1394e --- /dev/null +++ b/sense-utilities.d.ts @@ -0,0 +1,38 @@ +/** + * This object describes the configuration that is sent into `buildUrl(config)`. + */ +interface SenseConfiguration { + appId?: string; + noData?: boolean; + secure?: boolean; + host?: string; + port?: number; + prefix?: string; + subpath?: string; + route?: string; + identity?: string; + urlParams?: { [key: string]: any }; + ttl?: number; +} + +declare class SenseUtilities { + /** + * Function used to build a URL. + * + * @param {SenseConfiguration} urlConfig - The URL configuration object. + * @returns {string} Returns the websocket URL. + * + * @example + * import SenseUtilities from 'sense-utilities'; + * + * const urlConfig: SenseConfiguration = { + * host: 'my-sense-host', + * appId: 'some-app', + * }; + * + * const url: string = SenseUtilities.buildUrl(urlConfig); + */ + static buildUrl(urlConfig: SenseConfiguration): string; +} + +export default SenseUtilities; From c3545bfa630e23e847d2d82cdbca5e3edf80f3fb Mon Sep 17 00:00:00 2001 From: Anders Nilsson Date: Wed, 6 Sep 2023 11:44:38 +0200 Subject: [PATCH 2/2] fixed docs and any types --- enigma.d.ts | 142 +++++++++++++++++++++++++++---------------- error-codes.d.ts | 12 ---- sense-utilities.d.ts | 4 +- 3 files changed, 91 insertions(+), 67 deletions(-) diff --git a/enigma.d.ts b/enigma.d.ts index 37570f1e..b4b2d1ca 100644 --- a/enigma.d.ts +++ b/enigma.d.ts @@ -5,15 +5,17 @@ declare module "enigma.js" { declare namespace EnigmaJS { + /** Module exported from the "enigma.js" package */ interface Enigma { /** - * Create a session object. - * @returns - Returns a session. - * Note: See Configuration for the configuration options. + * Create a session to Qix Engine. */ create(config: Configuration): Session; } + /** + * Represents a session over a websocket to a Qix Engine. A session is unique for every Qix Engine Application and user. + */ interface Session { /** * Establishes the websocket against the configured URL. Eventually resolved with the QIX global interface when the connection has been established. @@ -24,30 +26,29 @@ declare namespace EnigmaJS { /** * Closes the websocket and cleans up internal caches, also triggers the closed event on all generated APIs. * Eventually resolved when the websocket has been closed. - * * @param code Code number for closing event https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent * @param reason Description of the reason for closing. * Note: you need to manually invoke this when you want to close a session and config.suspendOnClose is true. * @return Promise. */ - close(code?: number, reason?: string): Promise; + close(code?: number, reason?: string): Promise; /** * Suspends the enigma.js session by closing the websocket and rejecting all method calls until it has been resumed again. * @return Promise. */ - suspend(): Promise; + suspend(): Promise; /** * Resume a previously suspended enigma.js session by re-creating the websocket and, if possible, re-open the document - * as well as refreshing the internal caches. If successful, changed events will be triggered on all generated APIs, + * as well as refreshing the internal cache. If successful, changed events will be triggered on all generated APIs, * and on the ones it was unable to restore, the closed event will be triggered. * @param onlyIfAttached onlyIfAttached can be used to only allow resuming if the QIX Engine session was reattached properly. * @return Promise. * Note: Eventually resolved when the websocket (and potentially the previously opened document, and generated APIs) has been restored, * rejected when it fails any of those steps, or when onlyIfAttached is true and a new QIX Engine session was created. */ - resume(onlyIfAttached?: boolean): Promise; + resume(onlyIfAttached?: boolean): Promise; /** * Handle opened state. This event is triggered whenever the websocket is connected and ready for communication. @@ -69,10 +70,11 @@ declare namespace EnigmaJS { */ on( event: "opened" | "closed" | "suspended" | "resumed" | string, - func: any, + func: Function, ): void; } + /** Defines the type of Qix object the mixin should be applied to */ type MixinType = | "Doc" | "GenericObject" @@ -83,49 +85,36 @@ declare namespace EnigmaJS { | "CurrentSelections" | "FieldList" | "Field" - | "UndoInfo"; + | "UndoInfo" + | string; + /** Can be used to augments api to Qix Objects */ interface Mixin { - // Defines which Qix types this mixin should be applied to + /** Defines which Qix types this mixin should be applied to */ types?: MixinType[] | MixinType; - // Mixin on a single type + /** Mixin on a single type */ type?: MixinType; - - // Init method is run once for each mixin - init(args: { config: any; api: QixObject }): void; - - // Extend mehods + /** Init method is run once for each mixin */ + init(args: { config: Configuration; api: QixObject }): void; + /** Extend mehods */ extend: Record; - - // Extend mehods + /** Override mehods */ overrides: Record; } - /** - * Engine Session Configuration - */ + /** Engine Session Configuration */ interface Configuration { - /** - * Object containing the specification for the API to generate. Corresponds to a specific version of the QIX Engine API. - */ + /** Object containing the specification for the API to generate. Corresponds to a specific version of the QIX Engine API. */ schema: object; - /** - * String containing a proper websocket URL to QIX Engine. - */ + /** String containing a proper websocket URL to QIX Engine. */ url: string; - /** - * A function to use when instantiating the WebSocket, mandatory for Node.js. - */ + /** A function to use when instantiating the WebSocket, mandatory for Node.js. */ createSocket?: (url: string) => WebSocket; - /** - * ES6-compatible Promise library. - */ + /** ES6-compatible Promise library. */ Promise?: PromiseConstructor; - /** - * Set to true if the session should be suspended instead of closed when the websocket is closed. - */ + /** Set to true if the session should be suspended instead of closed when the websocket is closed. */ suspendOnClose?: boolean; - /** + /** * Mixins to extend/augment the QIX Engine API. * Mixins are applied in the array order. */ @@ -137,7 +126,7 @@ declare namespace EnigmaJS { */ requestInterceptors?: RequestInterceptors[]; /** - * Interceptors for augmenting responses before they are sent to QIX Engine. + * Interceptors for augmenting responses before they are resolved to caller. * See Interceptors section for more information how each entry in this array should look like. * Interceptors are applied in the array order. */ @@ -148,33 +137,37 @@ declare namespace EnigmaJS { */ protocol?: Protocol; } + + interface ResponseInterceptors { /** - * This method is invoked when a previous interceptor has rejected the promise, use this to handle for example errors before they are sent into mixins. - * @param session refers to the session executing the interceptor. - * @param request is the JSON-RPC request resulting in this error. You may use .retry() to retry sending it to QIX Engine. - * @param error is whatever the previous interceptor rejected with. + * This method is invoked when a response has been rejected either from Qix Engine as an error or from a previous + * interceptor that has rejected the promise, use this to handle errors before they are rejected at the callee. + * @param session Session executing the interceptor. + * @param request JSON-RPC request resulting in this error. You may use .retry() to retry sending it to QIX Engine. + * @param error Error causing the rejection. */ - onRejected?(session: Session, request: any, error: any): Promise; + onRejected?(session: Session, request: RpcRequest, error: Error): Promise; /** - * This method is invoked when a promise has been successfully resolved, use this to modify the result or reject the promise chain before it is sent to mixins. - * @param session refers to the session executing the interceptor. - * @param request is the JSON-RPC request resulting in this error. You may use .retry() to retry sending it to QIX Engine. - * @param error is whatever the previous interceptor resolved with. + * This method is called when a response has been successfully resolved, use this to modify the response or reject the + * promise chain before it is sent as a response to the callee. + * @param session Session executing the interceptor. + * @param request The outgoing request. + * @param response Resolved response from either the Qix Engine or what the previous interceptor resolved to. */ - onFulfilled?(session: Session, request: any, result: any): Promise; + onFulfilled?(session: Session, request: RpcRequest, response: RpcResponse): RpcResponse; } interface RequestInterceptors { /** * This method is invoked when a request is about to be sent to QIX Engine. - * @param session refers to the session executing the interceptor. - * @param request is the JSON-RPC request resulting in this error. You may use .retry() to retry sending it to QIX Engine. - * @returns request the new request + * @param session Session executing the interceptor. + * @param request The request that can be changed before going out to Qix Engine + * @returns The changed request that will be sent */ - onFulfilled?(session: Session, request: any, result: any): any; + onFulfilled?(session: Session, request: RpcRequest): RpcRequest; } interface Protocol { @@ -186,10 +179,15 @@ declare namespace EnigmaJS { * Represents an Qix Engine object, e.g. a chart, listbox or the app */ interface QixObject { + /** Session used to connect to this qix object */ session: Session; + /** number representing the qix object */ handle: number; + /** id for the qix object */ id: string; + /** type definition used to create the qix object */ type: string; + /** qix type (e.g. GenericObject, GenericMeasure etc) of this qix object */ genericType: string; /** * register a function for events @@ -204,4 +202,42 @@ declare namespace EnigmaJS { */ emit(event: "changed" | "closed"): void; } + + /** + * Represents a websocket request in a json-rpc format sent to Qix Engine + */ + type RpcRequest = { + /** version of JSON-RPC defaults to 2.0 */ + jsonrpc?: string; + /** Identifier established by the initiator of the request */ + id?: string; + /** name of the engine method. */ + method: string; + /** target of the method. */ + handle: number; + /** If set to true, the engine returns delta values. The default value is false. */ + delta?: boolean; + /** the parameters can be provided by name through an object or by position through an array */ + params: Array | string; + /** returns default and empty values. */ + return_empty?: boolean; + }; + + /** + * Represents a websocket response in a json-rpc format sent from Qix Engine + */ + type RpcResponse = { + /** ID of the backend object. */ + id: string; + /** QIX type of the backend object. Can for example be "Doc" or "GenericVariable". */ + type: string; + /** Custom type of the backend object, if defined in qInfo. */ + genericType: string; + /** The QIX Engine session object. */ + session: RpcSession; + /** Handle of the backend object. */ + handle: number; + /** represents the returned value from engine */ + result: { qReturn: any } | null; + }; } diff --git a/error-codes.d.ts b/error-codes.d.ts index 13440f55..a1dce8d5 100644 --- a/error-codes.d.ts +++ b/error-codes.d.ts @@ -16,62 +16,50 @@ declare const errorCodes: { /** * You're trying to send data on a socket that's not connected. - * @type {number} */ NOT_CONNECTED: number; /** * The object you're trying to fetch does not exist. - * @type {number} */ OBJECT_NOT_FOUND: number; /** * Unexpected RPC response, expected array of patches. - * @type {number} */ EXPECTED_ARRAY_OF_PATCHES: number; /** * Not an object that can be patched. - * @type {number} */ PATCH_HAS_NO_PARENT: number; /** * This entry is already defined with another key. - * @type {number} */ ENTRY_ALREADY_DEFINED: number; /** * You need to supply a configuration. - * @type {number} */ NO_CONFIG_SUPPLIED: number; /** * There's no promise object available (polyfill required?). - * @type {number} */ PROMISE_REQUIRED: number; /** * The schema struct type you requested does not exist. - * @type {number} */ SCHEMA_STRUCT_TYPE_NOT_FOUND: number; /** * Can't override this function. - * @type {number} */ SCHEMA_MIXIN_CANT_OVERRIDE_FUNCTION: number; /** * Extend is not allowed for this mixin. - * @type {number} */ SCHEMA_MIXIN_EXTEND_NOT_ALLOWED: number; /** * Session suspended - no interaction allowed. - * @type {number} */ SESSION_SUSPENDED: number; /** * onlyIfAttached supplied, but you got SESSION_CREATED. - * @type {number} */ SESSION_NOT_ATTACHED: number; }; diff --git a/sense-utilities.d.ts b/sense-utilities.d.ts index 11b1394e..0c3713d7 100644 --- a/sense-utilities.d.ts +++ b/sense-utilities.d.ts @@ -19,8 +19,8 @@ declare class SenseUtilities { /** * Function used to build a URL. * - * @param {SenseConfiguration} urlConfig - The URL configuration object. - * @returns {string} Returns the websocket URL. + * @param urlConfig - The URL configuration object. + * @returns Returns the websocket URL. * * @example * import SenseUtilities from 'sense-utilities';