summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAsko Nõmm <asko@nmm.ee>2025-04-19 18:49:32 +0300
committerAsko Nõmm <asko@nmm.ee>2025-04-19 18:49:32 +0300
commit08ed3d406f178f372899fd76b982bf20597756c1 (patch)
treedb7a69d8dbe770b333c6bb9cba715fb947a43d0a
parent92ae308186773a8f8bfd013259aeeb31d27aaaa9 (diff)
Impl generics on dispatch/subscription arguments.
-rw-r--r--README.md15
-rw-r--r--deno.json2
-rw-r--r--src/shapex.test.ts37
-rw-r--r--src/shapex.ts82
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<AppState>({ counter: 1 });
- // deno-lint-ignore no-unused-vars
- const testEventCb: EventCallback<AppState> = (state, arg1, arg2) => ({
+ const testEventCb: EventCallback<AppState, [string, string]> = (
+ 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<AppState>({ counter: 1 });
// deno-lint-ignore no-unused-vars
- const cb: EventCallback<AppState> = (state, arg) => ({ state });
+ const cb: EventCallback<AppState, [string]> = (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<T> = {
};
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<T> = (
+export type EventCallback<T, S extends unknown[] = []> = (
state: T,
- ...args: unknown[]
+ ...args: S
) => SubscriptionResponse<T>;
-type Subscription<T> = {
+type Subscription<T, S extends unknown[]> = {
listener: string;
- callback: EventCallback<T>;
+ callback: EventCallback<T, S>;
once: boolean;
};
@@ -43,11 +43,17 @@ export type ShapeXInstance<T> = {
/**
* Subcribe to an event.
*/
- subscribe: (listener: string, callback: EventCallback<T>) => number;
+ subscribe: <S extends unknown[] = []>(
+ listener: string,
+ callback: EventCallback<T, S>
+ ) => number;
/**
* Subscribe to an event once.
*/
- subscribeOnce: (listener: string, callback: EventCallback<T>) => number;
+ subscribeOnce: <S extends unknown[] = []>(
+ listener: string,
+ callback: EventCallback<T, S>
+ ) => number;
/**
* Unsubscribe from an event.
@@ -67,7 +73,7 @@ export type ShapeXInstance<T> = {
/**
* Dispatch an event.
*/
- dispatch: (eventName: string, ...args: unknown[]) => void;
+ dispatch: <S extends unknown[]>(eventName: string, ...args: S) => void;
/**
* Get the current state.
@@ -82,10 +88,13 @@ export type ShapeXInstance<T> = {
* @returns {ShapeXInstance<T>} The ShapeX object.
*/
export default function ShapeX<T extends object>(
- initialState: T,
+ initialState: T
): ShapeXInstance<T> {
let _state = initialState;
- const _subscriptions: Map<string, Subscription<T>[]> = new Map();
+ const _subscriptions: Map<
+ string,
+ Array<Subscription<T, unknown[]>>
+ > = new Map();
let subscriptionId = 0;
/**
@@ -95,16 +104,22 @@ export default function ShapeX<T extends object>(
* @param {EventCallback<T>} callback
* @returns
*/
- const subscribe = (listener: string, callback: EventCallback<T>): number => {
+ const subscribe = <S extends unknown[]>(
+ listener: string,
+ callback: EventCallback<T, S>
+ ): 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<T, unknown[]>,
+ once: false,
+ });
+ }
return ++subscriptionId;
};
@@ -116,19 +131,22 @@ export default function ShapeX<T extends object>(
* @param {EventCallback<T>} callback
* @returns
*/
- const subscribeOnce = (
+ const subscribeOnce = <S extends unknown[]>(
listener: string,
- callback: EventCallback<T>,
+ callback: EventCallback<T, S>
): 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<T, unknown[]>,
+ once: true,
+ });
+ }
return ++subscriptionId;
};
@@ -148,11 +166,11 @@ export default function ShapeX<T extends object>(
*/
const changedState = <T extends object>(
oldState: T,
- newState: T,
+ newState: T
): string[] => {
const paths = <R extends object>(
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<T extends object>(
* @param {unknown[]} args The arguments to pass to the event listeners.
* @returns {void}
*/
- const dispatch = (eventName: string, ...args: unknown[]): void => {
+ const dispatch = <S extends unknown[]>(
+ eventName: string,
+ ...args: S
+ ): void => {
if (!_subscriptions.has(eventName)) {
return;
}
const scopedSubsriptions = _subscriptions.get(eventName) ?? [];
- const remainingSubscriptions = [] as Subscription<T>[];
+ const remainingSubscriptions = [] as Array<Subscription<T, unknown[]>>;
let callbackCount = 0;
for (const subscription of scopedSubsriptions) {
- const response = subscription.callback(_state, ...args);
+ const callback = subscription.callback as unknown as EventCallback<T, S>;
+ 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<T extends object>(
} else {
dispatch(
response.dispatch.eventName,
- ...(response.dispatch.args ?? []),
+ ...(response.dispatch.args ?? [])
);
}
}
@@ -284,7 +306,7 @@ export default function ShapeX<T extends object>(
*/
const state = (): T => {
return _state;
- }
+ };
return {
subscribe,