diff --git a/.gitignore b/.gitignore index c1124a8..e25351e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /node_modules -/docs \ No newline at end of file +/docs +/dist \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..49e0fc6 --- /dev/null +++ b/.npmignore @@ -0,0 +1,2 @@ +/node_modules +/package-lock.json \ No newline at end of file diff --git a/README.md b/README.md index 670c114..76c05cc 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,20 @@ ## Fire Js This contains various helpers and types. -## Unstable -the graphql module is unstable. - -## Usage +### Usage ```json { "dependencies": { "fire": "npm:fire-lib-js@^0.2" } } -``` \ No newline at end of file +``` + +### Unstable +the graphql module is unstable. + +### Time +consider using luxon instead of this time implementation. + +### Deprecated +the entire **data** folder is deprecated either use zod or something like that or just use `Object.assign` and typescript. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 14290d1..f3e398f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,18 @@ { "name": "fire-lib-js", - "version": "0.3.0-alpha.1", + "version": "0.3.0-alpha.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fire-lib-js", - "version": "0.3.0-alpha.1", + "version": "0.3.0-alpha.6", "license": "MIT or Apache-2.0", "devDependencies": { "eslint": "^8.45.0", "prettier": "^3.2.4", + "tslib": "^2.6.2", + "typescript": "^5.2.2", "vitest": "^1.1.3" } }, @@ -2293,6 +2295,12 @@ "node": ">=14.0.0" } }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -2326,6 +2334,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typescript": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/ufo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", diff --git a/package.json b/package.json index 86a2a9a..5eb0ff2 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "scripts": { "test": "vitest", "check": "prettier -c .", - "lint": "eslint ./src" + "lint": "eslint ./src", + "build": "tsc", + "watch": "tsc -w" }, "repository": { "type": "git", @@ -14,10 +16,10 @@ }, "license": "MIT or Apache-2.0", "exports": { - "./api/Api": "./src/api/Api.js", - "./api/ApiError": "./src/api/ApiError.js", - "./api/Stream": "./src/api/Stream.js", - "./collections/SlotArray": "./src/collections/SlotArray.js", + "./api/Api": "./dist/api/Api.js", + "./api/ApiError": "./dist/api/ApiError.js", + "./api/Stream": "./dist/api/Stream.js", + "./collections/SlotArray": "./dist/collections/SlotArray.js", "./data/Data": "./src/data/Data.js", "./data/MapType": "./src/data/MapType.js", "./data/ParseType": "./src/data/ParseType.js", @@ -26,20 +28,22 @@ "./dom/utils": "./src/dom/utils.js", "./graphQl/GraphQl": "./src/graphQl/GraphQl.js", "./graphQl/GraphQlError": "./src/graphQl/GraphQlError.js", - "./sync/Barrier": "./src/sync/Barrier.js", - "./sync/Listeners": "./src/sync/Listeners.js", - "./sync/NonConcurrent": "./src/sync/NonConcurrent.js", + "./sync/Barrier": "./dist/sync/Barrier.js", + "./sync/Listeners": "./dist/sync/Listeners.js", + "./sync/NonConcurrent": "./dist/sync/NonConcurrent.js", "./time/Date": "./src/time/Date.js", "./time/DateTime": "./src/time/DateTime.js", "./time/DateTimeRange": "./src/time/DateTimeRange.js", "./time/Duration": "./src/time/Duration.js", "./time/localization": "./src/time/localization.js", - "./utils": "./src/utils/utils.js", - "./utils/colors": "./src/utils/colors.js" + "./utils": "./dist/utils/utils.js", + "./utils/colors": "./dist/utils/colors.js" }, "devDependencies": { "eslint": "^8.45.0", "vitest": "^1.1.3", - "prettier": "^3.2.4" + "prettier": "^3.2.4", + "tslib": "^2.6.2", + "typescript": "^5.2.2" } } diff --git a/src/api/Api.d.ts b/src/api/Api.d.ts deleted file mode 100644 index 9de3b3b..0000000 --- a/src/api/Api.d.ts +++ /dev/null @@ -1,78 +0,0 @@ -import ApiError from './ApiError'; - -/** - * Api class to handle requests to a server - */ -export default class Api { - /** - * get or set the api address - */ - addr: string; - - /** - * Creates a new Api instance - */ - constructor(addr?: string); - - /** - * Prepares a json object to be sent as a header - */ - static jsonHeader(data: object): string; - - /** - * Send a request to the server - * - * @param {string} method - The method of the request - * @param {string} path - The path of the request - * @param {object|null} data - The data to be sent - * @param {object} headers - The headers to be sent - * @param {object} opts - The additional options to be sent to fetch - * - * @returns The response of the request - * - * @throws {ApiError} - If the request fails - */ - request( - method: string, - path: string, - data?: object | null, - headers?: object, - opts?: object, - ): Promise; - - /** - * Send a request to the server with a file - * - * @param {string} method - The method of the request - * @param {string} path - The path of the request - * @param {File} file - The file to be sent - * @param {function|null} progress - The progress callback - * @param {object} headers - The headers to be sent - * - * @throws {ApiError} - If the request fails - */ - requestWithFile( - method: string, - path: string, - file: File, - progress: ((event: ProgressEvent) => void) | null, - headers: object, - ): Promise; - - /** - * Send a request to the server with a timeout - * - * @param {string} method - The method of the request - * @param {string} path - The path of the request - * @param {object|null} data - The data to be sent - * @param {object} headers - The headers to be sent - * @param {number} timeout - The timeout of the request if the value is 0 there is no timeout - */ - requestTimeout( - method: string, - path: string, - data?: object | null, - headers?: object, - timeout?: number, - ): Promise; -} diff --git a/src/api/Api.js b/src/api/Api.ts similarity index 60% rename from src/api/Api.js rename to src/api/Api.ts index 22be9ae..15a9a30 100644 --- a/src/api/Api.js +++ b/src/api/Api.ts @@ -1,30 +1,48 @@ -import ApiError from './ApiError.js'; +import ApiError from './ApiError'; /** * Api class to handle requests to a server */ export default class Api { - constructor(addr = null) { + /** + * get or set the api address + */ + addr: string; + + /** + * Creates a new Api instance + */ + constructor(addr: string) { this.addr = addr; } /** * Prepares a json object to be sent as a header */ - static jsonHeader(data) { + static jsonHeader(data: object): string { return encodeURIComponent(JSON.stringify(data)); } /** * Send a request to the server * - * @param {string} method - The method of the request - * @param {string} path - The path of the request - * @param {object|null} data - The data to be sent - * @param {object} headers - The headers to be sent - * @param {object} opts - The additional options to be sent to fetch + * @param method - The method of the request + * @param path - The path of the request + * @param data - The data to be sent + * @param headers - The headers to be sent + * @param opts - The additional options to be sent to fetch + * + * @returns The response of the request + * + * @throws - If the request fails */ - async request(method, path, data = null, headers = {}, opts = {}) { + async request( + method: string, + path: string, + data: object | null = null, + headers: object = {}, + opts: any = {}, + ): Promise { let err; if (!this.addr) throw ApiError.newOther('Server addr not defined'); @@ -50,7 +68,7 @@ export default class Api { const errObj = await resp.json(); err = ApiError.fromJson(errObj); } - } catch (e) { + } catch (e: any) { console.error('request error raw', e); err = ApiError.newOther(e.message); } @@ -62,14 +80,22 @@ export default class Api { /** * Send a request to the server with a file * - * @param {string} method - The method of the request - * @param {string} path - The path of the request - * @param {File} file - The file to be sent - * @param {function|null} progress - The progress callback - * @param {object} headers - The headers to be sent + * @param method - The method of the request + * @param path - The path of the request + * @param file - The file to be sent + * @param progress - The progress callback + * @param headers - The headers to be sent + * + * @throws {ApiError} - If the request fails */ - async requestWithFile(method, path, file, progress = null, headers = {}) { - if (!progress) progress = () => {}; + async requestWithFile( + method: string, + path: string, + file: File, + progress: ((percent: number) => void) | null = null, + headers: Record = {}, + ): Promise { + const prog = progress ?? ((percent: number) => {}); if (!this.addr) throw ApiError.newOther('Server addr not defined'); @@ -80,7 +106,7 @@ export default class Api { req.responseType = 'json'; req.addEventListener('load', () => { - progress(100); + prog(100); // should probably be a json object now if (req.status === 200) { @@ -88,25 +114,25 @@ export default class Api { } else { try { err(ApiError.fromJson(req.response)); - } catch (e) { + } catch (e: any) { err(ApiError.newOther(e.message)); } } }); req.addEventListener('error', () => { - console.error('file request error', req.error); - err(req.error); + console.error('requestWithFile network failure'); + err(ApiError.newOther('reuestWithFile network failure')); }); // to manually 'estimate the progress'; let prevProgress = 0; req.addEventListener('progress', e => { if (e.lengthComputable) { - progress(Math.min((e.loaded / e.total) * 100, 100)); + prog(Math.min((e.loaded / e.total) * 100, 100)); } else { prevProgress += prevProgress >= 75 ? 5 : 25; - progress(Math.min(prevProgress, 100)); + prog(Math.min(prevProgress, 100)); } }); @@ -126,13 +152,19 @@ export default class Api { /** * Send a request to the server with a timeout * - * @param {string} method - The method of the request - * @param {string} path - The path of the request - * @param {object|null} data - The data to be sent - * @param {object} headers - The headers to be sent - * @param {number} timeout - The timeout of the request if the value is 0 there is no timeout + * @param method - The method of the request + * @param path - The path of the request + * @param data - The data to be sent + * @param headers - The headers to be sent + * @param timeout - The timeout of the request if the value is 0 there is no timeout */ - async requestTimeout(method, path, data = null, headers = {}, timeout = 0) { + async requestTimeout( + method: string, + path: string, + data: object | null = null, + headers: object = {}, + timeout: number = 0, + ): Promise { if (!this.addr) throw ApiError.newOther('Server addr not defined'); return new Promise((res, err) => { @@ -148,20 +180,20 @@ export default class Api { } else { try { err(ApiError.fromJson(req.response)); - } catch (e) { + } catch (e: any) { err(ApiError.newOther(e.message)); } } }); req.addEventListener('timeout', () => { - console.error('requested TimedOut', req.error); - err(req.error); + console.error('requested TimedOut'); + err(ApiError.newOther('requested TimedOut')); }); req.addEventListener('error', () => { - console.error('requestTimeout error', req.error); - err(req.error); + console.error('requestTimeout network failure'); + err(ApiError.newOther('requestTimeout network failure')); }); // open request diff --git a/src/api/ApiError.d.ts b/src/api/ApiError.d.ts deleted file mode 100644 index aedc2c0..0000000 --- a/src/api/ApiError.d.ts +++ /dev/null @@ -1,47 +0,0 @@ -export default class ApiError { - /** - * The kind of error - */ - kind: string; - - /** - * The data associated with this error - * - * should provide a toString method - */ - data: any; - - /** - * Creates a new ApiError - */ - constructor(kind: string, data: any); - - /** - * The message of the error - * - * @returns {string} - */ - get msg(): string; - - /** - * Creates a new ApiError with the kind 'OTHER' - */ - static newOther(data: any): ApiError; - - /** - * Creates a new ApiError with the kind 'SESSION_NOT_FOUND' - */ - static newSessionError(): ApiError; - - /** - * Returns a string representation of the error - */ - toString(): string; -} - -/** - * Returns whether the value is an ApiError object - * - * @returns {boolean} - */ -export function isApiErrorObject(value: any): boolean; diff --git a/src/api/ApiError.js b/src/api/ApiError.ts similarity index 75% rename from src/api/ApiError.js rename to src/api/ApiError.ts index 4b5dd63..0eba75c 100644 --- a/src/api/ApiError.js +++ b/src/api/ApiError.ts @@ -2,6 +2,18 @@ * An error returned from the API */ export default class ApiError { + /** + * The kind of error + */ + kind: string; + + /** + * The data associated with this error + * + * should provide a toString method + */ + data: any; + /* fields: - kind: str, @@ -9,11 +21,8 @@ export default class ApiError { */ /** * Creates a new ApiError - * - * @param {string} kind - The kind of the error should be in SCREAMING-KEBAB-CASE - * @param {any} data - The data of the error if it provides a toString method it will be stored in the msg */ - constructor(kind, data) { + constructor(kind: string, data: any) { this.kind = kind; this.data = data; } @@ -21,9 +30,9 @@ export default class ApiError { /** * The message of the error * - * @returns {string} + * @returns */ - get msg() { + get msg(): string { if (this.data && typeof this.data.toString === 'function') return this.data.toString(); return ''; @@ -35,7 +44,7 @@ export default class ApiError { /// str /// { 'kind': data } /// { kind, data } - static fromJson(obj) { + static fromJson(obj: any) { if (typeof obj === 'string') return new ApiError(obj, null); // {kind, data} @@ -49,8 +58,8 @@ export default class ApiError { /** * Creates a new ApiError with the kind 'OTHER' */ - static newOther(msg) { - return new ApiError('OTHER', msg); + static newOther(data: any): ApiError { + return new ApiError('OTHER', data); } /** @@ -70,9 +79,7 @@ export default class ApiError { /** * Returns whether the value is an ApiError object - * - * @returns {boolean} */ -export function isApiErrorObject(val) { +export function isApiErrorObject(val: any): boolean { return typeof (val ? val.__isApiErrorObject__ : null) === 'function'; } diff --git a/src/api/Stream.js b/src/api/Stream.ts similarity index 81% rename from src/api/Stream.js rename to src/api/Stream.ts index 808e4b4..7b231b5 100644 --- a/src/api/Stream.js +++ b/src/api/Stream.ts @@ -1,6 +1,6 @@ -import ApiError from './ApiError.js'; -import Data from '../data/Data.js'; -import Listeners from '../sync/Listeners.js'; +import ApiError from './ApiError'; +import Listeners from '../sync/Listeners'; +import Api from './Api'; /** * The Stream is responsible for managing your connection with a server @@ -35,8 +35,23 @@ export default class Stream { - path */ + api: Api; + path: string; + + private ws: WebSocket | null; + private connected: boolean; + + private openListeners: Listeners<[]>; + private errorListeners: Listeners<[Event]>; + private closeListeners: Listeners<[]>; + + // private + senders: Map; + // private + receivers: Map; + // api: Api instance - constructor(api, path) { + constructor(api: Api, path: string) { this.api = api; this.path = path; @@ -58,11 +73,11 @@ export default class Stream { } /// Only returns true if the connection state is Open. - isConnect() { + isConnect(): boolean { return this.connected; } - _addr() { + private _addr(): string { if (!this.api.addr) throw new ApiError('Other', 'Server addr not defined'); const url = new URL(this.api.addr); @@ -89,13 +104,14 @@ export default class Stream { this.connected = true; this.openListeners.trigger(); }; - const onMessage = e => { + const onMessage = (e: MessageEvent) => { if (typeof e.data !== 'string') return console.log('unrecognized websocket message', e); - let protMsg; + let protMsg: ProtMessage; try { - protMsg = new ProtMessage(JSON.parse(e.data)); + protMsg = JSON.parse(e.data); + // todo do we need to validate the protMsg? } catch (e) { console.log('failed to deserialize', e); return; @@ -105,8 +121,16 @@ export default class Stream { // should propagate this._handleProtMessage(protMsg); }; - let onClose = () => {}; + const onClose = () => { + // As noted in the comment of the class we ignore this event + // if we're not connected + if (!this.connected) return; + + close(); + }; const close = () => { + if (!this.ws) return; + // reset the entire state this.connected = false; this.ws.removeEventListener('open', onOpen); @@ -117,7 +141,7 @@ export default class Stream { this.closeListeners.trigger(); }; - const onError = e => { + const onError = (e: Event) => { this.errorListeners.trigger(e); // As noted in the comment of the class we treat this event as @@ -128,13 +152,6 @@ export default class Stream { return; } }; - onClose = () => { - // As noted in the comment of the class we ignore this event - // if we're not connected - if (!this.connected) return; - - close(); - }; this.ws.addEventListener('open', onOpen); this.ws.addEventListener('message', onMessage); @@ -142,11 +159,11 @@ export default class Stream { this.ws.addEventListener('close', onClose); } - onError(fn) { + onError(fn: (e: Event) => void) { return this.errorListeners.add(fn); } - onClose(fn) { + onClose(fn: () => void) { return this.closeListeners.add(fn); } @@ -162,7 +179,7 @@ export default class Stream { /// /// Does not throw an exception async _waitReady() { - return await new Promise((resolve, _error) => { + return await new Promise((resolve: (v?: null) => void) => { if (this.connected) return resolve(); let rmFn = () => {}; @@ -177,7 +194,7 @@ export default class Stream { /// /// ## Throws /// If the action already exists. - newSender(action) { + newSender(action: string): Sender { if (this.senders.has(action)) throw new ApiError('Other', 'sender already exists'); @@ -191,7 +208,7 @@ export default class Stream { /// /// ## Throws /// If the action already exists. - newReceiver(action) { + newReceiver(action: string): Receiver { if (this.receivers.has(action)) throw new ApiError('Other', 'receiver already exists'); @@ -201,16 +218,16 @@ export default class Stream { return receiver; } - // priv - _send(protMsg) { - if (this.ws.readyState !== 1) + // hidden + _send(protMsg: ProtMessage) { + if (this.ws?.readyState !== 1) throw new ApiError('Closed', 'connection not ready'); this.ws.send(JSON.stringify(protMsg)); } // priv - _handleProtMessage(msg) { + private _handleProtMessage(msg: ProtMessage) { // define it here since switch does not have variable scopes // how anoying!!! let receiver, sender; @@ -243,18 +260,17 @@ export default class Stream { } // protocolMessage -export class ProtMessage extends Data { - constructor(data) { - super( - { - kind: 'str', - action: 'str', - data: 'any', - }, - data, - ); - } -} +export type ProtMessage = { + kind: + | 'SenderRequest' + | 'SenderClose' + | 'SenderMessage' + | 'ReceiverRequest' + | 'ReceiverMessage' + | 'ReceiverClose'; + action: string; + data: any; +}; const STATE_CLOSED_FOREVER = 0; const STATE_READY_TO_OPEN = 1; @@ -263,7 +279,17 @@ const STATE_OPENING = 3; const STATE_OPENED = 4; export class Sender { - constructor(action, stream) { + action: string; + private stream: Stream; + private state: number; + private openProm: { + resolve: () => void; + error: (e: ApiError) => void; + } | null; + private errorListeners: Listeners<[ApiError]>; + private rmCloseListener: () => void | null; + + constructor(action: string, stream: Stream) { this.action = action; this.stream = stream; @@ -283,23 +309,25 @@ export class Sender { this.rmCloseListener = stream.onClose(() => { if (this.state < STATE_OPENING) return; - this._closeWithError(); + this._closeWithError( + new ApiError('STREAM_CLOSED', 'Stream closed'), + ); }); } /// Returns true if the sender is ready to receive an open call - isReadyToOpen() { + isReadyToOpen(): boolean { return this.state === STATE_READY_TO_OPEN; } /// Returns true if the sender is ready to send some data - isReady() { + isReady(): boolean { return this.state === STATE_OPENED; } /// If this returns true you will never be able to call open again. /// You will need to call newSender on Stream. - isClosed() { + isClosed(): boolean { return this.state === STATE_CLOSED_FOREVER; } @@ -307,14 +335,14 @@ export class Sender { /// /// ## Throws /// If the sender is already open or if requesting a sender failed - async open(req = null) { + async open(req: any = null) { if (this.state === STATE_CLOSED_FOREVER) throw new ApiError('Other', 'Sender closed forever'); if (this.state >= STATE_WAITING_ON_CONNECTION) throw new ApiError('Other', 'Sender already opened'); - const prom = new Promise((resolve, error) => { + const prom = new Promise((resolve: (v?: null) => void, error) => { this.openProm = { resolve, error }; }); @@ -341,7 +369,7 @@ export class Sender { } // we receive a message from the stream - _onMsg(msg) { + _onMsg(msg: ProtMessage) { switch (msg.kind) { // the request was acknowledged case 'SenderRequest': @@ -351,7 +379,7 @@ export class Sender { } // send open finished - this.openProm.resolve(); + this.openProm?.resolve(); break; @@ -374,10 +402,10 @@ export class Sender { } } - _closeWithError(err = null) { + _closeWithError(err: ApiError) { // there was an error while trying to register if (this.state === STATE_OPENING) { - this.openProm.error(err); + this.openProm?.error(err); return; } @@ -388,7 +416,7 @@ export class Sender { /// ## Throws // if could not send the message // or if the channel is already closed - send(msg) { + send(msg: any) { if (this.state !== STATE_OPENED) throw new ApiError('SenderClosed', 'Sender already closed'); @@ -403,7 +431,7 @@ export class Sender { /// You can call open again to try to reconnect. /// /// Returns a fn to unsubscribe - onError(fn) { + onError(fn: (e: ApiError) => void): () => void { return this.errorListeners.add(fn); } @@ -437,18 +465,31 @@ export class Sender { console.log('could not send SenderClose', e); } - this.state === STATE_READY_TO_OPEN; + // todo for what was this?? + // this.state === STATE_READY_TO_OPEN; } this.stream.senders.delete(this.action); this.state = STATE_CLOSED_FOREVER; this.rmCloseListener(); - this.rmCloseListener = null; + this.rmCloseListener = () => {}; } } export class Receiver { - constructor(action, stream) { + action: string; + private stream: Stream; + private state: number; + private openProm: { + resolve: () => void; + error: (e: ApiError) => void; + } | null; + private parseFn: (d: any) => any; + private errorListeners: Listeners<[ApiError]>; + private messageListeners: Listeners<[any]>; + private rmCloseListener: () => void | null; + + constructor(action: string, stream: Stream) { this.action = action; this.stream = stream; @@ -471,23 +512,25 @@ export class Receiver { this.rmCloseListener = stream.onClose(() => { if (this.state < STATE_OPENING) return; - this._closeWithError(); + this._closeWithError( + new ApiError('STREAM_CLOSED', 'Stream closed'), + ); }); } /// Returns true if the receiver is ready to receive an open call - isReadyToOpen() { + isReadyToOpen(): boolean { return this.state === STATE_READY_TO_OPEN; } /// Returns true if the receiver is ready to receive some data - isReady() { + isReady(): boolean { return this.state === STATE_OPENED; } /// If this returns true you will never be able to call open again. /// You will need to call newReceiver on Stream. - isClosed() { + isClosed(): boolean { return this.state === STATE_CLOSED_FOREVER; } @@ -495,14 +538,14 @@ export class Receiver { /// /// ## Throws /// If the sender is already open or if requesting a sender failed - async open(req = null) { + async open(req: any = null) { if (this.state === STATE_CLOSED_FOREVER) throw new ApiError('Other', 'Receiver closed forever'); if (this.state >= STATE_WAITING_ON_CONNECTION) throw new ApiError('Other', 'Receiver already opened'); - const prom = new Promise((resolve, error) => { + const prom = new Promise((resolve: (v?: null) => void, error) => { this.openProm = { resolve, error }; }); @@ -529,7 +572,7 @@ export class Receiver { } // we receive a message from the stream - _onMsg(msg) { + _onMsg(msg: ProtMessage) { switch (msg.kind) { // the request was acknowledged case 'ReceiverRequest': @@ -539,7 +582,7 @@ export class Receiver { } // send open finished - this.openProm.resolve(); + this.openProm?.resolve(); break; @@ -569,10 +612,10 @@ export class Receiver { } } - _closeWithError(err = null) { + _closeWithError(err: ApiError) { // there was an error while trying to register if (this.state === STATE_OPENING) { - this.openProm.error(err); + this.openProm?.error(err); return; } @@ -581,13 +624,13 @@ export class Receiver { } // fn (msg) - onMessage(fn) { + onMessage(fn: (msg: any) => void): () => void { return this.messageListeners.add(fn); } // fn(data) -> data /// this is not allowed to fail - setParseFn(fn) { + setParseFn(fn: (d: any) => any) { this.parseFn = fn; } @@ -595,7 +638,7 @@ export class Receiver { /// You can call open again to try to reconnect. /// /// Returns a fn to unsubscribe - onError(fn) { + onError(fn: (e: ApiError) => void): () => void { return this.errorListeners.add(fn); } @@ -615,7 +658,7 @@ export class Receiver { this.stream.receivers.delete(this.action); this.state = STATE_CLOSED_FOREVER; this.rmCloseListener(); - this.rmCloseListener = null; + this.rmCloseListener = () => {}; } /// Closes the channel without unregistering on the Stream. @@ -640,7 +683,8 @@ export class Receiver { console.log('could not send ReceiverClose', e); } - this.state === STATE_READY_TO_OPEN; + // todo for what was this?? + // this.state === STATE_READY_TO_OPEN; } this.state = STATE_READY_TO_OPEN; @@ -655,11 +699,28 @@ const STATE_MAN_OPEN = 2; /// stop one as required. It works similarly to a normal receiver but you don't /// call open at the beginning but only when the open event get's called. export class ReceiverManager { + receiver: Receiver; + openFn: ( + receiver: Receiver, + hasError: boolean, + error: any, + ) => Promise; + + private state: number; + private listeners: number; + // open should call open on the receiver. It is allowed to throw. When an // exception occurs the manager will call open again with the exception. // // open: async (receiver, hasError: bool, error = null) - constructor(receiver, open) { + constructor( + receiver: Receiver, + open: ( + receiver: Receiver, + hasError: boolean, + error: any, + ) => Promise, + ) { this.receiver = receiver; this.openFn = open; @@ -672,7 +733,7 @@ export class ReceiverManager { }); } - onMessage(fn) { + onMessage(fn: (msg: any) => void): () => void { this.listeners++; const rmFn = this.receiver.onMessage(fn); @@ -707,7 +768,7 @@ export class ReceiverManager { // the channel is open now this.state = STATE_MAN_OPEN; break; - } catch (e) { + } catch (e: any) { // opening failed hasError = true; error = e; @@ -715,7 +776,7 @@ export class ReceiverManager { } } - _onError(_e) { + _onError(_e: any) { if (this.state !== STATE_MAN_OPEN) { // probably still opening so we don't need to close anything return; diff --git a/src/collections/SlotArray.js b/src/collections/SlotArray.ts similarity index 53% rename from src/collections/SlotArray.js rename to src/collections/SlotArray.ts index 69b192b..673766e 100644 --- a/src/collections/SlotArray.js +++ b/src/collections/SlotArray.ts @@ -1,12 +1,12 @@ /** * Implements a slot array data structure. - * @class - * @exports collections/SlotArray/SlotArray */ -export default class SlotArray { +export default class SlotArray { + inner: (T | null)[]; + free: number[]; + /** * Creates a new instance of SlotArray. - * @constructor */ constructor() { this.inner = []; @@ -18,10 +18,10 @@ export default class SlotArray { * inserted into one of them. * Otherwise, it's appended to the end of the array. * - * @param {*} val - The value to be inserted. - * @returns {number} The index at which the value was inserted. + * @param val - The value to be inserted. + * @returns The index at which the value was inserted. */ - push(val) { + push(val: T): number { let id = this.free.pop(); if (typeof id === 'number') { this.inner[id] = val; @@ -36,40 +36,43 @@ export default class SlotArray { /** * Returns all non-null items in the array. * - * @returns {*[]} An array of all non-null items in the array. + * @returns An array of all non-null items in the array. */ - all() { - return this.inner.filter(f => f !== null); + all(): T[] { + return this.inner.filter(f => f !== null) as T[]; } /** * Returns all non-null items in the array, along with their indices. * - * @returns {[number, *][]} An array of tuples, where each tuple consists + * @returns An array of tuples, where each tuple consists * of an index and the corresponding non-null item. */ - entries() { - // @ts-ignore - return this.inner.map((v, i) => [i, v]).filter(([_, v]) => v !== null); + entries(): [number, T][] { + return this.inner + .map((v, i) => [i, v]) + .filter(([_, v]) => v !== null) as [number, T][]; } /** * Retrieves the item at the given index. * - * @param {number} id - The index to retrieve the item from. - * @returns {*} The item at the given index, or null if no item is present. + * @param id - The index to retrieve the item from. + * @returns The item at the given index, or null if no item is present. */ - get(id) { - return this.inner[id]; + get(id: number): T | null { + return this.inner[id] ?? null; } /** * Removes the item at the given index, making its slot available for future * insertions. * - * @param {number} id - The index to remove the item from. + * @param id - The index to remove the item from. */ - remove(id) { + remove(id: number) { + if (this.inner.length <= id) return; + this.inner[id] = null; this.free.push(id); } diff --git a/src/sync/Barrier.d.ts b/src/sync/Barrier.d.ts deleted file mode 100644 index 25c7cdb..0000000 --- a/src/sync/Barrier.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -export declare type BarrierAction = { - ready: (val: T) => Promise; - remove: () => void; -}; - -export default class Barrier { - constructor(); - - /** - * Add yourself to the barrier - * - * Only if all participants call ready the barrier is opened. - */ - add(): BarrierAction; -} diff --git a/src/sync/Barrier.js b/src/sync/Barrier.js deleted file mode 100644 index c95ef6e..0000000 --- a/src/sync/Barrier.js +++ /dev/null @@ -1,62 +0,0 @@ -/// synchronisation point -export default class Barrier { - constructor() { - this.listeners = []; - this.lastValue = null; - this.open = false; - } - - /** - * Add yourself to the barrier - * - * Only if all participants call ready the barrier is opened. - */ - add() { - if (this.open) throw new Error('Barrier open'); - - const id = this.listeners.length; - - const obj = { - ready: false, - resolve: null, - }; - - const readyPromise = new Promise(resolve => { - obj.resolve = resolve; - }); - - this.listeners[id] = obj; - - return { - ready: async val => { - if (this.open) throw new Error('Barrier open'); - - this.lastValue = val; - obj.ready = true; - - this._maybeTrigger(); - - return await readyPromise; - }, - remove: () => { - if (this.open) throw new Error('Barrier open'); - - // remove myself from the barrier - this.listeners[id] = null; - - this._maybeTrigger(); - }, - }; - } - - _maybeTrigger() { - const ready = this.listeners.every(v => v === null || v.ready); - // if all are ready - if (!ready) return; - - // send the last value to all of them - this.listeners.forEach(v => v.resolve(this.lastValue)); - this.listeners = []; - this.open = true; - } -} diff --git a/src/sync/Barrier.ts b/src/sync/Barrier.ts new file mode 100644 index 0000000..331d654 --- /dev/null +++ b/src/sync/Barrier.ts @@ -0,0 +1,96 @@ +export type BarrierAction = { + // notify the barrier that you are ready + ready: (val: T) => Promise; + // remove yourself from the barrier + remove: () => void; +}; + +type Listener = { + ready: boolean; + resolve: (val: T) => void; +}; + +/** + * A class to help make async code execute at the same time. + * + * Making sure all participants are ready before continuing. + */ +export default class Barrier { + private listeners: Array | null>; + private lastValue: T | null; + private open: boolean; + + constructor() { + this.listeners = []; + this.lastValue = null; + this.open = false; + } + + /** + * Returns true if the barrier is already opened meaning the add + * function would panic + */ + isOpen(): boolean { + return this.open; + } + + /** + * Add yourself to the barrier + * + * Only if all participants call ready the barrier is opened. + * + * @throws if the barrier is already open + */ + add(): BarrierAction { + if (this.open) throw new Error('Barrier is already open'); + + const id = this.listeners.length; + + const obj: Listener = { + ready: false, + resolve: () => {}, + }; + + const readyPromise = new Promise(resolve => { + obj.resolve = resolve; + }); + + this.listeners[id] = obj; + + return { + ready: async val => { + if (this.open) throw new Error('Barrier is already open'); + + this.lastValue = val; + obj.ready = true; + + this._maybeTrigger(); + + return await readyPromise; + }, + remove: () => { + if (this.open) throw new Error('Barrier is already open'); + + // remove myself from the barrier + this.listeners[id] = null; + + this._maybeTrigger(); + }, + }; + } + + private _maybeTrigger() { + const ready = this.listeners.every(v => v === null || v.ready); + // if all are ready + if (!ready) return; + + // send the last value to all of them + + // the last value with always be T since either somebody called + // ready so the value is set or everbody called remove which + // means the value will never be read + this.listeners.forEach(v => v?.resolve(this.lastValue as T)); + this.listeners = []; + this.open = true; + } +} diff --git a/src/sync/Listeners.js b/src/sync/Listeners.js deleted file mode 100644 index 50fa7ad..0000000 --- a/src/sync/Listeners.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Manages event listeners or functions. - * @class - * @exports sync/listeners/Listeners - */ -export default class Listeners { - /** - * Creates a new instance of Listeners. - * @constructor Listeners - */ - constructor() { - this.inner = new Set(); - } - - /** - * Adds a new listener to the set. - * - * @param {Function} fn - The function to be added as a listener. - * @returns {Function} A function that, when called, will remove the added - * listener from the set. - */ - add(fn) { - const set = this.inner; - set.add(fn); - return () => { - set.delete(fn); - }; - } - - /** - * Calls each listener in the set with the given value. - * - * @param {...*} args - The arguments to be passed to each listener. - * @throws Will throw an error if a listener throws an error. - */ - trigger(...args) { - this.inner.forEach(fn => fn(...args)); - } - - /** - * Clears all listeners from the set, then calls each previously stored - * listener with the given value. - * - * @param {...*} args - The arguments to be passed to each listener. - * @throws Will throw an error if a listener throws an error. - */ - clearAndTrigger(...args) { - const s = this.inner; - this.clear(); - s.forEach(fn => fn(...args)); - } - - /** - * Removes all listeners from the set. - */ - clear() { - this.inner = new Set(); - } -} diff --git a/src/sync/Listeners.d.ts b/src/sync/Listeners.ts similarity index 67% rename from src/sync/Listeners.d.ts rename to src/sync/Listeners.ts index a4255e9..cebab58 100644 --- a/src/sync/Listeners.d.ts +++ b/src/sync/Listeners.ts @@ -2,10 +2,14 @@ * Manages event listeners or functions. */ export default class Listeners { + private inner: Set<(...args: T) => void>; + /** * Creates a new instance of Listeners. */ - constructor(); + constructor() { + this.inner = new Set(); + } /** * Adds a new listener to the set. @@ -13,7 +17,13 @@ export default class Listeners { * @param fn The function to be added as a listener. It should accept the same arguments as those passed to trigger. * @returns A function that, when called, will remove the added listener from the set. */ - add(fn: (...args: T) => void): () => void; + add(fn: (...args: T) => void): () => void { + const set = this.inner; + set.add(fn); + return () => { + set.delete(fn); + }; + } /** * Calls each listener in the set with the given arguments. @@ -21,7 +31,9 @@ export default class Listeners { * @param args The arguments to be passed to each listener. * @throws Will throw an error if a listener throws an error. */ - trigger(...args: T): void; + trigger(...args: T) { + this.inner.forEach(fn => fn(...args)); + } /** * Clears all listeners from the set, then calls each previously stored listener with the given arguments. @@ -29,10 +41,16 @@ export default class Listeners { * @param args The arguments to be passed to each listener. * @throws Will throw an error if a listener throws an error. */ - clearAndTrigger(...args: T): void; + clearAndTrigger(...args: T) { + const s = this.inner; + this.clear(); + s.forEach(fn => fn(...args)); + } /** * Removes all listeners from the set. */ - clear(): void; + clear() { + this.inner = new Set(); + } } diff --git a/src/sync/NonConcurrent.d.ts b/src/sync/NonConcurrent.d.ts deleted file mode 100644 index b901ef1..0000000 --- a/src/sync/NonConcurrent.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -export type NonConcurrentReady = { - ready: () => void; -}; - -export default class NonConcurrent { - /** - * Creates a new NonConcurrent - */ - constructor(); - - /** - * Waits until any other non-concurrent requests is done then waits until you call ready - * - * returns an object where you need to call ready once done - */ - start(): Promise; -} diff --git a/src/sync/NonConcurrent.js b/src/sync/NonConcurrent.js deleted file mode 100644 index 848da96..0000000 --- a/src/sync/NonConcurrent.js +++ /dev/null @@ -1,33 +0,0 @@ -/// synchronisation point -export default class NonConcurrent { - /** - * Creates a new NonConcurrent - */ - constructor() { - this.listeners = []; - this.running = false; - } - - /** - * Waits until any other non-concurrent requests is done then waits until you call ready - * - * returns an object where you need to call ready once done - */ - async start() { - if (this.running) { - await new Promise(resolve => { - this.listeners.push(resolve); - }); - } - - this.running = true; - - return { - ready: () => { - this.running = false; - const resolve = this.listeners.shift(); - if (resolve) resolve(); - }, - }; - } -} diff --git a/src/sync/NonConcurrent.ts b/src/sync/NonConcurrent.ts new file mode 100644 index 0000000..f8cd79b --- /dev/null +++ b/src/sync/NonConcurrent.ts @@ -0,0 +1,66 @@ +export type NonConcurrentReady = { + ready: () => void; +}; + +/// synchronisation point +/** + * A class to help make async code non concurrent. + * + * Example: + * ```ts + * const nonConcurrent = new NonConcurrent(); + * + * async function foo() { + * const ready = await nonConcurrent.start(); + * console.log('point 1'); + * // do something + * await timeout(100); + * console.log('point 2'); + * ready.ready(); + * } + * + * async function bar() { + * const ready = await nonConcurrent.start(); + * console.log('point 3'); + * // do something + * ready.ready(); + * } + * + * await Promise.all([foo(), bar()]); + * ``` + */ +export default class NonConcurrent { + private listeners: Array<(v?: null) => void>; + private running: boolean; + + /** + * Creates a new NonConcurrent + */ + constructor() { + this.listeners = []; + this.running = false; + } + + /** + * Waits until any other non-concurrent requests is done then waits until you call ready + * + * returns an object where you need to call ready once done + */ + async start(): Promise { + if (this.running) { + await new Promise(resolve => { + this.listeners.push(resolve); + }); + } + + this.running = true; + + return { + ready: () => { + this.running = false; + const resolve = this.listeners.shift(); + if (resolve) resolve(); + }, + }; + } +} diff --git a/src/utils/colors.d.ts b/src/utils/colors.d.ts deleted file mode 100644 index 38855d8..0000000 --- a/src/utils/colors.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * returns expects a hex value with 6 values `rgba(0,0,0,a)` - */ -export declare function toRgba(color: string, alpha?: number): string; diff --git a/src/utils/colors.js b/src/utils/colors.ts similarity index 57% rename from src/utils/colors.js rename to src/utils/colors.ts index 9475d1e..9b54d02 100644 --- a/src/utils/colors.js +++ b/src/utils/colors.ts @@ -1,14 +1,14 @@ -import { range } from './utils.js'; +import { range } from './utils'; /** * returns expects a hex value with 6 values `rgba(0,0,0,a)` */ -export function toRgba(val, alpha = 1) { - val = val.trim(); - if (!val.startsWith('#') || val.length !== 7) +export function toRgba(color: string, alpha: number = 1): string { + color = color.trim(); + if (!color.startsWith('#') || color.length !== 7) throw new Error('expected a hex value with 6 characters'); - const hex = val.substring(1); + const hex = color.substring(1); const values = range(0, 3) .map(i => parseInt(hex.substring(i * 2, i * 2 + 2), 16)) diff --git a/src/utils/utils.d.ts b/src/utils/utils.d.ts deleted file mode 100644 index 7e8cf94..0000000 --- a/src/utils/utils.d.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Delays for a specified amount of time. - * - * @param ms - The number of milliseconds to delay for. - * @returns A promise that resolves after the delay. - */ -export declare function timeout(ms: number): Promise; - -/** - * Comparison function for sorting in descending order. - * - * @param a - The first value to compare. - * @param b - The second value to compare. - * @returns -1 if a > b, 1 if b > a, 0 otherwise. - */ -export declare function sortToLower(a: any, b: any): number; - -/** - * Comparison function for sorting in ascending order. - * - * @param a - The first value to compare. - * @param b - The second value to compare. - * @returns 1 if a > b, -1 if b > a, 0 otherwise. - */ -export declare function sortToHigher(a: any, b: any): number; - -/** - * Pads a value with leading zeros until it reaches a specified length. - * - * @param val - The value to pad. - * @param length - The desired length of the padded value. - * @returns The padded value as a string. - */ -export declare function padZero(val: any, length?: number): string; - -export declare const ALPHABET: string; -export declare const ALPHABET_LENGTH: number; - -/** - * Generates a random token of a specified length. - * - * @param {number} [length=8] - The desired length of the token. - * @returns {string} A random token. - */ -export declare function randomToken(length?: number): string; - -/** - * Creates an array of whole numbers in a specified range. - * - * @param {number} start - The start of the range. - * @param {number} end - The end of the range (exclusive). - * @param {number} [step=1] - The step size between values. - * @returns {number[]} The generated array. - */ -export declare function range( - start: number, - end: number, - step?: number, -): number[]; - -/** - * Selects a random element from an array, or returns null if the array is - * empty. - * - * @param {*[]} arr - The array to select from. - * @returns {*} A random element from the array, or null. - */ -export declare function randomEl(arr: T[]): T | null; - -/** - * Checks how closely a search string matches a value, and returns a score - * representing the match quality. - * - * @param {string} search - The search string. - * @param {string} val - The value to check for a match. - * @returns {number} 0 if no match, 1+ if there was a match (lower is better). - */ -export declare function searchScore(search: string, val: string): number; - -/** - * Calculates the sum of all numbers in an array. - * - * @param {number[]} ar - The array of numbers to sum. - * @returns {number} The sum of all numbers in the array. - */ -export declare function sum(ar: number[]): number; diff --git a/src/utils/utils.js b/src/utils/utils.ts similarity index 52% rename from src/utils/utils.js rename to src/utils/utils.ts index 8b5760a..516e24d 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.ts @@ -3,21 +3,21 @@ /** * Delays for a specified amount of time. * - * @param {number} ms - The number of milliseconds to delay for. - * @returns {Promise} A promise that resolves after the delay. + * @param ms - The number of milliseconds to delay for. + * @returns A promise that resolves after the delay. */ -export function timeout(ms) { +export function timeout(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Comparison function for sorting in descending order. * - * @param {*} a - The first value to compare. - * @param {*} b - The second value to compare. - * @returns {number} -1 if a > b, 1 if b > a, 0 otherwise. + * @param a - The first value to compare. + * @param b - The second value to compare. + * @returns -1 if a > b, 1 if b > a, 0 otherwise. */ -export function sortToLower(a, b) { +export function sortToLower(a: any, b: any): number { if (a > b) return -1; else if (b > a) return 1; return 0; @@ -26,11 +26,11 @@ export function sortToLower(a, b) { /** * Comparison function for sorting in ascending order. * - * @param {*} a - The first value to compare. - * @param {*} b - The second value to compare. - * @returns {number} 1 if a > b, -1 if b > a, 0 otherwise. + * @param a - The first value to compare. + * @param b - The second value to compare. + * @returns 1 if a > b, -1 if b > a, 0 otherwise. */ -export function sortToHigher(a, b) { +export function sortToHigher(a: any, b: any): number { if (a > b) return 1; else if (b > a) return -1; return 0; @@ -39,11 +39,11 @@ export function sortToHigher(a, b) { /** * Pads a value with leading zeros until it reaches a specified length. * - * @param {*} val - The value to pad. - * @param {number} [length=2] - The desired length of the padded value. - * @returns {string} The padded value as a string. + * @param val - The value to pad. + * @param length - The desired length of the padded value. + * @returns The padded value as a string. */ -export function padZero(val, length = 2) { +export function padZero(val: any, length: number = 2): string { // make val a string val += ''; if (val.length >= length) return val; @@ -56,17 +56,17 @@ export function padZero(val, length = 2) { return prev + val; } -export const ALPHABET = +export const ALPHABET: string = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; export const ALPHABET_LENGTH = ALPHABET.length; /** * Generates a random token of a specified length. * - * @param {number} [length=8] - The desired length of the token. - * @returns {string} A random token. + * @param length - The desired length of the token. + * @returns A random token. */ -export function randomToken(length = 8) { +export function randomToken(length: number = 8): string { let s = ''; for (let i = 0; i < length; i++) { s += ALPHABET[Math.floor(Math.random() * ALPHABET_LENGTH)]; @@ -77,13 +77,13 @@ export function randomToken(length = 8) { /** * Creates an array of whole numbers in a specified range. * - * @param {number} start - The start of the range. - * @param {number} end - The end of the range (exclusive). - * @param {number} [step=1] - The step size between values. - * @returns {number[]} The generated array. + * @param start - The start of the range. + * @param end - The end of the range (exclusive). + * @param step - The step size between values, defaults to 1. + * @returns The generated array. */ // todo improve this function -export function range(start, end, step = 1) { +export function range(start: number, end: number, step: number = 1): number[] { const len = end - start / step; const ar = new Array(len); let c = 0; @@ -94,14 +94,30 @@ export function range(start, end, step = 1) { return ar; } +/** + * Shuffles an array and returns it. + * Does not modify the original array. + * + * @param arr - The array to shuffle. + * @returns The shuffled array. + */ +export function shuffle(arr: T[]): T[] { + const ar = arr.slice(); + for (let i = ar.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [ar[i], ar[j]] = [ar[j], ar[i]]; + } + return ar; +} + /** * Selects a random element from an array, or returns null if the array is * empty. * - * @param {*[]} arr - The array to select from. - * @returns {*} A random element from the array, or null. + * @param arr - The array to select from. + * @returns A random element from the array, or null. */ -export function randomEl(arr) { +export function randomEl(arr: T[]): T | null { if (arr.length === 0) return null; const i = Math.floor(Math.random() * arr.length); @@ -112,11 +128,11 @@ export function randomEl(arr) { * Checks how closely a search string matches a value, and returns a score * representing the match quality. * - * @param {string} search - The search string. - * @param {string} val - The value to check for a match. - * @returns {number} 0 if no match, 1+ if there was a match (lower is better). + * @param search - The search string. + * @param val - The value to check for a match. + * @returns 0 if no match, 1+ if there was a match (lower is better). */ -export function searchScore(search, val) { +export function searchScore(search: string, val: string): number { if (search.length === 0) return 0; search = search.normalize('NFKD').toLowerCase(); @@ -133,7 +149,7 @@ export function searchScore(search, val) { } // calculate distance to space left of -function searchSpaceLeft(idx, val) { +function searchSpaceLeft(idx: number, val: string): number { let dist = 0; while (idx > 0) { idx--; @@ -148,7 +164,7 @@ function searchSpaceLeft(idx, val) { // todo is this used??? // @ts-ignore /* eslint-disable no-unused-vars */ -function searchSpaceRight(idx, val) { +function searchSpaceRight(idx: number, val: string): number { let dist = 0; while (idx < val.length - 1) { idx++; @@ -162,9 +178,9 @@ function searchSpaceRight(idx, val) { /** * Calculates the sum of all numbers in an array. * - * @param {number[]} ar - The array of numbers to sum. - * @returns {number} The sum of all numbers in the array. + * @param ar - The array of numbers to sum. + * @returns The sum of all numbers in the array. */ -export function sum(ar) { +export function sum(ar: number[]): number { return ar.reduce((a, b) => a + b, 0); } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..dde13f7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + }, + "allowJs": true, + "checkJs": false, + "isolatedModules": true, + "strict": true, + "outDir": "./dist" + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.js", "src/**/*.test.ts"] +}