Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add custom transform functions for state persistence #50

Merged
merged 1 commit into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 66 additions & 8 deletions store/persist.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Err, Ok, type Operation, type Result } from "../deps.ts";
import type { AnyState, Next } from "../types.ts";
import { Err, Ok, Operation, Result } from "../deps.ts";
import { select, updateStore } from "./fx.ts";
import type { UpdaterCtx } from "./types.ts";

import type { AnyState, Next } from "../types.ts";
import type { UpdaterCtx } from "./types.ts";
export const PERSIST_LOADER_ID = "@@starfx/persist";

export interface PersistAdapter<S extends AnyState> {
Expand All @@ -17,6 +17,35 @@ export interface PersistProps<S extends AnyState> {
key: string;
reconciler: (original: S, rehydrated: Partial<S>) => S;
rehydrate: () => Operation<Result<unknown>>;
transform?: TransformFunctions<S>;
}
interface TransformFunctions<S extends AnyState> {
in(s: Partial<S>): Partial<S>;
out(s: Partial<S>): Partial<S>;
}

export function createTransform<S extends AnyState>() {
const transformers: TransformFunctions<S> = {
in: function (currentState: Partial<S>): Partial<S> {
return currentState;
},
out: function (currentState: Partial<S>): Partial<S> {
return currentState;
},
};

const inTransformer = function (state: Partial<S>): Partial<S> {
return transformers.in(state);
};

const outTransformer = function (state: Partial<S>): Partial<S> {
return transformers.out(state);
};

return {
in: inTransformer,
out: outTransformer,
};
}

export function createLocalStorageAdapter<S extends AnyState>(): PersistAdapter<
Expand Down Expand Up @@ -51,7 +80,13 @@ export function shallowReconciler<S extends AnyState>(
}

export function createPersistor<S extends AnyState>(
{ adapter, key = "starfx", reconciler = shallowReconciler, allowlist = [] }:
{
adapter,
key = "starfx",
reconciler = shallowReconciler,
allowlist = [],
transform,
}:
& Pick<PersistProps<S>, "adapter">
& Partial<PersistProps<S>>,
): PersistProps<S> {
Expand All @@ -60,9 +95,18 @@ export function createPersistor<S extends AnyState>(
if (!persistedState.ok) {
return Err(persistedState.error);
}
let stateFromStorage = persistedState.value as Partial<S>;

if (transform) {
try {
stateFromStorage = transform.out(persistedState.value);
} catch (err: any) {
console.error("Persistor outbound transformer error:", err);
}
}

const state = yield* select((s) => s);
const nextState = reconciler(state as S, persistedState.value);
const nextState = reconciler(state as S, stateFromStorage);
yield* updateStore<S>(function (state) {
Object.keys(nextState).forEach((key: keyof S) => {
state[key] = nextState[key];
Expand All @@ -78,25 +122,39 @@ export function createPersistor<S extends AnyState>(
allowlist,
reconciler,
rehydrate,
transform,
};
}

export function persistStoreMdw<S extends AnyState>(
{ allowlist, adapter, key }: PersistProps<S>,
{ allowlist, adapter, key, transform }: PersistProps<S>,
) {
return function* (_: UpdaterCtx<S>, next: Next) {
yield* next();
const state = yield* select((s: S) => s);

let transformedState: Partial<S> = state;
if (transform) {
try {
transformedState = transform.in(state);
} catch (err: any) {
console.error("Persistor inbound transformer error:", err);
}
}

// empty allowlist list means save entire state
if (allowlist.length === 0) {
yield* adapter.setItem(key, state);
yield* adapter.setItem(key, transformedState);
return;
}

const allowedState = allowlist.reduce<Partial<S>>((acc, key) => {
acc[key] = state[key];
if (key in transformedState) {
acc[key] = transformedState[key] as S[keyof S];
}
return acc;
}, {});

yield* adapter.setItem(key, allowedState);
};
}
Loading
Loading