Skip to content

Instantly share code, notes, and snippets.

@dovranJorayev
Created August 24, 2024 02:09
Show Gist options
  • Save dovranJorayev/74ce5005d11764248009f3b9ccbdfd74 to your computer and use it in GitHub Desktop.
Save dovranJorayev/74ce5005d11764248009f3b9ccbdfd74 to your computer and use it in GitHub Desktop.
Effector operator to create Effector Effect from @farfetched/core remote operations
// shared/lib/farfetched/to-effect.ts
import { Mutation, Query } from '@farfetched/core';
import { attach, createStore, sample } from 'effector';
import { nanoid } from 'nanoid';
import { DeferredPromise, makeDeferredPromise } from '../../promise';
const paramsKeySymbol = Symbol.for('operationEffectKey');
const setParamsId = (params: Record<string, unknown>) => {
const id = nanoid();
Object.defineProperty(params, paramsKeySymbol, {
value: id,
enumerable: false,
configurable: false,
writable: false
});
return id;
};
const getParamsId = (params: Record<string, unknown>): string | null => {
return (
Object.getOwnPropertyDescriptor(params, paramsKeySymbol)?.value ?? null
);
};
/** @private */
export function createEffectFromOperation<
Params extends Record<string, unknown>,
D,
E
>(operation: Query<Params, D, E> | Mutation<Params, D, E>) {
const $deferrsMap = createStore({
ref: new Map<string, DeferredPromise<D>>()
});
const effect = attach({
source: $deferrsMap,
effect: async (deffersMap, params: Params) => {
if (!(params !== null && typeof params === 'object')) {
throw new Error(
'Params should be an Record<string, unknown> constrains'
);
}
let id = getParamsId(params);
let promise = makeDeferredPromise<D>();
if (!id) {
id = setParamsId(params);
const deffer = makeDeferredPromise<D>();
deffersMap.ref.set(id, deffer);
promise = deffer;
} else {
const deffer = deffersMap.ref.get(id);
if (deffer) {
promise = deffer;
} else {
const deffer = makeDeferredPromise<D>();
deffersMap.ref.set(id, deffer);
promise = deffer;
}
}
operation.start(params);
return await promise;
}
});
sample({
clock: operation.finished.success,
target: attach({
source: $deferrsMap,
effect: async (
deferrsMap,
successData: { params: Params; result: D }
) => {
const id = getParamsId(successData.params);
if (id) {
const defer = deferrsMap.ref.get(id);
if (defer) {
defer.resolve(successData.result);
deferrsMap.ref.delete(id);
}
}
}
})
});
sample({
clock: operation.finished.failure,
target: attach({
source: $deferrsMap,
effect: async (defersMap, failData: { params: Params; error: E }) => {
const id = getParamsId(failData.params);
if (id) {
const deffer = defersMap.ref.get(id);
if (deffer) {
deffer.reject(failData.error);
defersMap.ref.delete(id);
}
}
}
})
});
sample({
clock: operation.finished.skip,
target: attach({
source: $deferrsMap,
effect: async (defersMap, failData: { params: Params }) => {
const id = getParamsId(failData.params);
if (id) {
const deffer = defersMap.ref.get(id);
if (deffer) {
deffer.reject(new OperationSkipError('Operation was skipped'));
defersMap.ref.delete(id);
}
}
}
})
});
sample({
clock: operation.aborted,
target: attach({
source: $deferrsMap,
effect: async (defersMap, failData: { params: Params }) => {
const id = getParamsId(failData.params);
if (id) {
const deffer = defersMap.ref.get(id);
if (deffer) {
deffer.reject(new OperationAbortError('Operation was aborted'));
defersMap.ref.delete(id);
}
}
}
})
});
return [
effect,
{
__: {
$deferrsMap
}
}
] as const;
}
export class OperationSkipError extends Error {}
export class OperationAbortError extends Error {}
/**
* Convert farfetched Query or Mutation instance to effector effect
* @template Params - operation params with Record<string, unknown> constrains
* @template Data - operation success data
* @param operation farfethed Query or Mutation instance
* @returns effect with params of operation.start event
*
* @throws {OperationAbortError} if operation was aborted
* @throws {OperationSkipError} if operation was skipped
* @throws {Error} if params is not an object
*/
export function toEffect<Params extends Record<string, unknown>, Data, Err>(
operation: Query<Params, Data, Err> | Mutation<Params, Data, Err>
) {
const [effect] = createEffectFromOperation(operation);
return effect;
}
toEffect.OperationAbortError = OperationAbortError;
toEffect.OperationSkipError = OperationSkipError;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment