From d0a85407f9e14d331e4dde4467c9a9b2f70ebff9 Mon Sep 17 00:00:00 2001 From: Vlad <55240003+VldMrgnn@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:11:47 +0200 Subject: [PATCH] feat: support multiple stores registering the same thunk (#51) * feat(thunk): enable multiple stores to register thunks with unique identifiers * refactor(store): remove unused getStoreId function --- query/thunk.ts | 32 +++++++++++++++++++------------- store/store.ts | 15 ++++++++++----- test/thunk.test.ts | 26 ++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 18 deletions(-) diff --git a/query/thunk.ts b/query/thunk.ts index 342a2a4..2f6ec6c 100644 --- a/query/thunk.ts +++ b/query/thunk.ts @@ -2,6 +2,7 @@ import { ActionContext, API_ACTION_PREFIX, takeEvery } from "../action.ts"; import { compose } from "../compose.ts"; import { Callable, ensure, Ok, Operation, Signal } from "../deps.ts"; import { keepAlive, supervise } from "../fx/mod.ts"; +import { IdContext } from "../store/store.ts"; import { createKey } from "./create-key.ts"; import { isFn, isObject } from "./util.ts"; @@ -15,7 +16,6 @@ import type { Supervisor, ThunkCtx, } from "./types.ts"; - export interface ThunksApi { use: (fn: Middleware) => void; routes: () => Middleware; @@ -82,6 +82,8 @@ export interface ThunksApi { ): CreateActionWithPayload; } +let id = 0; + /** * Creates a middleware pipeline. * @@ -124,6 +126,7 @@ export function createThunks>( } = { supervisor: takeEvery }, ): ThunksApi { let signal: Signal | undefined = undefined; + let storeId: number | undefined = undefined; const middleware: Middleware[] = []; const visors: { [key: string]: Callable } = {}; const middlewareMap: { [key: string]: Middleware } = {}; @@ -131,10 +134,9 @@ export function createThunks>( const actionMap: { [key: string]: CreateActionWithPayload; } = {}; - const thunkId = `${Date.now().toString(36)}-${ - Math.random().toString(36).substring(2, 11) - }`; - let hasRegistered = false; + const thunkId = id++; + + const storeMap = new Map>(); function* defaultMiddleware(_: Ctx, next: Next) { yield* next(); @@ -207,10 +209,10 @@ export function createThunks>( visors[name] = curVisor; - // If signal is available, register immediately, otherwise defer - if (signal) { - signal.send({ - type: `${API_ACTION_PREFIX}REGISTER_THUNK_${thunkId}`, + // If signal is already referenced, register immediately, otherwise defer + for (const [storeId, storeSignal] of storeMap.entries()) { + storeSignal.send({ + type: `${API_ACTION_PREFIX}REGISTER_THUNK_${storeId}_${thunkId}`, payload: curVisor, }); } @@ -253,15 +255,19 @@ export function createThunks>( } function* register() { - if (hasRegistered) { + storeId = yield* IdContext; + if (storeId && storeMap.has(storeId)) { console.warn("This thunk instance is already registered."); return; } - hasRegistered = true; + signal = yield* ActionContext; + storeMap.set(storeId, signal); yield* ensure(function* () { - hasRegistered = false; + if (storeId) { + storeMap.delete(storeId); + } }); // Register any thunks created after signal is available @@ -269,7 +275,7 @@ export function createThunks>( // Spawn a watcher for further thunk matchingPairs yield* takeEvery( - `${API_ACTION_PREFIX}REGISTER_THUNK_${thunkId}`, + `${API_ACTION_PREFIX}REGISTER_THUNK_${storeId}_${thunkId}`, watcher as any, ); } diff --git a/store/store.ts b/store/store.ts index 341ee95..fcc8f8a 100644 --- a/store/store.ts +++ b/store/store.ts @@ -1,4 +1,7 @@ +import { ActionContext, API_ACTION_PREFIX, emit } from "../action.ts"; +import { BaseMiddleware, compose } from "../compose.ts"; import { + createContext, createScope, createSignal, enablePatches, @@ -6,16 +9,15 @@ import { produceWithPatches, Scope, } from "../deps.ts"; -import { BaseMiddleware, compose } from "../compose.ts"; -import type { AnyAction, AnyState, Next } from "../types.ts"; -import type { FxStore, Listener, StoreUpdater, UpdaterCtx } from "./types.ts"; import { StoreContext, StoreUpdateContext } from "./context.ts"; -import { ActionContext, emit } from "../action.ts"; -import { API_ACTION_PREFIX } from "../action.ts"; import { createRun } from "./run.ts"; +import type { AnyAction, AnyState, Next } from "../types.ts"; +import type { FxStore, Listener, StoreUpdater, UpdaterCtx } from "./types.ts"; const stubMsg = "This is merely a stub, not implemented"; +let id = 0; + // https://github.com/reduxjs/redux/blob/4a6d2fb227ba119d3498a43fab8f53fe008be64c/src/createStore.ts#L344 function observable() { return { @@ -34,6 +36,8 @@ export interface CreateStore { middleware?: BaseMiddleware>[]; } +export const IdContext = createContext("starfx:id", 0); + export function createStore({ initialState, scope: initScope, @@ -53,6 +57,7 @@ export function createStore({ const signal = createSignal(); scope.set(ActionContext, signal); + scope.set(IdContext, id++); function getScope() { return scope; diff --git a/test/thunk.test.ts b/test/thunk.test.ts index 1aa41ef..f379043 100644 --- a/test/thunk.test.ts +++ b/test/thunk.test.ts @@ -8,6 +8,7 @@ import { } from "../mod.ts"; import { createStore, updateStore } from "../store/mod.ts"; import { assertLike, asserts, describe, it } from "../test.ts"; + import type { Next, ThunkCtx } from "../mod.ts"; // deno-lint-ignore no-explicit-any @@ -625,3 +626,28 @@ it( ); }, ); + +it( + tests, + "should allow multiple stores to register a thunk", + () => { + const api1 = createThunks(); + api1.use(api1.routes()); + const storeA = createStore({ initialState: {} }); + const storeB = createStore({ initialState: {} }); + storeA.run(api1.register); + storeB.run(api1.register); + let acc = ""; + const action = api1.create("/users", function* () { + acc += "b"; + }); + storeA.dispatch(action()); + storeB.dispatch(action()); + + asserts.assertEquals( + acc, + "bb", + "Expected 'bb' after first API call, but got: " + acc, + ); + }, +);