diff options
author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2023-12-22 10:58:37 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-22 15:58:37 +0000 |
commit | 2b29df7a8ecd95384dbee30e27743cf25703e447 (patch) | |
tree | 8bccc8c822f740325830496aca4855b75f37e6ed | |
parent | 8d2aaf2757f69bdf85281319813ff57db276f71a (diff) |
API map updates (#418)
* Simplify some types
* Organize
* Add additional types for explicit sync and async handlers
* Rename ApiItem to ApiDescriptor
* Simplify template names
* Remove lax types
* Document
* Add support for extra params
* Update APIs
* Make handler explicitly async
* Add comments
* Add more types
* Description fixes, add ApiParam
* Type updates
* Add invokeApiMapHandler
* Fixes
-rw-r--r-- | ext/js/background/offscreen.js | 2 | ||||
-rw-r--r-- | ext/js/core/api-map.js | 58 | ||||
-rw-r--r-- | types/ext/api-map.d.ts | 100 | ||||
-rw-r--r-- | types/ext/offscreen.d.ts | 10 |
4 files changed, 138 insertions, 32 deletions
diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js index 44b0af77..1cab5929 100644 --- a/ext/js/background/offscreen.js +++ b/ext/js/background/offscreen.js @@ -138,7 +138,7 @@ export class Offscreen { } /** @type {import('offscreen').OffscreenApiHandler<'findTermsOffscreen'>} */ - _findTermsHandler({mode, text, options}) { + async _findTermsHandler({mode, text, options}) { const enabledDictionaryMap = new Map(options.enabledDictionaryMap); const excludeDictionaryDefinitions = ( options.excludeDictionaryDefinitions !== null ? diff --git a/ext/js/core/api-map.js b/ext/js/core/api-map.js index eb4abeea..09be8035 100644 --- a/ext/js/core/api-map.js +++ b/ext/js/core/api-map.js @@ -15,10 +15,13 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import {ExtensionError} from './extension-error.js'; + /** * @template {import('api-map').ApiSurface} [TApiSurface=never] - * @param {import('api-map').ApiMapInit<TApiSurface>} init - * @returns {import('api-map').ApiMap<TApiSurface>} + * @template {unknown[]} [TExtraParams=[]] + * @param {import('api-map').ApiMapInit<TApiSurface, TExtraParams>} init + * @returns {import('api-map').ApiMap<TApiSurface, TExtraParams>} */ export function createApiMap(init) { return new Map(init); @@ -26,8 +29,9 @@ export function createApiMap(init) { /** * @template {import('api-map').ApiSurface} [TApiSurface=never] - * @param {import('api-map').ApiMap<TApiSurface>} map - * @param {import('api-map').ApiMapInit<TApiSurface>} init + * @template {unknown[]} [TExtraParams=[]] + * @param {import('api-map').ApiMap<TApiSurface, TExtraParams>} map + * @param {import('api-map').ApiMapInit<TApiSurface, TExtraParams>} init * @throws {Error} */ export function extendApiMap(map, init) { @@ -39,10 +43,52 @@ export function extendApiMap(map, init) { /** * @template {import('api-map').ApiSurface} [TApiSurface=never] - * @param {import('api-map').ApiMap<TApiSurface>} map + * @template {unknown[]} [TExtraParams=[]] + * @param {import('api-map').ApiMap<TApiSurface, TExtraParams>} map * @param {string} name - * @returns {import('api-map').ApiHandlerAny<TApiSurface>|undefined} + * @returns {import('api-map').ApiHandlerAny<TApiSurface, TExtraParams>|undefined} */ export function getApiMapHandler(map, name) { return map.get(/** @type {import('api-map').ApiNames<TApiSurface>} */ (name)); } + +/** + * @template {import('api-map').ApiSurface} [TApiSurface=never] + * @template {unknown[]} [TExtraParams=[]] + * @param {import('api-map').ApiMap<TApiSurface, TExtraParams>} map + * @param {string} name + * @param {import('api-map').ApiParamsAny<TApiSurface>} params + * @param {TExtraParams} extraParams + * @param {(response: import('core').Response<import('api-map').ApiReturnAny<TApiSurface>>) => void} callback + * @param {() => void} [handlerNotFoundCallback] + * @returns {boolean} `true` if async, `false` otherwise. + */ +export function invokeApiMapHandler(map, name, params, extraParams, callback, handlerNotFoundCallback) { + const handler = getApiMapHandler(map, name); + if (typeof handler === 'undefined') { + if (typeof handlerNotFoundCallback === 'function') { + try { + handlerNotFoundCallback(); + } catch (error) { + // NOP + } + } + return false; + } + try { + const promiseOrResult = handler(/** @type {import('core').SafeAny} */ (params), ...extraParams); + if (promiseOrResult instanceof Promise) { + /** @type {Promise<unknown>} */ (promiseOrResult).then( + (result) => { callback({result}); }, + (error) => { callback({error: ExtensionError.serialize(error)}); } + ); + return true; + } else { + callback({result: promiseOrResult}); + return false; + } + } catch (error) { + callback({error: ExtensionError.serialize(error)}); + return false; + } +} diff --git a/types/ext/api-map.d.ts b/types/ext/api-map.d.ts index eebc886a..4a4eb87c 100644 --- a/types/ext/api-map.d.ts +++ b/types/ext/api-map.d.ts @@ -15,41 +15,101 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +/** + * This type describes the structure of an API surface. + * It is effectively just an object containing a list of items which describe a basic API functionality. + */ type ApiSurface = { - [name: string]: ApiItem; + [name: string]: ApiDescriptor; }; -type ApiItem = { +/** + * This type describes the structure of a single API function. + */ +type ApiDescriptor = { + /** The parameters for the function. If there are no parameters, `void` should be used. */ params: void | {[name: string]: unknown}; + /** The return type for the function. */ return: unknown; }; -export type ApiHandler<TApiItem extends ApiItem> = (params: TApiItem['params']) => TApiItem['return'] | Promise<TApiItem['return']>; +/** + * This type represents a mapping of an entire API surface to its handlers. + */ +type ApiHandlerSurface<TSurface extends ApiSurface, TExtraParams extends ApiTExtraParams> = { + [name in ApiNames<TSurface>]: ApiHandler<TSurface[name], TExtraParams>; +}; -type ApiHandlerSurface<TApiSurface extends ApiSurface> = {[name in ApiNames<TApiSurface>]: ApiHandler<TApiSurface[name]>}; +/** + * This type represents a single API map initializer. + * Type safety is enforced by ensuring that the name and handler signature are valid. + */ +type ApiMapInitItem<TSurface extends ApiSurface, TExtraParams extends ApiTExtraParams, TName extends ApiNames<TSurface>> = [ + name: TName, + handler: ApiHandler<TSurface[TName], TExtraParams>, +]; -export type ApiHandlerAny<TApiSurface extends ApiSurface> = ApiHandlerSurface<TApiSurface>[ApiNames<TApiSurface>]; +/** + * This type represents a union of all API map initializers for a given surface. + */ +type ApiMapInitItemAny<TSurface extends ApiSurface, TExtraParams extends ApiTExtraParams> = {[key in ApiNames<TSurface>]: ApiMapInitItem<TSurface, TExtraParams, key>}[ApiNames<TSurface>]; -export type ApiNames<TApiSurface extends ApiSurface> = keyof TApiSurface; +/** Base type for extra params, which is just a generic array. */ +type ApiTExtraParams = unknown[]; -export type ApiParams<TApiSurface extends ApiSurface, TName extends ApiNames<TApiSurface>> = TApiSurface[TName]['params']; +/** Default type for extra params, which is an empty array. */ +type ApiExtraParamsDefault = []; -export type ApiReturn<TApiSurface extends ApiSurface, TName extends ApiNames<TApiSurface>> = TApiSurface[TName]['return']; +/** Type alias for the params member of a descriptor. */ +export type ApiParams<TDescriptor extends ApiDescriptor> = TDescriptor['params']; -export type ApiMap<TApiSurface extends ApiSurface> = Map<ApiNames<TApiSurface>, ApiHandlerAny<TApiSurface>>; +/** Type alias for a single param of a descriptor. */ +export type ApiParam<TDescriptor extends ApiDescriptor, TParamName extends ApiParamNames<TDescriptor>> = ApiParams<TDescriptor>[TParamName]; -export type ApiMapInit<TApiSurface extends ApiSurface> = ApiMapInitItemAny<TApiSurface>[]; +/** Type alias for the union of parameter names in a descriptor. */ +export type ApiParamNames<TDescriptor extends ApiDescriptor> = keyof ApiParams<TDescriptor>; -export type ApiMapInitLax<TApiSurface extends ApiSurface> = ApiMapInitLaxItem<TApiSurface>[]; +/** Type alias for a tuple of parameter types for a descriptor. */ +export type ApiOrderedParams<TDescriptor extends ApiDescriptor, TParamNames extends ApiParamNames<TDescriptor>[]> = { + [index in keyof TParamNames]: ApiParams<TDescriptor>[TParamNames[index]]; +}; -export type ApiMapInitLaxItem<TApiSurface extends ApiSurface> = [ - name: ApiNames<TApiSurface>, - handler: ApiHandlerAny<TApiSurface>, -]; +/** Type alias for the return member of a descriptor. */ +export type ApiReturn<TDescriptor extends ApiDescriptor> = TDescriptor['return']; -type ApiMapInitItem<TApiSurface extends ApiSurface, TName extends ApiNames<TApiSurface>> = [ - name: TName, - handler: ApiHandler<TApiSurface[TName]>, -]; +/** A type representing a synchronous handler. */ +export type ApiHandlerSync<TDescriptor extends ApiDescriptor, TExtraParams extends ApiTExtraParams = ApiExtraParamsDefault> = (params: ApiParams<TDescriptor>, ...extraParams: TExtraParams) => ApiReturn<TDescriptor>; + +/** A type representing an asynchronous handler. */ +export type ApiHandlerAsync<TDescriptor extends ApiDescriptor, TExtraParams extends ApiTExtraParams = ApiExtraParamsDefault> = (params: ApiParams<TDescriptor>, ...extraParams: TExtraParams) => Promise<ApiReturn<TDescriptor>>; + +/** A type representing a generic handler. */ +export type ApiHandler<TDescriptor extends ApiDescriptor, TExtraParams extends ApiTExtraParams = ApiExtraParamsDefault> = (params: ApiParams<TDescriptor>, ...extraParams: TExtraParams) => ApiReturn<TDescriptor> | Promise<ApiReturn<TDescriptor>>; + +/** A union of all of the handlers for a given surface. */ +export type ApiHandlerAny<TSurface extends ApiSurface, TExtraParams extends ApiTExtraParams = ApiExtraParamsDefault> = ApiHandlerSurface<TSurface, TExtraParams>[ApiNames<TSurface>]; + +/** A union of all of the names for a given surface. */ +export type ApiNames<TSurface extends ApiSurface> = keyof TSurface; + +/** A mapping of names to the corresponding handler function. */ +export type ApiMap<TSurface extends ApiSurface, TExtraParams extends ApiTExtraParams = ApiExtraParamsDefault> = Map<ApiNames<TSurface>, ApiHandlerAny<TSurface, TExtraParams>>; + +/** The initialization array structure for populating an API map. */ +export type ApiMapInit<TSurface extends ApiSurface, TExtraParams extends ApiTExtraParams = ApiExtraParamsDefault> = ApiMapInitItemAny<TSurface, TExtraParams>[]; + +/** The type for a public API function, using a parameters object. */ +export type ApiFunction<TSurface extends ApiSurface, TName extends ApiNames<TSurface>> = ( + params: ApiParams<TSurface[TName]>, +) => Promise<ApiReturn<TSurface[TName]>>; + +/** The type for a public API function, using ordered parameters. */ +export type ApiFunctionOrdered<TSurface extends ApiSurface, TName extends ApiNames<TSurface>, TParamNames extends ApiParamNames<TSurface[TName]>[]> = ( + ...params: ApiOrderedParams<TSurface[TName], TParamNames>, +) => Promise<ApiReturn<TSurface[TName]>>; + +/** Type alias for a union of all params types. */ +export type ApiParamsAny<TSurface extends ApiSurface> = ApiParams<TSurface[keyof TSurface]>; -type ApiMapInitItemAny<TApiSurface extends ApiSurface> = {[key in ApiNames<TApiSurface>]: ApiMapInitItem<TApiSurface, key>}[ApiNames<TApiSurface>]; +/** Type alias for a union of all return types. */ +export type ApiReturnAny<TSurface extends ApiSurface> = ApiReturn<TSurface[keyof TSurface]>; diff --git a/types/ext/offscreen.d.ts b/types/ext/offscreen.d.ts index 451f5f9e..d67733bb 100644 --- a/types/ext/offscreen.d.ts +++ b/types/ext/offscreen.d.ts @@ -22,7 +22,7 @@ import type * as DictionaryImporter from './dictionary-importer'; import type * as Environment from './environment'; import type * as Translation from './translation'; import type * as Translator from './translator'; -import type {ApiMap, ApiMapInit, ApiHandler, ApiParams, ApiReturn} from './api-map'; +import type {ApiMap, ApiMapInit, ApiHandler, ApiParams, ApiReturn, ApiNames} from './api-map'; type OffscreenApiSurface = { databasePrepareOffscreen: { @@ -99,7 +99,7 @@ export type Message<TName extends MessageType> = ( {action: TName, params: OffscreenApiParams<TName>} ); -export type MessageType = keyof OffscreenApiSurface; +export type MessageType = ApiNames<OffscreenApiSurface>; export type FindKanjiOptionsOffscreen = Omit<Translation.FindKanjiOptions, 'enabledDictionaryMap'> & { enabledDictionaryMap: [ @@ -126,8 +126,8 @@ export type OffscreenApiMap = ApiMap<OffscreenApiSurface>; export type OffscreenApiMapInit = ApiMapInit<OffscreenApiSurface>; -export type OffscreenApiHandler<TName extends keyof OffscreenApiSurface> = ApiHandler<OffscreenApiSurface[TName]>; +export type OffscreenApiHandler<TName extends MessageType> = ApiHandler<OffscreenApiSurface[TName]>; -export type OffscreenApiParams<TName extends keyof OffscreenApiSurface> = ApiParams<OffscreenApiSurface, TName>; +export type OffscreenApiParams<TName extends MessageType> = ApiParams<OffscreenApiSurface[TName]>; -export type OffscreenApiReturn<TName extends keyof OffscreenApiSurface> = ApiReturn<OffscreenApiSurface, TName>; +export type OffscreenApiReturn<TName extends MessageType> = ApiReturn<OffscreenApiSurface[TName]>; |