From 08ed3d406f178f372899fd76b982bf20597756c1 Mon Sep 17 00:00:00 2001 From: Asko Nõmm Date: Sat, 19 Apr 2025 18:49:32 +0300 Subject: Impl generics on dispatch/subscription arguments. --- README.md | 15 ++++++---- deno.json | 2 +- src/shapex.test.ts | 37 ++++++++++++------------ src/shapex.ts | 82 ++++++++++++++++++++++++++++++++++-------------------- 4 files changed, 81 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index ce96c00..7e82c14 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ app.subscribe("$.counter", (state) => { }; }); -app.subscribe("request", (state) => { +app.subscribe<[]>("request", (state) => { return { state: { ...state, @@ -95,11 +95,14 @@ Subscriptions listen to events or changes to state. Each subscription must retur You can listen to events like so: ```typescript -app.subscribe("some-event-name", (state, arg1, arg2, arg3) => { - return { - state, - }; -}); +app.subscribe( + "some-event-name", + (state, arg1: string, arg2: string, arg3: string) => { + return { + state, + }; + } +); ``` Each subscription has a callback function which gets passed to it the app state and whatever data was passed diff --git a/deno.json b/deno.json index 7101a66..5a5b735 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@shapex/shapex", - "version": "1.1.0", + "version": "1.1.1", "license": "MIT", "exports": "./src/shapex.ts", "imports": { diff --git a/src/shapex.test.ts b/src/shapex.test.ts index 35207bb..de40708 100644 --- a/src/shapex.test.ts +++ b/src/shapex.test.ts @@ -56,8 +56,13 @@ describe("dispatch", () => { const $ = ShapeX({ counter: 1 }); - // deno-lint-ignore no-unused-vars - const testEventCb: EventCallback = (state, arg1, arg2) => ({ + const testEventCb: EventCallback = ( + state, + // deno-lint-ignore no-unused-vars + arg1, + // deno-lint-ignore no-unused-vars + arg2 + ) => ({ state, }); @@ -67,11 +72,7 @@ describe("dispatch", () => { $.dispatch("test-event", "arg1-value", "arg2-value"); assertSpyCall(callback, 0, { - args: [ - { counter: 1 }, - "arg1-value", - "arg2-value", - ], + args: [{ counter: 1 }, "arg1-value", "arg2-value"], }); }); @@ -93,9 +94,7 @@ describe("dispatch", () => { $.dispatch("increment"); assertSpyCall(spyCb, 0, { - args: [ - { counter: 2 }, - ], + args: [{ counter: 2 }], }); }); @@ -136,9 +135,12 @@ describe("dispatch", () => { $.subscribe("parent-event", (state) => ({ state, - dispatch: [{ eventName: "nested-event-1" }, { - eventName: "nested-event-2", - }], + dispatch: [ + { eventName: "nested-event-1" }, + { + eventName: "nested-event-2", + }, + ], })); $.dispatch("parent-event"); @@ -154,7 +156,7 @@ describe("dispatch", () => { const $ = ShapeX({ counter: 1 }); // deno-lint-ignore no-unused-vars - const cb: EventCallback = (state, arg) => ({ state }); + const cb: EventCallback = (state, arg) => ({ state }); const spyCb = spy(cb); $.subscribe("nested-event", spyCb); @@ -368,10 +370,9 @@ describe("utility methods", () => { it("returns updated state", () => { const $ = ShapeX({ counter: 1 }); - $.subscribe( - "event1", - (state) => ({ state: { counter: state.counter + 1 } }), - ); + $.subscribe("event1", (state) => ({ + state: { counter: state.counter + 1 }, + })); $.dispatch("event1"); diff --git a/src/shapex.ts b/src/shapex.ts index fce55d4..d12e159 100644 --- a/src/shapex.ts +++ b/src/shapex.ts @@ -18,21 +18,21 @@ export type SubscriptionResponse = { }; const isSubscriptionResponseList = ( - dispatch: SubscriptionResponseDispatch | SubscriptionResponseDispatch[], + dispatch: SubscriptionResponseDispatch | SubscriptionResponseDispatch[] ): dispatch is SubscriptionResponseDispatch[] => Array.isArray(dispatch); /** * A callback passed to subcriptions, called when the event * that the subscription is listening to is called. */ -export type EventCallback = ( +export type EventCallback = ( state: T, - ...args: unknown[] + ...args: S ) => SubscriptionResponse; -type Subscription = { +type Subscription = { listener: string; - callback: EventCallback; + callback: EventCallback; once: boolean; }; @@ -43,11 +43,17 @@ export type ShapeXInstance = { /** * Subcribe to an event. */ - subscribe: (listener: string, callback: EventCallback) => number; + subscribe: ( + listener: string, + callback: EventCallback + ) => number; /** * Subscribe to an event once. */ - subscribeOnce: (listener: string, callback: EventCallback) => number; + subscribeOnce: ( + listener: string, + callback: EventCallback + ) => number; /** * Unsubscribe from an event. @@ -67,7 +73,7 @@ export type ShapeXInstance = { /** * Dispatch an event. */ - dispatch: (eventName: string, ...args: unknown[]) => void; + dispatch: (eventName: string, ...args: S) => void; /** * Get the current state. @@ -82,10 +88,13 @@ export type ShapeXInstance = { * @returns {ShapeXInstance} The ShapeX object. */ export default function ShapeX( - initialState: T, + initialState: T ): ShapeXInstance { let _state = initialState; - const _subscriptions: Map[]> = new Map(); + const _subscriptions: Map< + string, + Array> + > = new Map(); let subscriptionId = 0; /** @@ -95,16 +104,22 @@ export default function ShapeX( * @param {EventCallback} callback * @returns */ - const subscribe = (listener: string, callback: EventCallback): number => { + const subscribe = ( + listener: string, + callback: EventCallback + ): number => { if (!_subscriptions.has(listener)) { _subscriptions.set(listener, []); } - _subscriptions.get(listener)?.push({ - listener, - callback, - once: false, - }); + const subscriptions = _subscriptions.get(listener); + if (subscriptions) { + subscriptions.push({ + listener, + callback: callback as unknown as EventCallback, + once: false, + }); + } return ++subscriptionId; }; @@ -116,19 +131,22 @@ export default function ShapeX( * @param {EventCallback} callback * @returns */ - const subscribeOnce = ( + const subscribeOnce = ( listener: string, - callback: EventCallback, + callback: EventCallback ): number => { if (!_subscriptions.has(listener)) { _subscriptions.set(listener, []); } - _subscriptions.get(listener)?.push({ - listener, - callback, - once: true, - }); + const subscriptions = _subscriptions.get(listener); + if (subscriptions) { + subscriptions.push({ + listener, + callback: callback as unknown as EventCallback, + once: true, + }); + } return ++subscriptionId; }; @@ -148,11 +166,11 @@ export default function ShapeX( */ const changedState = ( oldState: T, - newState: T, + newState: T ): string[] => { const paths = ( state: R, - path: string, + path: string ): { path: string; value: unknown }[] => { const _paths = [] as { path: string; value: unknown }[]; @@ -207,17 +225,21 @@ export default function ShapeX( * @param {unknown[]} args The arguments to pass to the event listeners. * @returns {void} */ - const dispatch = (eventName: string, ...args: unknown[]): void => { + const dispatch = ( + eventName: string, + ...args: S + ): void => { if (!_subscriptions.has(eventName)) { return; } const scopedSubsriptions = _subscriptions.get(eventName) ?? []; - const remainingSubscriptions = [] as Subscription[]; + const remainingSubscriptions = [] as Array>; let callbackCount = 0; for (const subscription of scopedSubsriptions) { - const response = subscription.callback(_state, ...args); + const callback = subscription.callback as unknown as EventCallback; + const response = callback(_state, ...args); // Updates state, and checks for state changes, and if any changes present, // fires a dispatch for all the state listeners (if there are any). @@ -239,7 +261,7 @@ export default function ShapeX( } else { dispatch( response.dispatch.eventName, - ...(response.dispatch.args ?? []), + ...(response.dispatch.args ?? []) ); } } @@ -284,7 +306,7 @@ export default function ShapeX( */ const state = (): T => { return _state; - } + }; return { subscribe, -- cgit v1.2.3