Last active
August 9, 2024 19:45
-
-
Save 7iomka/7b57c21a780bcf3a2d5b937e0f59aa44 to your computer and use it in GitHub Desktop.
Disposable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable consistent-return */ | |
/* eslint-disable effector/mandatory-scope-binding */ | |
/* eslint-disable effector/no-watch */ | |
import type { Node } from 'effector'; | |
import { | |
clearNode, | |
attach, | |
// combine, | |
createEffect, | |
createNode, | |
createStore, | |
withRegion, | |
} from 'effector'; | |
export const KvFactory = <A, R>(modelFactory: (factoryArgs: A) => R) => { | |
type ID = string; | |
type FactoryArgs = Parameters<typeof modelFactory>[0]; | |
type RegionsKv = Record<ID, { owner: Node; ref: any | null }>; | |
const $regions = createStore<RegionsKv>({}); // <<-- | |
const $modelsKv = createStore<Record<ID, any>>({}); // <<-- | |
// create | |
const createFx = attach({ | |
source: $regions, | |
mapParams: ({ modelId, params }: { modelId: ID; params: FactoryArgs }, regions) => ({ | |
modelId, | |
params, | |
regions, | |
}), | |
effect: createEffect< | |
{ | |
modelId: string; | |
params: FactoryArgs; | |
regions: RegionsKv; | |
}, | |
{ modelId: string; regions: RegionsKv; model: any } | |
>(({ modelId, params, regions }) => { | |
const owner = createNode(); | |
regions[modelId] = { owner, ref: null }; | |
withRegion(owner, () => { | |
regions[modelId].ref = modelFactory(params); | |
}); | |
const model = regions[modelId].ref; | |
return { modelId, model, regions }; | |
}), | |
}); | |
$modelsKv.on(createFx.doneData, (currentModels, { modelId, model }) => ({ | |
...currentModels, | |
[modelId]: model, | |
})); | |
$regions.on(createFx.doneData, (_, { regions }) => ({ ...regions })); | |
// dispose | |
const disposeFx = attach({ | |
source: $regions, | |
mapParams: (modelId: ID, regions) => ({ modelId, regions }), | |
effect: createEffect< | |
{ modelId: string; regions: RegionsKv }, | |
{ modelId: string; regions: RegionsKv } | |
>(({ modelId, regions }) => { | |
if (!regions[modelId]) { | |
throw new Error(`${modelId} not found in regions`); | |
} | |
clearNode(regions[modelId].owner); | |
const { [modelId]: _, ...withoutModelID } = regions; | |
return { modelId, regions: withoutModelID }; | |
}), | |
}); | |
const disposeManyFx = createEffect<string[], any>((modelIds) => | |
modelIds.forEach((modelId) => disposeFx(modelId)), | |
); | |
$modelsKv.on(disposeFx.doneData, (currentModels, { modelId }) => { | |
const newModels = { ...currentModels }; | |
delete newModels[modelId]; | |
return newModels; | |
}); | |
$regions.on(disposeFx.doneData, (_, { regions }) => ({ ...regions })); | |
// reset | |
const resetFx = attach({ | |
source: $regions, | |
mapParams: (_: void, regions) => ({ regions }), | |
effect: createEffect<{ regions: RegionsKv }, void>(({ regions }) => { | |
if (Object.keys(regions).length === 0) return; | |
if (process.env.NEXT_PUBLIC_APP_STAGE !== 'production') { | |
console.log('disposeAll works'); | |
} | |
Object.keys(regions).forEach((modelId) => { | |
if (!regions[modelId]) { | |
console.warn(`${modelId} not found in regions`); | |
return; | |
} | |
const { owner } = regions[modelId]; | |
clearNode(owner); | |
regions[modelId].ref = null; | |
// @ts-ignore no-error here, just cleanup | |
delete regions[modelId].owner; | |
delete regions[modelId].ref; | |
delete regions[modelId]; | |
}); | |
}), | |
}); | |
$modelsKv.on(resetFx.doneData, () => ({})); | |
$regions.on(resetFx.doneData, () => ({})); | |
// if (process.env.NEXT_PUBLIC_APP_STAGE !== 'production') { | |
// combine({ | |
// regions: $regions.map(Object.keys), | |
// models: $modelsKv.map(Object.keys), | |
// }).watch(console.log); | |
// } | |
return { | |
$modelsKv, | |
$models: $modelsKv.map(Object.values), | |
$modelsIds: $modelsKv.map(Object.keys), | |
createOne: createFx, | |
createMany: createEffect< | |
{ params: FactoryArgs; modelId: ID }[], | |
{ modelId: string; regions: RegionsKv; model: any }[] | |
>(async (items) => | |
Promise.all(items.map(({ modelId, params }) => createFx({ modelId, params }))), | |
), | |
disposeOne: disposeFx, | |
disposeMany: disposeManyFx, | |
disposeAll: resetFx, | |
}; | |
}; | |
/** | |
Example | |
// Factory | |
const SummFactory = ({ a, b }: { a: number; b: number }) => { | |
const $summ = createStore(a + b); | |
return { $summ }; | |
}; | |
// KvModelFactory | |
const KvModelFactory = KvFactory(SummFactory); | |
// Creating a single disposable model | |
KvModelFactory.createOne({ modelId: '1', params: { a: 1, b: 1 } }); | |
const getDataFx = createEffect(() => | |
Promise.resolve([ | |
{ a: 3, b: 5 }, | |
{ a: 4, b: 6 }, | |
{ a: 6, b: 4 }, | |
]), | |
); | |
// Create a disposable model for each element of the array at once | |
sample({ | |
clock: getDataFx.doneData, | |
fn: (items) => items.map((item, idx) => ({ modelId: String(idx), params: { ...item } })), | |
target: KvModelFactory.createMany, | |
}); | |
getDataFx(); | |
// Cleanup separate | |
getDataFx.doneData.watch(() => { | |
setTimeout(() => { | |
KvModelFactory.disposeOne('1'); | |
}); | |
}); | |
// But basically, when new data comes in, you should probably clean everything - that would be the only simple solution | |
setTimeout(() => KvModelFactory.disposeAll(), 2000); | |
*/ | |
// TODO: somehow correct the typing to not require params for `createOne`, `createMany` if the factory has no arguments |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment