From 2b29df7a8ecd95384dbee30e27743cf25703e447 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 22 Dec 2023 10:58:37 -0500 Subject: 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 --- ext/js/background/offscreen.js | 2 +- ext/js/core/api-map.js | 58 +++++++++++++++++++++--- types/ext/api-map.d.ts | 100 ++++++++++++++++++++++++++++++++--------- 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 . */ +import {ExtensionError} from './extension-error.js'; + /** * @template {import('api-map').ApiSurface} [TApiSurface=never] - * @param {import('api-map').ApiMapInit} init - * @returns {import('api-map').ApiMap} + * @template {unknown[]} [TExtraParams=[]] + * @param {import('api-map').ApiMapInit} init + * @returns {import('api-map').ApiMap} */ 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} map - * @param {import('api-map').ApiMapInit} init + * @template {unknown[]} [TExtraParams=[]] + * @param {import('api-map').ApiMap} map + * @param {import('api-map').ApiMapInit} 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} map + * @template {unknown[]} [TExtraParams=[]] + * @param {import('api-map').ApiMap} map * @param {string} name - * @returns {import('api-map').ApiHandlerAny|undefined} + * @returns {import('api-map').ApiHandlerAny|undefined} */ export function getApiMapHandler(map, name) { return map.get(/** @type {import('api-map').ApiNames} */ (name)); } + +/** + * @template {import('api-map').ApiSurface} [TApiSurface=never] + * @template {unknown[]} [TExtraParams=[]] + * @param {import('api-map').ApiMap} map + * @param {string} name + * @param {import('api-map').ApiParamsAny} params + * @param {TExtraParams} extraParams + * @param {(response: import('core').Response>) => 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} */ (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 . */ +/** + * 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 = (params: TApiItem['params']) => TApiItem['return'] | Promise; +/** + * This type represents a mapping of an entire API surface to its handlers. + */ +type ApiHandlerSurface = { + [name in ApiNames]: ApiHandler; +}; -type ApiHandlerSurface = {[name in ApiNames]: ApiHandler}; +/** + * This type represents a single API map initializer. + * Type safety is enforced by ensuring that the name and handler signature are valid. + */ +type ApiMapInitItem> = [ + name: TName, + handler: ApiHandler, +]; -export type ApiHandlerAny = ApiHandlerSurface[ApiNames]; +/** + * This type represents a union of all API map initializers for a given surface. + */ +type ApiMapInitItemAny = {[key in ApiNames]: ApiMapInitItem}[ApiNames]; -export type ApiNames = keyof TApiSurface; +/** Base type for extra params, which is just a generic array. */ +type ApiTExtraParams = unknown[]; -export type ApiParams> = TApiSurface[TName]['params']; +/** Default type for extra params, which is an empty array. */ +type ApiExtraParamsDefault = []; -export type ApiReturn> = TApiSurface[TName]['return']; +/** Type alias for the params member of a descriptor. */ +export type ApiParams = TDescriptor['params']; -export type ApiMap = Map, ApiHandlerAny>; +/** Type alias for a single param of a descriptor. */ +export type ApiParam> = ApiParams[TParamName]; -export type ApiMapInit = ApiMapInitItemAny[]; +/** Type alias for the union of parameter names in a descriptor. */ +export type ApiParamNames = keyof ApiParams; -export type ApiMapInitLax = ApiMapInitLaxItem[]; +/** Type alias for a tuple of parameter types for a descriptor. */ +export type ApiOrderedParams[]> = { + [index in keyof TParamNames]: ApiParams[TParamNames[index]]; +}; -export type ApiMapInitLaxItem = [ - name: ApiNames, - handler: ApiHandlerAny, -]; +/** Type alias for the return member of a descriptor. */ +export type ApiReturn = TDescriptor['return']; -type ApiMapInitItem> = [ - name: TName, - handler: ApiHandler, -]; +/** A type representing a synchronous handler. */ +export type ApiHandlerSync = (params: ApiParams, ...extraParams: TExtraParams) => ApiReturn; + +/** A type representing an asynchronous handler. */ +export type ApiHandlerAsync = (params: ApiParams, ...extraParams: TExtraParams) => Promise>; + +/** A type representing a generic handler. */ +export type ApiHandler = (params: ApiParams, ...extraParams: TExtraParams) => ApiReturn | Promise>; + +/** A union of all of the handlers for a given surface. */ +export type ApiHandlerAny = ApiHandlerSurface[ApiNames]; + +/** A union of all of the names for a given surface. */ +export type ApiNames = keyof TSurface; + +/** A mapping of names to the corresponding handler function. */ +export type ApiMap = Map, ApiHandlerAny>; + +/** The initialization array structure for populating an API map. */ +export type ApiMapInit = ApiMapInitItemAny[]; + +/** The type for a public API function, using a parameters object. */ +export type ApiFunction> = ( + params: ApiParams, +) => Promise>; + +/** The type for a public API function, using ordered parameters. */ +export type ApiFunctionOrdered, TParamNames extends ApiParamNames[]> = ( + ...params: ApiOrderedParams, +) => Promise>; + +/** Type alias for a union of all params types. */ +export type ApiParamsAny = ApiParams; -type ApiMapInitItemAny = {[key in ApiNames]: ApiMapInitItem}[ApiNames]; +/** Type alias for a union of all return types. */ +export type ApiReturnAny = ApiReturn; 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 = ( {action: TName, params: OffscreenApiParams} ); -export type MessageType = keyof OffscreenApiSurface; +export type MessageType = ApiNames; export type FindKanjiOptionsOffscreen = Omit & { enabledDictionaryMap: [ @@ -126,8 +126,8 @@ export type OffscreenApiMap = ApiMap; export type OffscreenApiMapInit = ApiMapInit; -export type OffscreenApiHandler = ApiHandler; +export type OffscreenApiHandler = ApiHandler; -export type OffscreenApiParams = ApiParams; +export type OffscreenApiParams = ApiParams; -export type OffscreenApiReturn = ApiReturn; +export type OffscreenApiReturn = ApiReturn; -- cgit v1.2.3