Skip to content
Closed
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
73 changes: 70 additions & 3 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion fx/safe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function* safe<T>(operator: Callable<T>): Operation<Result<T>> {
try {
const value = yield* call<T>(operator as any);
return Ok(value);
} catch (error) {
} catch (error: any) {
return Err(error);
}
}
34 changes: 13 additions & 21 deletions mdw/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { nameParser } from "./fetch.ts";

export interface ApiMdwProps<
Ctx extends ApiCtx = ApiCtx,
M extends AnyState = AnyState,
M extends AnyState = AnyState
> {
schema: {
loaders: LoaderOutput<M, AnyState>;
Expand All @@ -37,7 +37,7 @@ export interface ApiMdwProps<
* - {@link mdw.cache}
*/
export function api<Ctx extends ApiCtx = ApiCtx, S extends AnyState = AnyState>(
props: ApiMdwProps<Ctx, S>,
props: ApiMdwProps<Ctx, S>
) {
return compose<Ctx>([
err,
Expand All @@ -54,15 +54,10 @@ export function api<Ctx extends ApiCtx = ApiCtx, S extends AnyState = AnyState>(
* This middleware will automatically cache any data found inside `ctx.json`
* which is where we store JSON data from the {@link mdw.fetch} middleware.
*/
export function cache<
Ctx extends ApiCtx = ApiCtx,
>(schema: {
export function cache<Ctx extends ApiCtx = ApiCtx>(schema: {
cache: TableOutput<any, AnyState>;
}) {
return function* cache(
ctx: Ctx,
next: Next,
) {
return function* cache(ctx: Ctx, next: Next) {
ctx.cacheData = yield* select(schema.cache.selectById, { id: ctx.key });
yield* next();
if (!ctx.cache) return;
Expand All @@ -83,9 +78,10 @@ export function cache<
export function loader<M extends AnyState = AnyState>(schema: {
loaders: LoaderOutput<M, AnyState>;
}) {
return function* <
Ctx extends ThunkCtxWLoader = ThunkCtxWLoader,
>(ctx: Ctx, next: Next) {
return function* <Ctx extends ThunkCtxWLoader = ThunkCtxWLoader>(
ctx: Ctx,
next: Next
) {
yield* updateStore([
schema.loaders.start({ id: ctx.name }),
schema.loaders.start({ id: ctx.key }),
Expand All @@ -104,7 +100,7 @@ export function loader<M extends AnyState = AnyState>(schema: {
schema.loaders.success({ id: ctx.name, ...ctx.loader }),
schema.loaders.success({ id: ctx.key, ...ctx.loader }),
]);
} catch (err) {
} catch (err: any) {
if (!ctx.loader) {
ctx.loader = {};
}
Expand Down Expand Up @@ -147,10 +143,8 @@ function defaultErrorFn<Ctx extends ApiCtx = ApiCtx>(ctx: Ctx) {
*/
export function loaderApi<
Ctx extends ApiCtx = ApiCtx,
S extends AnyState = AnyState,
>(
{ schema, errorFn = defaultErrorFn }: ApiMdwProps<Ctx, S>,
) {
S extends AnyState = AnyState
>({ schema, errorFn = defaultErrorFn }: ApiMdwProps<Ctx, S>) {
return function* trackLoading(ctx: Ctx, next: Next) {
try {
yield* updateStore([
Expand All @@ -162,9 +156,7 @@ export function loaderApi<
yield* next();

if (!ctx.response) {
yield* updateStore(
schema.loaders.resetByIds([ctx.name, ctx.key]),
);
yield* updateStore(schema.loaders.resetByIds([ctx.name, ctx.key]));
return;
}

Expand Down Expand Up @@ -192,7 +184,7 @@ export function loaderApi<
schema.loaders.success({ id: ctx.name, ...ctx.loader }),
schema.loaders.success({ id: ctx.key, ...ctx.loader }),
]);
} catch (err) {
} catch (err: any) {
const message = err?.message || "unknown exception";
yield* updateStore([
schema.loaders.error({
Expand Down
2 changes: 1 addition & 1 deletion react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export function useLoaderSuccess(

interface PersistGateProps {
children: React.ReactNode;
loading?: JSX.Element;
loading?: React.JSX.Element;
}

function Loading({ text }: { text: string }) {
Expand Down
111 changes: 62 additions & 49 deletions store/store.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { ActionContext, API_ACTION_PREFIX, emit } from "../action.ts";
import { BaseMiddleware, compose } from "../compose.ts";
import {
// effection
createContext,
createScope,
createSignal,
enablePatches,
Ok,
produceWithPatches,
Scope,
// immer
enablePatches,
produceWithPatches,
} from "../deps.ts";
import { StoreContext, StoreUpdateContext } from "./context.ts";
import { createRun } from "./run.ts";
Expand All @@ -34,14 +36,31 @@ export interface CreateStore<S extends AnyState> {
scope?: Scope;
initialState: S;
middleware?: BaseMiddleware<UpdaterCtx<S>>[];
determineNextState?: any;
}

export const IdContext = createContext("starfx:id", 0);

const immerProduceNextState = <S extends AnyState>(
state: S,
upds: StoreUpdater<S>[] = []
) => {
const [nextState, patches, _] = produceWithPatches(state, (draft) => {
// TODO: check for return value inside updater
// deno-lint-ignore no-explicit-any
upds.forEach((updater) => updater(draft as any));
});
return { nextState, patches };
};
immerProduceNextState.setup = () => {
enablePatches();
};

export function createStore<S extends AnyState>({
initialState,
scope: initScope,
middleware = [],
determineNextState = immerProduceNextState,
}: CreateStore<S>): FxStore<S> {
let scope: Scope;
if (initScope) {
Expand All @@ -53,7 +72,6 @@ export function createStore<S extends AnyState>({

let state = initialState;
const listeners = new Set<Listener>();
enablePatches();

const signal = createSignal<AnyAction, void>();
scope.set(ActionContext, signal);
Expand All @@ -72,26 +90,28 @@ export function createStore<S extends AnyState>({
return () => listeners.delete(fn);
}

function* updateMdw(ctx: UpdaterCtx<S>, next: Next) {
const upds: StoreUpdater<S>[] = [];

if (Array.isArray(ctx.updater)) {
upds.push(...ctx.updater);
} else {
upds.push(ctx.updater);
}
function dispatch(action: AnyAction | AnyAction[]) {
emit({ signal, action });
}

const [nextState, patches, _] = produceWithPatches(getState(), (draft) => {
// TODO: check for return value inside updater
// deno-lint-ignore no-explicit-any
upds.forEach((updater) => updater(draft as any));
});
ctx.patches = patches;
function getInitialState() {
return initialState;
}

// set the state!
state = nextState;
function* reset(ignoreList: (keyof S)[] = []) {
return yield* update((s) => {
const keep = ignoreList.reduce<S>(
(acc, key) => {
acc[key] = s[key];
return acc;
},
{ ...initialState }
);

yield* next();
Object.keys(s).forEach((key: keyof S) => {
s[key] = keep[key];
});
});
}

function* logMdw(ctx: UpdaterCtx<S>, next: Next) {
Expand All @@ -113,6 +133,27 @@ export function createStore<S extends AnyState>({
yield* next();
}

if (determineNextState?.setup) determineNextState.setup();

function* updateMdw(ctx: UpdaterCtx<S>, next: Next) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a mdw that users always provide instead of forcing it to be included in the store. In that case this would reduce the default store functionality, just a thought.

const upds: StoreUpdater<S>[] = [];

if (Array.isArray(ctx.updater)) {
upds.push(...ctx.updater);
} else {
upds.push(ctx.updater);
}

const produced = determineNextState(getState(), upds);

ctx.patches = produced.patches;

// set the state!
state = produced.nextState;

yield* next();
}

function createUpdater() {
const fn = compose<UpdaterCtx<S>>([
updateMdw,
Expand Down Expand Up @@ -145,27 +186,6 @@ export function createStore<S extends AnyState>({
return ctx;
}

function dispatch(action: AnyAction | AnyAction[]) {
emit({ signal, action });
}

function getInitialState() {
return initialState;
}

function* reset(ignoreList: (keyof S)[] = []) {
return yield* update((s) => {
const keep = ignoreList.reduce<S>((acc, key) => {
acc[key] = s[key];
return acc;
}, { ...initialState });

Object.keys(s).forEach((key: keyof S) => {
s[key] = keep[key];
});
});
}

const store = {
getScope,
getState,
Expand All @@ -176,20 +196,13 @@ export function createStore<S extends AnyState>({
// instead of actions relating to store mutation, they
// refer to pieces of business logic -- that can also mutate state
dispatch,
// stubs so `react-redux` is happy
// deno-lint-ignore no-explicit-any
replaceReducer<S = any>(
_nextReducer: (_s: S, _a: AnyAction) => void,
): void {
throw new Error(stubMsg);
},
getInitialState,
[Symbol.observable]: observable,
};

// deno-lint-ignore no-explicit-any
store.getScope().set(StoreContext, store as any);
return store;
return store as any;
}

/**
Expand Down
Loading
Loading