@api-client/ui 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { ApiError } from '@api-client/core/runtime/store/Errors.js';
1
+ import { Exception } from '@api-client/core/exceptions/exception.js';
2
2
  import { type IoEvent, type IoCommand, type IoNotification } from './PlatformBindings.js';
3
3
  /**
4
4
  * The base IO thread class containing the communication logic with the clients.
@@ -19,7 +19,7 @@ export declare abstract class IoThread {
19
19
  * To be called by the IO thread implementation when receiving a message from the client.
20
20
  */
21
21
  protected handleMessage(event: IoCommand | IoEvent): void;
22
- protected notifyError(id: number, error: ApiError): void;
22
+ protected notifyError(id: number, error: Exception): void;
23
23
  protected getFunction(fn: string): Function | undefined;
24
24
  /**
25
25
  * Calls the function on self, creates a response event, and dispatches it.
@@ -1 +1 @@
1
- {"version":3,"file":"IoThread.d.ts","sourceRoot":"","sources":["../../../../src/bindings/base/IoThread.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,0CAA0C,CAAA;AACnE,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAEzF;;GAEG;AACH,8BAAsB,QAAQ;IAC5B;;OAEG;IACH,QAAQ,CAAC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAEpC;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,GAAG,IAAI;IAE7D;;OAEG;IACH,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,IAAI;IA0CzD,SAAS,CAAC,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,IAAI;IAWxD,SAAS,CAAC,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAiBvD;;;;;;OAMG;cAEa,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBhF;;;;;OAKG;IACH,SAAS,CAAC,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;CAQjE"}
1
+ {"version":3,"file":"IoThread.d.ts","sourceRoot":"","sources":["../../../../src/bindings/base/IoThread.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,0CAA0C,CAAA;AACpE,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAEzF;;GAEG;AACH,8BAAsB,QAAQ;IAC5B;;OAEG;IACH,QAAQ,CAAC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAEpC;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,GAAG,IAAI;IAE7D;;OAEG;IACH,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,IAAI;IA0CzD,SAAS,CAAC,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI;IAWzD,SAAS,CAAC,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAiBvD;;;;;;OAMG;cAEa,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBhF;;;;;OAKG;IACH,SAAS,CAAC,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;CAQjE"}
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  /* eslint-disable no-console */
3
- import { ApiError } from '@api-client/core/runtime/store/Errors.js';
3
+ import { Exception } from '@api-client/core/exceptions/exception.js';
4
4
  /**
5
5
  * The base IO thread class containing the communication logic with the clients.
6
6
  */
@@ -23,7 +23,7 @@ export class IoThread {
23
23
  return;
24
24
  }
25
25
  if (typeof fn !== 'string' || !fn) {
26
- const err = new ApiError('Invalid message received on the io-channel. The second argument must be a function name to call.', 400);
26
+ const err = new Exception('Invalid message received on the io-channel. The second argument must be a function name to call.', { status: 400 });
27
27
  this.notifyError(id, err);
28
28
  console.warn(err.message);
29
29
  return;
@@ -31,7 +31,7 @@ export class IoThread {
31
31
  const callable = this.getFunction(fn);
32
32
  const isFunction = typeof callable === 'function';
33
33
  if (!isFunction) {
34
- const err = new ApiError(`Invalid message received on the io-channel. The function "${fn}" is either not implemented or invalid.`, 400);
34
+ const err = new Exception(`Invalid message received on the io-channel. The function "${fn}" is either not implemented or invalid.`, { status: 400 });
35
35
  this.notifyError(id, err);
36
36
  console.warn(err.message);
37
37
  return;
@@ -86,7 +86,7 @@ export class IoThread {
86
86
  catch (e) {
87
87
  event.type = 'error';
88
88
  event.message = e.message;
89
- if (e.code) {
89
+ if (e.status) {
90
90
  event.result = e;
91
91
  }
92
92
  this.postMessage(event);
@@ -1 +1 @@
1
- {"version":3,"file":"IoThread.js","sourceRoot":"","sources":["../../../../src/bindings/base/IoThread.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable no-console */\nimport { ApiError } from '@api-client/core/runtime/store/Errors.js'\nimport { type IoEvent, type IoCommand, type IoNotification } from './PlatformBindings.js'\n\n/**\n * The base IO thread class containing the communication logic with the clients.\n */\nexport abstract class IoThread {\n /**\n * Initializes the communication channel with the clients.\n */\n abstract initialize(): Promise<void>\n\n /**\n * The IO thread must implement this method to send the message to the client with\n * their particular implementation of message passing.\n *\n * @param message The message to send.\n */\n abstract postMessage(message: IoNotification | IoEvent): void\n\n /**\n * To be called by the IO thread implementation when receiving a message from the client.\n */\n protected handleMessage(event: IoCommand | IoEvent): void {\n if (!event.kind) {\n console.warn('Invalid message received on the app-config-channel.', event)\n return\n }\n if (event.kind === 'IO#Event') {\n // message sent by self.\n return\n }\n const { args, fn, id } = event\n\n if (typeof id !== 'number') {\n console.warn('Invalid message received on the app-config-channel. The id must be a number.')\n return\n }\n\n if (typeof fn !== 'string' || !fn) {\n const err = new ApiError(\n 'Invalid message received on the io-channel. The second argument must be a function name to call.',\n 400\n )\n this.notifyError(id, err)\n console.warn(err.message)\n return\n }\n\n const callable = this.getFunction(fn)\n const isFunction = typeof callable === 'function'\n\n if (!isFunction) {\n const err = new ApiError(\n `Invalid message received on the io-channel. The function \"${fn}\" is either not implemented or invalid.`,\n 400\n )\n this.notifyError(id, err)\n console.warn(err.message)\n return\n }\n\n this.call(callable, id, args)\n }\n\n protected notifyError(id: number, error: ApiError): void {\n const event: IoEvent = {\n kind: 'IO#Event',\n id,\n type: 'error',\n result: error,\n }\n this.postMessage(event)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n protected getFunction(fn: string): Function | undefined {\n const path = fn.trim().split('.')\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n let current: any = this\n path.forEach((key) => {\n if (!current) {\n return\n }\n const scope = current\n current = current[key as any]\n if (current && current.bind) {\n current = current.bind(scope)\n }\n })\n return current\n }\n\n /**\n * Calls the function on self, creates a response event, and dispatches it.\n *\n * @param fnName The function name to call.\n * @param id The request id sent back to the client.\n * @param args The function arguments.\n */\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n protected async call(callable: Function, id: number, args: any[]): Promise<void> {\n const event: IoEvent = {\n kind: 'IO#Event',\n id,\n type: 'result',\n }\n try {\n const promise = (callable as (...init: any[]) => Promise<any>)(...args)\n event.result = await promise\n this.postMessage(event)\n } catch (e) {\n event.type = 'error'\n event.message = (e as Error).message\n if ((e as ApiError).code) {\n event.result = e\n }\n this.postMessage(event)\n }\n }\n\n /**\n * Sends a message to the clients that is a request to send a client DOM event.\n *\n * @param eventPath The path in the `Events` prototype.\n * @param args The arguments to call the event with.\n */\n protected notifyClients(eventPath: string, ...args: any[]): void {\n const info: IoNotification = {\n kind: 'IO#Notification',\n args,\n path: eventPath,\n }\n this.postMessage(info)\n }\n}\n"]}
1
+ {"version":3,"file":"IoThread.js","sourceRoot":"","sources":["../../../../src/bindings/base/IoThread.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable no-console */\nimport { Exception } from '@api-client/core/exceptions/exception.js'\nimport { type IoEvent, type IoCommand, type IoNotification } from './PlatformBindings.js'\n\n/**\n * The base IO thread class containing the communication logic with the clients.\n */\nexport abstract class IoThread {\n /**\n * Initializes the communication channel with the clients.\n */\n abstract initialize(): Promise<void>\n\n /**\n * The IO thread must implement this method to send the message to the client with\n * their particular implementation of message passing.\n *\n * @param message The message to send.\n */\n abstract postMessage(message: IoNotification | IoEvent): void\n\n /**\n * To be called by the IO thread implementation when receiving a message from the client.\n */\n protected handleMessage(event: IoCommand | IoEvent): void {\n if (!event.kind) {\n console.warn('Invalid message received on the app-config-channel.', event)\n return\n }\n if (event.kind === 'IO#Event') {\n // message sent by self.\n return\n }\n const { args, fn, id } = event\n\n if (typeof id !== 'number') {\n console.warn('Invalid message received on the app-config-channel. The id must be a number.')\n return\n }\n\n if (typeof fn !== 'string' || !fn) {\n const err = new Exception(\n 'Invalid message received on the io-channel. The second argument must be a function name to call.',\n { status: 400 }\n )\n this.notifyError(id, err)\n console.warn(err.message)\n return\n }\n\n const callable = this.getFunction(fn)\n const isFunction = typeof callable === 'function'\n\n if (!isFunction) {\n const err = new Exception(\n `Invalid message received on the io-channel. The function \"${fn}\" is either not implemented or invalid.`,\n { status: 400 }\n )\n this.notifyError(id, err)\n console.warn(err.message)\n return\n }\n\n this.call(callable, id, args)\n }\n\n protected notifyError(id: number, error: Exception): void {\n const event: IoEvent = {\n kind: 'IO#Event',\n id,\n type: 'error',\n result: error,\n }\n this.postMessage(event)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n protected getFunction(fn: string): Function | undefined {\n const path = fn.trim().split('.')\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n let current: any = this\n path.forEach((key) => {\n if (!current) {\n return\n }\n const scope = current\n current = current[key as any]\n if (current && current.bind) {\n current = current.bind(scope)\n }\n })\n return current\n }\n\n /**\n * Calls the function on self, creates a response event, and dispatches it.\n *\n * @param fnName The function name to call.\n * @param id The request id sent back to the client.\n * @param args The function arguments.\n */\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n protected async call(callable: Function, id: number, args: any[]): Promise<void> {\n const event: IoEvent = {\n kind: 'IO#Event',\n id,\n type: 'result',\n }\n try {\n const promise = (callable as (...init: any[]) => Promise<any>)(...args)\n event.result = await promise\n this.postMessage(event)\n } catch (e) {\n event.type = 'error'\n event.message = (e as Error).message\n if ((e as Exception).status) {\n event.result = e\n }\n this.postMessage(event)\n }\n }\n\n /**\n * Sends a message to the clients that is a request to send a client DOM event.\n *\n * @param eventPath The path in the `Events` prototype.\n * @param args The arguments to call the event with.\n */\n protected notifyClients(eventPath: string, ...args: any[]): void {\n const info: IoNotification = {\n kind: 'IO#Notification',\n args,\n path: eventPath,\n }\n this.postMessage(info)\n }\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-unsafe-function-type */
2
- import { ApiError } from '@api-client/core/runtime/store/Errors.js';
2
+ import { Exception } from '@api-client/core/exceptions/exception.js';
3
3
  import { Events } from '../../events/Events.js';
4
4
  /**
5
5
  * A base class for all platform bindings.
@@ -122,7 +122,7 @@ export class PlatformBindings {
122
122
  else if (type === 'error') {
123
123
  const apiErr = result;
124
124
  if (apiErr && apiErr.code) {
125
- info.reject(new ApiError(apiErr));
125
+ info.reject(Exception.fromRawException(apiErr, 'Unknown exception'));
126
126
  }
127
127
  else {
128
128
  info.reject(new Error(message));
@@ -1 +1 @@
1
- {"version":3,"file":"PlatformBindings.js","sourceRoot":"","sources":["../../../../src/bindings/base/PlatformBindings.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,OAAO,EAAE,QAAQ,EAAkB,MAAM,0CAA0C,CAAA;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAgD/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,OAAgB,gBAAgB;IACpC;;;OAGG;IACK,EAAE,GAAG,CAAC,CAAA;IAEJ,KAAK,GAAgB,EAAE,CAAA;IAOjC;;OAEG;IACO,KAAK;QACb,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;QACZ,OAAO,IAAI,CAAC,EAAE,CAAA;IAChB,CAAC;IAWD;;;;;;;OAOG;IACO,MAAM,CAAC,EAAU,EAAE,GAAG,IAAe;QAC7C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAA;YACvB,MAAM,IAAI,GAAG;gBACX,OAAO;gBACP,MAAM;gBACN,EAAE;gBACF,EAAE;gBACF,IAAI;aACL,CAAA;YACD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACrB,MAAM,GAAG,GAAc;gBACrB,EAAE;gBACF,EAAE;gBACF,IAAI;gBACJ,IAAI,EAAE,YAAY;aACnB,CAAA;YACD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAA;QACzB,CAAC,CAAC,CAAA;IACJ,CAAC;IAED;;;;;OAKG;IACO,eAAe,CAAC,OAA6C;QACrE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAClB,sCAAsC;YACtC,OAAO,CAAC,IAAI,CAAC,qDAAqD,EAAE,OAAO,CAAC,CAAA;YAC5E,OAAM;QACR,CAAC;QACD,IAAI,OAAO,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YAClC,wBAAwB;YACxB,OAAM;QACR,CAAC;QACD,IAAI,OAAO,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;YACvC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QACtB,CAAC;aAAM,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YACvC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QACvB,CAAC;IACH,CAAC;IAED;;;;OAIG;IACO,OAAO,CAAC,KAAc;QAC9B,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,KAAK,CAAA;QAC3C,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;YAC3B,sCAAsC;YACtC,OAAO,CAAC,IAAI,CAAC,uDAAuD,EAAE,KAAK,CAAC,CAAA;YAC5E,OAAM;QACR,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;QACtD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,qCAAqC;YACrC,OAAM;QACR,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QAC9B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;QAE3B,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QACtB,CAAC;aAAM,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YAC5B,MAAM,MAAM,GAAG,MAA+B,CAAA;YAC9C,IAAI,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC1B,IAAI,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAA;YACnC,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;YACjC,CAAC;QACH,CAAC;aAAM,CAAC;YACN,sCAAsC;YACtC,OAAO,CAAC,IAAI,CAAC,kCAAkC,EAAE,KAAK,CAAC,CAAA;QACzD,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACO,MAAM,CAAC,KAAqB;QACpC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,KAAK,CAAA;QAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC7B,IAAI,OAAO,GAAG,MAAiB,CAAA;QAC/B,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC1B,KAAK,CAAC,KAAK,EAAE,CAAA;QACf,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,2DAA2D;YAC3D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnB,sCAAsC;gBACtC,OAAO,CAAC,IAAI,CAAC,2BAA2B,EAAE,IAAI,CAAC,CAAA;gBAC/C,OAAM;YACR,CAAC;YACD,2DAA2D;YAC3D,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;QACzB,CAAC;QACD,CAAC;QAAC,OAAoB,CAAC,GAAG,IAAI,CAAC,CAAA;IACjC,CAAC;CACF","sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-function-type */\nimport { ApiError, type IApiError } from '@api-client/core/runtime/store/Errors.js'\nimport { Events } from '../../events/Events.js'\n\n/**\n * A command sent from the client to a IO thread.\n */\nexport interface IoCommand {\n kind: 'IO#Command'\n id: number\n fn: string\n args: unknown[]\n}\n\n/**\n * An event dispatched from the IO thread to the clients.\n */\nexport interface IoEvent {\n kind: 'IO#Event'\n id: number\n type: 'error' | 'result'\n message?: string\n result?: unknown\n}\n\n/**\n * A special kind of an event from the IO thread to the clients that asks to notify the client\n * using the events system.\n */\nexport interface IoNotification {\n kind: 'IO#Notification'\n path: string\n args: unknown[]\n}\n\n/**\n * Events queue on the client side.\n * This is used when communication with the IO thread via Promises is impossible and event based.\n * The client wraps the execution into a promise and pushes this schema into the queue.\n * When the event is dispatched by the IO thread it resolves/rejects cached promise.\n */\nexport interface QueueItem {\n id: number\n resolve: (value: unknown) => void\n reject: (reason?: Error) => void\n // helpful for debugging\n fn: string\n args: unknown[]\n}\n\n/**\n * A base class for all platform bindings.\n *\n * Platform bindings is the way how the application runs a platform specific logic.\n *\n * For example, it implements how the application stores the state or implements file picker / file saver.\n * Depending on the platform (Electron, web, Chrome, more?) it uses different set of bindings\n * defined in the target application. This creates a framework for the bindings to exist.\n *\n * The IO thread can be any process that is separated from the browser tab context.\n * Can be a WebWorker, ServiceWorker, of an API call to a server.\n * When communication with the binding is not possible via the Promise API\n * this system should be used to communicate with the background page.\n *\n * ```typescript\n * class MyBinding extends ParentBaseBinding {\n * channel: BroadcastChannel;\n * constructor() {\n * super();\n * this.channel = new BroadcastChannel('my-channel');\n * }\n *\n * doStuff(arg: any): Promise<any> {\n * return this.invoke('doStuff', 'own argument', arg);\n * }\n * }\n * ```\n *\n * This assumes that the IO thread has the `doStuff` function implemented.\n * The IO thread extends the `IoThread` class which has the defined the communication logic.\n */\nexport abstract class PlatformBindings {\n /**\n * Used with the queue as the identifier of the request.\n * USe the `getId()` to read a unique request id.\n */\n private id = 0\n\n protected queue: QueueItem[] = []\n\n /**\n * Initializes the bindings.\n */\n abstract initialize(): Promise<void>\n\n /**\n * @returns A unique for the current session id.\n */\n protected getId(): number {\n this.id += 1\n return this.id\n }\n\n /**\n * This method to be implemented by child classes that uses the events system to communicate with the\n * IO thread. The argument is the prepared command message ready to be sent to the IO thread.\n * Specific implementation of the communication channel is left to the child class.\n *\n * @param cmd The command to send to the IO thread.\n */\n protected abstract sendIoCommand(cmd: IoCommand): void\n\n /**\n * Invokes a function in the IO thread.\n *\n * @param fn The function name to invoke (or an identifier understood by the IO thread)\n * @param args The function arguments to pass.\n * @returns A promise that should be returned to the client's application logic.\n * The promise is resolved when the IO thread sends an event with the same identifier generated with this call.\n */\n protected invoke(fn: string, ...args: unknown[]): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const id = this.getId()\n const item = {\n resolve,\n reject,\n id,\n fn,\n args,\n }\n this.queue.push(item)\n const cmd: IoCommand = {\n id,\n fn,\n args,\n kind: 'IO#Command',\n }\n this.sendIoCommand(cmd)\n })\n }\n\n /**\n * To be called by the child class when the IO thread send an event.\n * It recognizes the type of the message sent by the IO thread an performs the requested operation.\n *\n * @param message The message sent by the IO thread.\n */\n protected handleIoMessage(message: IoCommand | IoEvent | IoNotification): void {\n if (!message.kind) {\n // eslint-disable-next-line no-console\n console.warn('Invalid message received on the app-config-channel.', message)\n return\n }\n if (message.kind === 'IO#Command') {\n // message sent by self.\n return\n }\n if (message.kind === 'IO#Notification') {\n this.notify(message)\n } else if (message.kind === 'IO#Event') {\n this.resolve(message)\n }\n }\n\n /**\n * Resolves a pending promise stored in the `queue`.\n *\n * @param event The IO thread event\n */\n protected resolve(event: IoEvent): void {\n const { id, type, message, result } = event\n if (typeof id !== 'number') {\n // eslint-disable-next-line no-console\n console.warn(`Unknown event from the IO thread. Id is not a number.`, event)\n return\n }\n const index = this.queue.findIndex((i) => i.id === id)\n if (index < 0) {\n // this might be used by another tab.\n return\n }\n const info = this.queue[index]\n this.queue.splice(index, 1)\n\n if (type === 'result') {\n info.resolve(result)\n } else if (type === 'error') {\n const apiErr = result as IApiError | undefined\n if (apiErr && apiErr.code) {\n info.reject(new ApiError(apiErr))\n } else {\n info.reject(new Error(message))\n }\n } else {\n // eslint-disable-next-line no-console\n console.warn(`Unknown event from the IO thread`, event)\n }\n }\n\n /**\n * Notifies this tab about something using the `Events` definition.\n *\n * This expects that the event has the path to the event function,\n * for example `Events.Config.Environment.State.created`.\n *\n * @param event The IO thread event\n */\n protected notify(event: IoNotification): void {\n const { path, args } = event\n const parts = path.split('.')\n let current = Events as unknown\n if (parts[0] === 'Events') {\n parts.shift()\n }\n for (const part of parts) {\n // @ts-expect-error It's dynamic access to a deep property.\n if (!current[part]) {\n // eslint-disable-next-line no-console\n console.warn(`Invalid notification path`, path)\n return\n }\n // @ts-expect-error It's dynamic access to a deep property.\n current = current[part]\n }\n ;(current as Function)(...args)\n }\n}\n"]}
1
+ {"version":3,"file":"PlatformBindings.js","sourceRoot":"","sources":["../../../../src/bindings/base/PlatformBindings.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-function-type */\nimport { Exception, type ExceptionSchema } from '@api-client/core/exceptions/exception.js'\nimport { Events } from '../../events/Events.js'\n\n/**\n * A command sent from the client to a IO thread.\n */\nexport interface IoCommand {\n kind: 'IO#Command'\n id: number\n fn: string\n args: unknown[]\n}\n\n/**\n * An event dispatched from the IO thread to the clients.\n */\nexport interface IoEvent {\n kind: 'IO#Event'\n id: number\n type: 'error' | 'result'\n message?: string\n result?: unknown\n}\n\n/**\n * A special kind of an event from the IO thread to the clients that asks to notify the client\n * using the events system.\n */\nexport interface IoNotification {\n kind: 'IO#Notification'\n path: string\n args: unknown[]\n}\n\n/**\n * Events queue on the client side.\n * This is used when communication with the IO thread via Promises is impossible and event based.\n * The client wraps the execution into a promise and pushes this schema into the queue.\n * When the event is dispatched by the IO thread it resolves/rejects cached promise.\n */\nexport interface QueueItem {\n id: number\n resolve: (value: unknown) => void\n reject: (reason?: Error) => void\n // helpful for debugging\n fn: string\n args: unknown[]\n}\n\n/**\n * A base class for all platform bindings.\n *\n * Platform bindings is the way how the application runs a platform specific logic.\n *\n * For example, it implements how the application stores the state or implements file picker / file saver.\n * Depending on the platform (Electron, web, Chrome, more?) it uses different set of bindings\n * defined in the target application. This creates a framework for the bindings to exist.\n *\n * The IO thread can be any process that is separated from the browser tab context.\n * Can be a WebWorker, ServiceWorker, of an API call to a server.\n * When communication with the binding is not possible via the Promise API\n * this system should be used to communicate with the background page.\n *\n * ```typescript\n * class MyBinding extends ParentBaseBinding {\n * channel: BroadcastChannel;\n * constructor() {\n * super();\n * this.channel = new BroadcastChannel('my-channel');\n * }\n *\n * doStuff(arg: any): Promise<any> {\n * return this.invoke('doStuff', 'own argument', arg);\n * }\n * }\n * ```\n *\n * This assumes that the IO thread has the `doStuff` function implemented.\n * The IO thread extends the `IoThread` class which has the defined the communication logic.\n */\nexport abstract class PlatformBindings {\n /**\n * Used with the queue as the identifier of the request.\n * USe the `getId()` to read a unique request id.\n */\n private id = 0\n\n protected queue: QueueItem[] = []\n\n /**\n * Initializes the bindings.\n */\n abstract initialize(): Promise<void>\n\n /**\n * @returns A unique for the current session id.\n */\n protected getId(): number {\n this.id += 1\n return this.id\n }\n\n /**\n * This method to be implemented by child classes that uses the events system to communicate with the\n * IO thread. The argument is the prepared command message ready to be sent to the IO thread.\n * Specific implementation of the communication channel is left to the child class.\n *\n * @param cmd The command to send to the IO thread.\n */\n protected abstract sendIoCommand(cmd: IoCommand): void\n\n /**\n * Invokes a function in the IO thread.\n *\n * @param fn The function name to invoke (or an identifier understood by the IO thread)\n * @param args The function arguments to pass.\n * @returns A promise that should be returned to the client's application logic.\n * The promise is resolved when the IO thread sends an event with the same identifier generated with this call.\n */\n protected invoke(fn: string, ...args: unknown[]): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const id = this.getId()\n const item = {\n resolve,\n reject,\n id,\n fn,\n args,\n }\n this.queue.push(item)\n const cmd: IoCommand = {\n id,\n fn,\n args,\n kind: 'IO#Command',\n }\n this.sendIoCommand(cmd)\n })\n }\n\n /**\n * To be called by the child class when the IO thread send an event.\n * It recognizes the type of the message sent by the IO thread an performs the requested operation.\n *\n * @param message The message sent by the IO thread.\n */\n protected handleIoMessage(message: IoCommand | IoEvent | IoNotification): void {\n if (!message.kind) {\n // eslint-disable-next-line no-console\n console.warn('Invalid message received on the app-config-channel.', message)\n return\n }\n if (message.kind === 'IO#Command') {\n // message sent by self.\n return\n }\n if (message.kind === 'IO#Notification') {\n this.notify(message)\n } else if (message.kind === 'IO#Event') {\n this.resolve(message)\n }\n }\n\n /**\n * Resolves a pending promise stored in the `queue`.\n *\n * @param event The IO thread event\n */\n protected resolve(event: IoEvent): void {\n const { id, type, message, result } = event\n if (typeof id !== 'number') {\n // eslint-disable-next-line no-console\n console.warn(`Unknown event from the IO thread. Id is not a number.`, event)\n return\n }\n const index = this.queue.findIndex((i) => i.id === id)\n if (index < 0) {\n // this might be used by another tab.\n return\n }\n const info = this.queue[index]\n this.queue.splice(index, 1)\n\n if (type === 'result') {\n info.resolve(result)\n } else if (type === 'error') {\n const apiErr = result as ExceptionSchema | undefined\n if (apiErr && apiErr.code) {\n info.reject(Exception.fromRawException(apiErr, 'Unknown exception'))\n } else {\n info.reject(new Error(message))\n }\n } else {\n // eslint-disable-next-line no-console\n console.warn(`Unknown event from the IO thread`, event)\n }\n }\n\n /**\n * Notifies this tab about something using the `Events` definition.\n *\n * This expects that the event has the path to the event function,\n * for example `Events.Config.Environment.State.created`.\n *\n * @param event The IO thread event\n */\n protected notify(event: IoNotification): void {\n const { path, args } = event\n const parts = path.split('.')\n let current = Events as unknown\n if (parts[0] === 'Events') {\n parts.shift()\n }\n for (const part of parts) {\n // @ts-expect-error It's dynamic access to a deep property.\n if (!current[part]) {\n // eslint-disable-next-line no-console\n console.warn(`Invalid notification path`, path)\n return\n }\n // @ts-expect-error It's dynamic access to a deep property.\n current = current[part]\n }\n ;(current as Function)(...args)\n }\n}\n"]}
@@ -1,3 +1,43 @@
1
+ import type { Exception } from '@api-client/core/exceptions/exception.js';
2
+ /**
3
+ * Defines the options for the retry mechanism.
4
+ */
5
+ export interface RetryOptions {
6
+ /**
7
+ * The maximum number of retry attempts.
8
+ * Defaults to 3.
9
+ */
10
+ retries?: number;
11
+ /**
12
+ * The delay in milliseconds before the next retry.
13
+ * Can be a fixed number or a function that returns a number (e.g., for exponential backoff).
14
+ * Defaults to 1000ms.
15
+ */
16
+ delayMs?: number | ((attempt: number) => number);
17
+ /**
18
+ * An optional function that determines if a retry should be attempted based on the error.
19
+ * If not provided, all errors will trigger a retry up to the maximum number of retries.
20
+ * @param error The error that occurred.
21
+ * @returns True if a retry should be attempted, false otherwise.
22
+ */
23
+ shouldRetry?: (error: Error) => boolean;
24
+ }
25
+ /**
26
+ * Calculates an exponential backoff delay for retry attempts.
27
+ * The delay doubles with each attempt.
28
+ *
29
+ * @param attempt The current retry attempt number (should be 1-indexed).
30
+ * @param initialDelay The initial delay in milliseconds. Defaults to 1000ms.
31
+ * @returns The calculated delay in milliseconds.
32
+ */
33
+ export declare function exponentialBackoffDelay(attempt: number, initialDelay?: number): number;
34
+ /**
35
+ * Checks if the API error should be retried based on its status code.
36
+ * If the status code is 404, it returns false (do not retry).
37
+ * For other status codes, it returns true (retry).
38
+ * @param error The error to check.
39
+ */
40
+ export declare function shouldRetryApiError(error: Exception): boolean;
1
41
  /**
2
42
  * The base class for models.
3
43
  *
@@ -36,6 +76,10 @@ export declare abstract class Model<T> extends EventTarget {
36
76
  * @type A promise resolved when the debounced task finished.
37
77
  */
38
78
  get taskComplete(): Promise<void> | undefined;
79
+ /**
80
+ * A getter for the raw value.
81
+ */
82
+ get value(): T | undefined;
39
83
  /**
40
84
  * @param obj The source object to use.
41
85
  */
@@ -53,5 +97,28 @@ export declare abstract class Model<T> extends EventTarget {
53
97
  debounce(callback: (...args: unknown[]) => unknown): void;
54
98
  notifyError(error: Error): void;
55
99
  protected resolveTaskPromise(): void;
100
+ /**
101
+ * Executes an asynchronous operation with a retry mechanism.
102
+ * @param operation A function that returns a Promise to be executed.
103
+ * @param options Optional retry configurations.
104
+ * @returns A Promise that resolves with the result of the operation or rejects if all retries fail.
105
+ * @example
106
+ * ```typescript
107
+ * override async create(data: Partial<IUser>): Promise<IUser> {
108
+ * const operation = async (): Promise<UserData> => {
109
+ * // ... perform API call to create user
110
+ * }
111
+ * const retryOptions: RetryOptions = {
112
+ * retries: 5,
113
+ * delayMs: exponentialBackoffDelay,
114
+ * shouldRetry: shouldRetryApiError,
115
+ * }
116
+ * this.raw = await this.retry(operation)
117
+ * this.dispatchEvent(new CustomEvent('data-changed', { detail: this.raw }))
118
+ * return this.raw;
119
+ * }
120
+ * ```
121
+ */
122
+ protected retry<R>(operation: () => Promise<R>, options?: RetryOptions): Promise<R>;
56
123
  }
57
124
  //# sourceMappingURL=Model.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Model.d.ts","sourceRoot":"","sources":["../../../src/core/Model.ts"],"names":[],"mappings":"AACA;;;;;;;;;GASG;AACH,8BAAsB,KAAK,CAAC,CAAC,CAAE,SAAQ,WAAW;;IAChD;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,CAAC,CAAA;IAEP;;;;OAIG;IACH,eAAe,SAAM;IAErB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,OAAO,CAAA;IAExC;;OAEG;IACH,KAAK,SAAK;IAiBV;;OAEG;IACH,IAAI,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAE5C;IAED;;OAEG;gBACS,GAAG,CAAC,EAAE,CAAC;IAMnB;;;;;;;;OAQG;IACH,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAIpC,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,IAAI;IAqBzD,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAe/B,SAAS,CAAC,kBAAkB,IAAI,IAAI;CAUrC"}
1
+ {"version":3,"file":"Model.d.ts","sourceRoot":"","sources":["../../../src/core/Model.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0CAA0C,CAAA;AAEzE;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,CAAA;IAChD;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAA;CACxC;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,SAAO,GAAG,MAAM,CAIpF;AACD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAO7D;AACD;;;;;;;;;GASG;AACH,8BAAsB,KAAK,CAAC,CAAC,CAAE,SAAQ,WAAW;;IAChD;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,CAAC,CAAA;IAEP;;;;OAIG;IACH,eAAe,SAAM;IAErB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,OAAO,CAAA;IAExC;;OAEG;IACH,KAAK,SAAK;IAiBV;;OAEG;IACH,IAAI,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAE5C;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,CAAC,GAAG,SAAS,CAEzB;IAED;;OAEG;gBACS,GAAG,CAAC,EAAE,CAAC;IAMnB;;;;;;;;OAQG;IACH,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAIpC,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,IAAI;IAqBzD,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAe/B,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAWpC;;;;;;;;;;;;;;;;;;;;;OAqBG;cACa,KAAK,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,CAAC,CAAC;CAmC9F"}
@@ -1,4 +1,32 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ /**
3
+ * Calculates an exponential backoff delay for retry attempts.
4
+ * The delay doubles with each attempt.
5
+ *
6
+ * @param attempt The current retry attempt number (should be 1-indexed).
7
+ * @param initialDelay The initial delay in milliseconds. Defaults to 1000ms.
8
+ * @returns The calculated delay in milliseconds.
9
+ */
10
+ export function exponentialBackoffDelay(attempt, initialDelay = 1000) {
11
+ if (attempt <= 0)
12
+ return initialDelay;
13
+ // For attempt 1, delay is initialDelay * 2^0 = initialDelay
14
+ return initialDelay * Math.pow(2, attempt - 1);
15
+ }
16
+ /**
17
+ * Checks if the API error should be retried based on its status code.
18
+ * If the status code is 404, it returns false (do not retry).
19
+ * For other status codes, it returns true (retry).
20
+ * @param error The error to check.
21
+ */
22
+ export function shouldRetryApiError(error) {
23
+ const { status } = error;
24
+ if (status === 404) {
25
+ // Do not retry on 404 errors
26
+ return false;
27
+ }
28
+ return true;
29
+ }
2
30
  /**
3
31
  * The base class for models.
4
32
  *
@@ -50,6 +78,12 @@ export class Model extends EventTarget {
50
78
  get taskComplete() {
51
79
  return this.#taskComplete;
52
80
  }
81
+ /**
82
+ * A getter for the raw value.
83
+ */
84
+ get value() {
85
+ return this.raw;
86
+ }
53
87
  /**
54
88
  * @param obj The source object to use.
55
89
  */
@@ -113,5 +147,60 @@ export class Model extends EventTarget {
113
147
  resolver();
114
148
  }
115
149
  }
150
+ /**
151
+ * Executes an asynchronous operation with a retry mechanism.
152
+ * @param operation A function that returns a Promise to be executed.
153
+ * @param options Optional retry configurations.
154
+ * @returns A Promise that resolves with the result of the operation or rejects if all retries fail.
155
+ * @example
156
+ * ```typescript
157
+ * override async create(data: Partial<IUser>): Promise<IUser> {
158
+ * const operation = async (): Promise<UserData> => {
159
+ * // ... perform API call to create user
160
+ * }
161
+ * const retryOptions: RetryOptions = {
162
+ * retries: 5,
163
+ * delayMs: exponentialBackoffDelay,
164
+ * shouldRetry: shouldRetryApiError,
165
+ * }
166
+ * this.raw = await this.retry(operation)
167
+ * this.dispatchEvent(new CustomEvent('data-changed', { detail: this.raw }))
168
+ * return this.raw;
169
+ * }
170
+ * ```
171
+ */
172
+ async retry(operation, options = {}) {
173
+ const { retries = 3, delayMs = 1000, shouldRetry = () => true } = options;
174
+ let lastError;
175
+ for (let attempt = 0; attempt < retries; attempt++) {
176
+ try {
177
+ return await operation();
178
+ }
179
+ catch (e) {
180
+ lastError = e;
181
+ if (!shouldRetry(lastError) || attempt === retries - 1) {
182
+ // Do not retry if shouldRetry returns false or if it's the last attempt
183
+ this.notifyError(lastError);
184
+ throw lastError;
185
+ }
186
+ const delay = typeof delayMs === 'function' ? delayMs(attempt + 1) : delayMs;
187
+ if (delay > 0) {
188
+ await new Promise((resolve) => setTimeout(resolve, delay));
189
+ }
190
+ }
191
+ }
192
+ // This part should ideally not be reached if retries > 0,
193
+ // as the loop would throw on the last attempt.
194
+ // However, to satisfy TypeScript and guard against retries = 0:
195
+ if (lastError) {
196
+ this.notifyError(lastError);
197
+ throw lastError;
198
+ }
199
+ // Should not happen if operation is called at least once.
200
+ // Adding a generic error for completeness.
201
+ const err = new Error('Retry operation failed without a specific error.');
202
+ this.notifyError(err);
203
+ throw err;
204
+ }
116
205
  }
117
206
  //# sourceMappingURL=Model.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Model.js","sourceRoot":"","sources":["../../../src/core/Model.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD;;;;;;;;;GASG;AACH,MAAM,OAAgB,KAAS,SAAQ,WAAW;IAChD;;;;;;OAMG;IACH,GAAG,CAAI;IAEP;;;;OAIG;IACH,eAAe,GAAG,GAAG,CAAA;IAErB;;OAEG;IACH,cAAc,CAA0B;IAExC;;OAEG;IACH,KAAK,GAAG,EAAE,CAAA;IAEV;;OAEG;IACH,sBAAsB,GAAG,KAAK,CAAA;IAE9B;;OAEG;IACH,aAAa,CAAgB;IAE7B;;OAEG;IACH,aAAa,CAAa;IAE1B;;OAEG;IACH,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,aAAa,CAAA;IAC3B,CAAC;IAED;;OAEG;IACH,YAAY,GAAO;QACjB,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,GAAG,GAAG,GAAG,CAAA;QACd,IAAI,CAAC,iBAAiB,EAAE,CAAA;IAC1B,CAAC;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,IAAgB;QACrB,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAA;IACrC,CAAC;IAED,QAAQ,CAAC,QAAyC;QAChD,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;QACnC,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,CAAC;YACjC,IAAI,CAAC,iBAAiB,EAAE,CAAA;QAC1B,CAAC;QACD,IAAI,CAAC,cAAc,GAAG,UAAU,CAAC,KAAK,IAAI,EAAE;YAC1C,IAAI,CAAC,cAAc,GAAG,SAAS,CAAA;YAC/B,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAC3B,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,MAAM,GAAG,GAAG,CAAU,CAAA;gBACtB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;gBACrB,MAAM,GAAG,CAAA;YACX,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,kBAAkB,EAAE,CAAA;YAC3B,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,CAAA;IAC1B,CAAC;IAED,WAAW,CAAC,KAAY;QACtB,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAQ,OAAO,EAAE;YAC9B,MAAM,EAAE,KAAK;SACd,CAAC,CACH,CAAA;IACH,CAAC;IAED,iBAAiB;QACf,IAAI,CAAC,aAAa,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACjD,IAAI,CAAC,aAAa,GAAG,OAAO,CAAA;YAC5B,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAA;QACpC,CAAC,CAAC,CAAA;IACJ,CAAC;IAES,kBAAkB;QAC1B,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,CAAC;YACjC,OAAM;QACR,CAAC;QACD,IAAI,CAAC,sBAAsB,GAAG,KAAK,CAAA;QACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAA;QACnC,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,EAAE,CAAA;QACZ,CAAC;IACH,CAAC;CACF","sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\n/**\n * The base class for models.\n *\n * Models wrap a logic related to manipulating model data, querying for data from the API,\n * retry logic, cashing, refreshing of data, offline storage, pooling endpoints for new data, etc.\n *\n * The application should use these models in order to centralize object manipulation.\n *\n * @template T The underlying domain model.\n */\nexport abstract class Model<T> extends EventTarget {\n /**\n * The underlying raw object.\n * Do not modify properties of the object directly.\n * Use one of the methods provided by the model.\n * Direct changes will not be reflected in the UI\n * and may cause issues when processing the object.\n */\n raw?: T\n\n /**\n * The timeout for the query debouncer.\n * When any property change this is the time the element will wait\n * until the actual query is made.\n */\n debounceTimeout = 100\n\n /**\n * A reference to the current debouncer.\n */\n debouncerValue?: number | NodeJS.Timeout\n\n /**\n * Default limit for list queries.\n */\n limit = 30\n\n /**\n * A flag that helps to determine whether the `taskComplete` is setup.\n */\n #hasPendingTaskPromise = false\n\n /**\n * A hidden value for the `taskComplete` getter.\n */\n #taskComplete?: Promise<void>\n\n /**\n * The resolver to call when the debounced task completes.\n */\n #taskResolver?: () => void\n\n /**\n * @type A promise resolved when the debounced task finished.\n */\n get taskComplete(): Promise<void> | undefined {\n return this.#taskComplete\n }\n\n /**\n * @param obj The source object to use.\n */\n constructor(obj?: T) {\n super()\n this.raw = obj\n this.#setUpdatePromise()\n }\n\n /**\n * Creates a new instance of the data. It often invokes API call.\n * This is what happens after the user triggers \"create new\" flow.\n *\n * After this method is calls, the `raw` object is set.\n *\n * @param data The partial data to create.\n * @returns The created object.\n */\n create(data: Partial<T>): Promise<T> {\n throw new Error(`Not implemented.`)\n }\n\n debounce(callback: (...args: unknown[]) => unknown): void {\n if (this.debouncerValue) {\n clearTimeout(this.debouncerValue)\n }\n if (!this.#hasPendingTaskPromise) {\n this.#setUpdatePromise()\n }\n this.debouncerValue = setTimeout(async () => {\n this.debouncerValue = undefined\n try {\n await callback.call(this)\n } catch (e) {\n const err = e as Error\n this.notifyError(err)\n throw err\n } finally {\n this.resolveTaskPromise()\n }\n }, this.debounceTimeout)\n }\n\n notifyError(error: Error): void {\n this.dispatchEvent(\n new CustomEvent<Error>('error', {\n detail: error,\n })\n )\n }\n\n #setUpdatePromise(): void {\n this.#taskComplete = new Promise<void>((resolve) => {\n this.#taskResolver = resolve\n this.#hasPendingTaskPromise = true\n })\n }\n\n protected resolveTaskPromise(): void {\n if (!this.#hasPendingTaskPromise) {\n return\n }\n this.#hasPendingTaskPromise = false\n const resolver = this.#taskResolver\n if (resolver) {\n resolver()\n }\n }\n}\n"]}
1
+ {"version":3,"file":"Model.js","sourceRoot":"","sources":["../../../src/core/Model.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\n\nimport type { Exception } from '@api-client/core/exceptions/exception.js'\n\n/**\n * Defines the options for the retry mechanism.\n */\nexport interface RetryOptions {\n /**\n * The maximum number of retry attempts.\n * Defaults to 3.\n */\n retries?: number\n /**\n * The delay in milliseconds before the next retry.\n * Can be a fixed number or a function that returns a number (e.g., for exponential backoff).\n * Defaults to 1000ms.\n */\n delayMs?: number | ((attempt: number) => number)\n /**\n * An optional function that determines if a retry should be attempted based on the error.\n * If not provided, all errors will trigger a retry up to the maximum number of retries.\n * @param error The error that occurred.\n * @returns True if a retry should be attempted, false otherwise.\n */\n shouldRetry?: (error: Error) => boolean\n}\n\n/**\n * Calculates an exponential backoff delay for retry attempts.\n * The delay doubles with each attempt.\n *\n * @param attempt The current retry attempt number (should be 1-indexed).\n * @param initialDelay The initial delay in milliseconds. Defaults to 1000ms.\n * @returns The calculated delay in milliseconds.\n */\nexport function exponentialBackoffDelay(attempt: number, initialDelay = 1000): number {\n if (attempt <= 0) return initialDelay\n // For attempt 1, delay is initialDelay * 2^0 = initialDelay\n return initialDelay * Math.pow(2, attempt - 1)\n}\n/**\n * Checks if the API error should be retried based on its status code.\n * If the status code is 404, it returns false (do not retry).\n * For other status codes, it returns true (retry).\n * @param error The error to check.\n */\nexport function shouldRetryApiError(error: Exception): boolean {\n const { status } = error\n if (status === 404) {\n // Do not retry on 404 errors\n return false\n }\n return true\n}\n/**\n * The base class for models.\n *\n * Models wrap a logic related to manipulating model data, querying for data from the API,\n * retry logic, cashing, refreshing of data, offline storage, pooling endpoints for new data, etc.\n *\n * The application should use these models in order to centralize object manipulation.\n *\n * @template T The underlying domain model.\n */\nexport abstract class Model<T> extends EventTarget {\n /**\n * The underlying raw object.\n * Do not modify properties of the object directly.\n * Use one of the methods provided by the model.\n * Direct changes will not be reflected in the UI\n * and may cause issues when processing the object.\n */\n raw?: T\n\n /**\n * The timeout for the query debouncer.\n * When any property change this is the time the element will wait\n * until the actual query is made.\n */\n debounceTimeout = 100\n\n /**\n * A reference to the current debouncer.\n */\n debouncerValue?: number | NodeJS.Timeout\n\n /**\n * Default limit for list queries.\n */\n limit = 30\n\n /**\n * A flag that helps to determine whether the `taskComplete` is setup.\n */\n #hasPendingTaskPromise = false\n\n /**\n * A hidden value for the `taskComplete` getter.\n */\n #taskComplete?: Promise<void>\n\n /**\n * The resolver to call when the debounced task completes.\n */\n #taskResolver?: () => void\n\n /**\n * @type A promise resolved when the debounced task finished.\n */\n get taskComplete(): Promise<void> | undefined {\n return this.#taskComplete\n }\n\n /**\n * A getter for the raw value.\n */\n get value(): T | undefined {\n return this.raw\n }\n\n /**\n * @param obj The source object to use.\n */\n constructor(obj?: T) {\n super()\n this.raw = obj\n this.#setUpdatePromise()\n }\n\n /**\n * Creates a new instance of the data. It often invokes API call.\n * This is what happens after the user triggers \"create new\" flow.\n *\n * After this method is calls, the `raw` object is set.\n *\n * @param data The partial data to create.\n * @returns The created object.\n */\n create(data: Partial<T>): Promise<T> {\n throw new Error(`Not implemented.`)\n }\n\n debounce(callback: (...args: unknown[]) => unknown): void {\n if (this.debouncerValue) {\n clearTimeout(this.debouncerValue)\n }\n if (!this.#hasPendingTaskPromise) {\n this.#setUpdatePromise()\n }\n this.debouncerValue = setTimeout(async () => {\n this.debouncerValue = undefined\n try {\n await callback.call(this)\n } catch (e) {\n const err = e as Error\n this.notifyError(err)\n throw err\n } finally {\n this.resolveTaskPromise()\n }\n }, this.debounceTimeout)\n }\n\n notifyError(error: Error): void {\n this.dispatchEvent(\n new CustomEvent<Error>('error', {\n detail: error,\n })\n )\n }\n\n #setUpdatePromise(): void {\n this.#taskComplete = new Promise<void>((resolve) => {\n this.#taskResolver = resolve\n this.#hasPendingTaskPromise = true\n })\n }\n\n protected resolveTaskPromise(): void {\n if (!this.#hasPendingTaskPromise) {\n return\n }\n this.#hasPendingTaskPromise = false\n const resolver = this.#taskResolver\n if (resolver) {\n resolver()\n }\n }\n\n /**\n * Executes an asynchronous operation with a retry mechanism.\n * @param operation A function that returns a Promise to be executed.\n * @param options Optional retry configurations.\n * @returns A Promise that resolves with the result of the operation or rejects if all retries fail.\n * @example\n * ```typescript\n * override async create(data: Partial<IUser>): Promise<IUser> {\n * const operation = async (): Promise<UserData> => {\n * // ... perform API call to create user\n * }\n * const retryOptions: RetryOptions = {\n * retries: 5,\n * delayMs: exponentialBackoffDelay,\n * shouldRetry: shouldRetryApiError,\n * }\n * this.raw = await this.retry(operation)\n * this.dispatchEvent(new CustomEvent('data-changed', { detail: this.raw }))\n * return this.raw;\n * }\n * ```\n */\n protected async retry<R>(operation: () => Promise<R>, options: RetryOptions = {}): Promise<R> {\n const { retries = 3, delayMs = 1000, shouldRetry = () => true } = options\n\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt < retries; attempt++) {\n try {\n return await operation()\n } catch (e) {\n lastError = e as Error\n if (!shouldRetry(lastError) || attempt === retries - 1) {\n // Do not retry if shouldRetry returns false or if it's the last attempt\n this.notifyError(lastError)\n throw lastError\n }\n\n const delay = typeof delayMs === 'function' ? delayMs(attempt + 1) : delayMs\n if (delay > 0) {\n await new Promise((resolve) => setTimeout(resolve, delay))\n }\n }\n }\n // This part should ideally not be reached if retries > 0,\n // as the loop would throw on the last attempt.\n // However, to satisfy TypeScript and guard against retries = 0:\n if (lastError) {\n this.notifyError(lastError)\n throw lastError\n }\n // Should not happen if operation is called at least once.\n // Adding a generic error for completeness.\n const err = new Error('Retry operation failed without a specific error.')\n this.notifyError(err)\n throw err\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@api-client/ui",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Internal UI component library for the API Client ecosystem.",
5
5
  "license": "UNLICENSED",
6
6
  "main": "build/src/index.js",
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  /* eslint-disable no-console */
3
- import { ApiError } from '@api-client/core/runtime/store/Errors.js'
3
+ import { Exception } from '@api-client/core/exceptions/exception.js'
4
4
  import { type IoEvent, type IoCommand, type IoNotification } from './PlatformBindings.js'
5
5
 
6
6
  /**
@@ -40,9 +40,9 @@ export abstract class IoThread {
40
40
  }
41
41
 
42
42
  if (typeof fn !== 'string' || !fn) {
43
- const err = new ApiError(
43
+ const err = new Exception(
44
44
  'Invalid message received on the io-channel. The second argument must be a function name to call.',
45
- 400
45
+ { status: 400 }
46
46
  )
47
47
  this.notifyError(id, err)
48
48
  console.warn(err.message)
@@ -53,9 +53,9 @@ export abstract class IoThread {
53
53
  const isFunction = typeof callable === 'function'
54
54
 
55
55
  if (!isFunction) {
56
- const err = new ApiError(
56
+ const err = new Exception(
57
57
  `Invalid message received on the io-channel. The function "${fn}" is either not implemented or invalid.`,
58
- 400
58
+ { status: 400 }
59
59
  )
60
60
  this.notifyError(id, err)
61
61
  console.warn(err.message)
@@ -65,7 +65,7 @@ export abstract class IoThread {
65
65
  this.call(callable, id, args)
66
66
  }
67
67
 
68
- protected notifyError(id: number, error: ApiError): void {
68
+ protected notifyError(id: number, error: Exception): void {
69
69
  const event: IoEvent = {
70
70
  kind: 'IO#Event',
71
71
  id,
@@ -114,7 +114,7 @@ export abstract class IoThread {
114
114
  } catch (e) {
115
115
  event.type = 'error'
116
116
  event.message = (e as Error).message
117
- if ((e as ApiError).code) {
117
+ if ((e as Exception).status) {
118
118
  event.result = e
119
119
  }
120
120
  this.postMessage(event)
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-unsafe-function-type */
2
- import { ApiError, type IApiError } from '@api-client/core/runtime/store/Errors.js'
2
+ import { Exception, type ExceptionSchema } from '@api-client/core/exceptions/exception.js'
3
3
  import { Events } from '../../events/Events.js'
4
4
 
5
5
  /**
@@ -185,9 +185,9 @@ export abstract class PlatformBindings {
185
185
  if (type === 'result') {
186
186
  info.resolve(result)
187
187
  } else if (type === 'error') {
188
- const apiErr = result as IApiError | undefined
188
+ const apiErr = result as ExceptionSchema | undefined
189
189
  if (apiErr && apiErr.code) {
190
- info.reject(new ApiError(apiErr))
190
+ info.reject(Exception.fromRawException(apiErr, 'Unknown exception'))
191
191
  } else {
192
192
  info.reject(new Error(message))
193
193
  }
package/src/core/Model.ts CHANGED
@@ -1,4 +1,58 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-vars */
2
+
3
+ import type { Exception } from '@api-client/core/exceptions/exception.js'
4
+
5
+ /**
6
+ * Defines the options for the retry mechanism.
7
+ */
8
+ export interface RetryOptions {
9
+ /**
10
+ * The maximum number of retry attempts.
11
+ * Defaults to 3.
12
+ */
13
+ retries?: number
14
+ /**
15
+ * The delay in milliseconds before the next retry.
16
+ * Can be a fixed number or a function that returns a number (e.g., for exponential backoff).
17
+ * Defaults to 1000ms.
18
+ */
19
+ delayMs?: number | ((attempt: number) => number)
20
+ /**
21
+ * An optional function that determines if a retry should be attempted based on the error.
22
+ * If not provided, all errors will trigger a retry up to the maximum number of retries.
23
+ * @param error The error that occurred.
24
+ * @returns True if a retry should be attempted, false otherwise.
25
+ */
26
+ shouldRetry?: (error: Error) => boolean
27
+ }
28
+
29
+ /**
30
+ * Calculates an exponential backoff delay for retry attempts.
31
+ * The delay doubles with each attempt.
32
+ *
33
+ * @param attempt The current retry attempt number (should be 1-indexed).
34
+ * @param initialDelay The initial delay in milliseconds. Defaults to 1000ms.
35
+ * @returns The calculated delay in milliseconds.
36
+ */
37
+ export function exponentialBackoffDelay(attempt: number, initialDelay = 1000): number {
38
+ if (attempt <= 0) return initialDelay
39
+ // For attempt 1, delay is initialDelay * 2^0 = initialDelay
40
+ return initialDelay * Math.pow(2, attempt - 1)
41
+ }
42
+ /**
43
+ * Checks if the API error should be retried based on its status code.
44
+ * If the status code is 404, it returns false (do not retry).
45
+ * For other status codes, it returns true (retry).
46
+ * @param error The error to check.
47
+ */
48
+ export function shouldRetryApiError(error: Exception): boolean {
49
+ const { status } = error
50
+ if (status === 404) {
51
+ // Do not retry on 404 errors
52
+ return false
53
+ }
54
+ return true
55
+ }
2
56
  /**
3
57
  * The base class for models.
4
58
  *
@@ -58,6 +112,13 @@ export abstract class Model<T> extends EventTarget {
58
112
  return this.#taskComplete
59
113
  }
60
114
 
115
+ /**
116
+ * A getter for the raw value.
117
+ */
118
+ get value(): T | undefined {
119
+ return this.raw
120
+ }
121
+
61
122
  /**
62
123
  * @param obj The source object to use.
63
124
  */
@@ -126,4 +187,62 @@ export abstract class Model<T> extends EventTarget {
126
187
  resolver()
127
188
  }
128
189
  }
190
+
191
+ /**
192
+ * Executes an asynchronous operation with a retry mechanism.
193
+ * @param operation A function that returns a Promise to be executed.
194
+ * @param options Optional retry configurations.
195
+ * @returns A Promise that resolves with the result of the operation or rejects if all retries fail.
196
+ * @example
197
+ * ```typescript
198
+ * override async create(data: Partial<IUser>): Promise<IUser> {
199
+ * const operation = async (): Promise<UserData> => {
200
+ * // ... perform API call to create user
201
+ * }
202
+ * const retryOptions: RetryOptions = {
203
+ * retries: 5,
204
+ * delayMs: exponentialBackoffDelay,
205
+ * shouldRetry: shouldRetryApiError,
206
+ * }
207
+ * this.raw = await this.retry(operation)
208
+ * this.dispatchEvent(new CustomEvent('data-changed', { detail: this.raw }))
209
+ * return this.raw;
210
+ * }
211
+ * ```
212
+ */
213
+ protected async retry<R>(operation: () => Promise<R>, options: RetryOptions = {}): Promise<R> {
214
+ const { retries = 3, delayMs = 1000, shouldRetry = () => true } = options
215
+
216
+ let lastError: Error | undefined
217
+
218
+ for (let attempt = 0; attempt < retries; attempt++) {
219
+ try {
220
+ return await operation()
221
+ } catch (e) {
222
+ lastError = e as Error
223
+ if (!shouldRetry(lastError) || attempt === retries - 1) {
224
+ // Do not retry if shouldRetry returns false or if it's the last attempt
225
+ this.notifyError(lastError)
226
+ throw lastError
227
+ }
228
+
229
+ const delay = typeof delayMs === 'function' ? delayMs(attempt + 1) : delayMs
230
+ if (delay > 0) {
231
+ await new Promise((resolve) => setTimeout(resolve, delay))
232
+ }
233
+ }
234
+ }
235
+ // This part should ideally not be reached if retries > 0,
236
+ // as the loop would throw on the last attempt.
237
+ // However, to satisfy TypeScript and guard against retries = 0:
238
+ if (lastError) {
239
+ this.notifyError(lastError)
240
+ throw lastError
241
+ }
242
+ // Should not happen if operation is called at least once.
243
+ // Adding a generic error for completeness.
244
+ const err = new Error('Retry operation failed without a specific error.')
245
+ this.notifyError(err)
246
+ throw err
247
+ }
129
248
  }