Skip to content

Commit

Permalink
chore: folder/file org cleanup (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
neurosnap authored Jan 19, 2024
1 parent bcdad66 commit 98a9de7
Show file tree
Hide file tree
Showing 16 changed files with 311 additions and 412 deletions.
3 changes: 1 addition & 2 deletions api-type-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,11 @@ import type {
ApiCtx,
CreateAction,
CreateActionWithPayload,
Next,
FetchJson,
MiddlewareApiCo,
Supervisor,
} from "./types.ts";
import type { Payload } from "../types.ts";
import type { Next, Payload } from "../types.ts";
import type { Operation } from "../deps.ts";
export type ApiName = string | string[];
Expand Down
2 changes: 1 addition & 1 deletion compose.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Next } from "./query/mod.ts";
import { Instruction, Operation } from "./deps.ts";
import type { Next } from "./types.ts";

export interface BaseCtx {
// deno-lint-ignore no-explicit-any
Expand Down
3 changes: 1 addition & 2 deletions query/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import type {
CreateActionWithPayload,
FetchJson,
MiddlewareApiCo,
Next,
Supervisor,
} from "./types.ts";
import type { Payload } from "../types.ts";
import type { Next, Payload } from "../types.ts";
import type { Operation } from "../deps.ts";

export type ApiName = string | string[];
Expand Down
3 changes: 2 additions & 1 deletion query/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// deno-lint-ignore-file no-explicit-any
import type { ApiCtx, ApiRequest, Next } from "./types.ts";
import type { ApiCtx, ApiRequest } from "./types.ts";
import type { Next } from "../types.ts";
import { createThunks } from "./thunk.ts";
import type { ThunksApi } from "./thunk.ts";
import type { ApiName, QueryApi } from "./api-types.ts";
Expand Down
3 changes: 2 additions & 1 deletion query/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { sleep } from "../deps.ts";
import { safe } from "../fx/mod.ts";
import type { FetchCtx, FetchJsonCtx, Next } from "./types.ts";
import type { FetchCtx, FetchJsonCtx } from "./types.ts";
import { isObject, noop } from "./util.ts";
import type { Next } from "../types.ts";

/**
* This middleware converts the name provided to {@link createApi}
Expand Down
2 changes: 1 addition & 1 deletion query/mdw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import type {
ApiRequest,
FetchJsonCtx,
MiddlewareApi,
Next,
PerfCtx,
RequiredApiRequest,
ThunkCtx,
} from "./types.ts";
import type { Next } from "../types.ts";
import { mergeRequest } from "./util.ts";
import * as fetchMdw from "./fetch.ts";
import { log } from "../log.ts";
Expand Down
3 changes: 1 addition & 2 deletions query/thunk.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { compose } from "../compose.ts";
import type { ActionWithPayload, Payload } from "../types.ts";
import type { ActionWithPayload, Next, Payload } from "../types.ts";
import { keepAlive } from "../mod.ts";
import { takeEvery } from "../action.ts";
import { isFn, isObject } from "./util.ts";
Expand All @@ -10,7 +10,6 @@ import type {
CreateActionWithPayload,
Middleware,
MiddlewareCo,
Next,
Supervisor,
ThunkCtx,
} from "./types.ts";
Expand Down
3 changes: 1 addition & 2 deletions query/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
ActionWithPayload,
LoaderItemState,
LoaderPayload,
Next,
Payload,
} from "../types.ts";

Expand Down Expand Up @@ -97,8 +98,6 @@ export type MiddlewareApiCo<Ctx extends ApiCtx = ApiCtx> =
| Middleware<Ctx>
| Middleware<Ctx>[];

export type Next = () => Operation<void>;

export interface CreateActionPayload<P = any, ApiSuccess = any> {
name: string;
key: string;
Expand Down
276 changes: 275 additions & 1 deletion react.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,277 @@
export * from "./store/react.ts";
import {
Provider as ReduxProvider,
React,
useDispatch,
useSelector,
} from "./deps.ts";
import type { AnyState, LoaderState } from "./types.ts";
import type { ThunkAction } from "./query/mod.ts";
import { type FxSchema, type FxStore, PERSIST_LOADER_ID } from "./store/mod.ts";

export { useDispatch, useSelector } from "./deps.ts";
export type { TypedUseSelectorHook } from "./deps.ts";

const {
useContext,
useEffect,
useRef,
createContext,
createElement: h,
} = React;

type ActionFn<P = any> = (p: P) => { toString: () => string };
type ActionFnSimple = () => { toString: () => string };

export interface UseApiProps<P = any> extends LoaderState {
trigger: (p: P) => void;
action: ActionFn<P>;
}
export interface UseApiSimpleProps extends LoaderState {
trigger: () => void;
action: ActionFn;
}
export interface UseApiAction<A extends ThunkAction = ThunkAction>
extends LoaderState {
trigger: () => void;
action: A;
}
export type UseApiResult<P, A extends ThunkAction = ThunkAction> =
| UseApiProps<P>
| UseApiSimpleProps
| UseApiAction<A>;

export interface UseCacheResult<D = any, A extends ThunkAction = ThunkAction>
extends UseApiAction<A> {
data: D | null;
}

const SchemaContext = createContext<FxSchema<any, any> | null>(null);

export function Provider(
{ store, schema, children }: {
store: FxStore<any>;
schema: FxSchema<any, any>;
children: React.ReactNode;
},
) {
return (
h(ReduxProvider, {
store,
children: h(
SchemaContext.Provider,
{ value: schema, children },
) as any,
})
);
}

export function useSchema<S extends AnyState>() {
return useContext(SchemaContext) as FxSchema<S>;
}

/**
* useLoader will take an action creator or action itself and return the associated
* loader for it.
*
* @returns the loader object for an action creator or action
*
* @example
* ```ts
* import { useLoader } from 'starfx/react';
*
* import { api } from './api';
*
* const fetchUsers = api.get('/users', function*() {
* // ...
* });
*
* const View = () => {
* const loader = useLoader(fetchUsers);
* // or: const loader = useLoader(fetchUsers());
* return <div>{loader.isLoader ? 'Loading ...' : 'Done!'}</div>
* }
* ```
*/
export function useLoader<S extends AnyState>(
action: ThunkAction | ActionFn,
) {
const schema = useSchema();
const id = typeof action === "function" ? `${action}` : action.payload.key;
return useSelector((s: S) => schema.loaders.selectById(s, { id }));
}

/**
* useApi will take an action creator or action itself and fetch
* the associated loader and create a `trigger` function that you can call
* later in your react component.
*
* This hook will *not* fetch the data for you because it does not know how to fetch
* data from your redux state.
*
* @example
* ```ts
* import { useApi } from 'starfx/react';
*
* import { api } from './api';
*
* const fetchUsers = api.get('/users', function*() {
* // ...
* });
*
* const View = () => {
* const { isLoading, trigger } = useApi(fetchUsers);
* useEffect(() => {
* trigger();
* }, []);
* return <div>{isLoading ? : 'Loading' : 'Done!'}</div>
* }
* ```
*/
export function useApi<P = any, A extends ThunkAction = ThunkAction<P>>(
action: A,
): UseApiAction<A>;
export function useApi<P = any, A extends ThunkAction = ThunkAction<P>>(
action: ActionFn<P>,
): UseApiProps<P>;
export function useApi<A extends ThunkAction = ThunkAction>(
action: ActionFnSimple,
): UseApiSimpleProps;
export function useApi(action: any): any {
const dispatch = useDispatch();
const loader = useLoader(action);
const trigger = (p: any) => {
if (typeof action === "function") {
dispatch(action(p));
} else {
dispatch(action);
}
};
return { ...loader, trigger, action };
}

/**
* useQuery uses {@link useApi} and automatically calls `useApi().trigger()`
*
* @example
* ```ts
* import { useQuery } from 'starfx/react';
*
* import { api } from './api';
*
* const fetchUsers = api.get('/users', function*() {
* // ...
* });
*
* const View = () => {
* const { isLoading } = useQuery(fetchUsers);
* return <div>{isLoading ? : 'Loading' : 'Done!'}</div>
* }
* ```
*/
export function useQuery<P = any, A extends ThunkAction = ThunkAction<P>>(
action: A,
): UseApiAction<A> {
const api = useApi(action);
useEffect(() => {
api.trigger();
}, [action.payload.key]);
return api;
}

/**
* useCache uses {@link useQuery} and automatically selects the cached data associated
* with the action creator or action provided.
*
* @example
* ```ts
* import { useCache } from 'starfx/react';
*
* import { api } from './api';
*
* const fetchUsers = api.get('/users', api.cache());
*
* const View = () => {
* const { isLoading, data } = useCache(fetchUsers());
* return <div>{isLoading ? : 'Loading' : data.length}</div>
* }
* ```
*/
export function useCache<P = any, ApiSuccess = any>(
action: ThunkAction<P, ApiSuccess>,
): UseCacheResult<typeof action.payload._result, ThunkAction<P, ApiSuccess>> {
const schema = useSchema();
const id = action.payload.key;
const data: any = useSelector((s: any) => schema.cache.selectById(s, { id }));
const query = useQuery(action);
return { ...query, data: data || null };
}

/**
* useLoaderSuccess will activate the callback provided when the loader transitions
* from some state to success.
*
* @example
* ```ts
* import { useLoaderSuccess, useApi } from 'starfx/react';
*
* import { api } from './api';
*
* const createUser = api.post('/users', function*(ctx, next) {
* // ...
* });
*
* const View = () => {
* const { loader, trigger } = useApi(createUser);
* const onSubmit = () => {
* trigger({ name: 'bob' });
* };
*
* useLoaderSuccess(loader, () => {
* // success!
* // Use this callback to navigate to another view
* });
*
* return <button onClick={onSubmit}>Create user!</button>
* }
* ```
*/
export function useLoaderSuccess(
cur: Pick<LoaderState, "status">,
success: () => any,
) {
const prev = useRef(cur);
useEffect(() => {
if (prev.current.status !== "success" && cur.status === "success") {
success();
}
prev.current = cur;
}, [cur.status]);
}

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

function Loading({ text }: { text: string }) {
return h("div", null, text);
}

export function PersistGate(
{ children, loading = h(Loading) }: PersistGateProps,
) {
const schema = useSchema();
const ldr = useSelector((s: any) =>
schema.loaders.selectById(s, { id: PERSIST_LOADER_ID })
);

if (ldr.status === "error") {
return h("div", null, ldr.message);
}

if (ldr.status !== "success") {
return loading;
}

return children;
}
3 changes: 1 addition & 2 deletions store/batch.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { action } from "../deps.ts";
import { Next } from "../query/types.ts";
import { UpdaterCtx } from "./types.ts";
import { AnyState } from "../types.ts";
import { AnyState, Next } from "../types.ts";

export function createBatchMdw<S extends AnyState>(
queue: (send: () => void) => void,
Expand Down
7 changes: 3 additions & 4 deletions store/persist.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Err, Ok, Operation, Result } from "../deps.ts";
import { Next } from "../query/types.ts";
import { AnyState } from "../types.ts";
import { Err, Ok, type Operation, type Result } from "../deps.ts";
import type { AnyState, Next } from "../types.ts";
import { select, updateStore } from "./fx.ts";
import { UpdaterCtx } from "./types.ts";
import type { UpdaterCtx } from "./types.ts";

export const PERSIST_LOADER_ID = "@@starfx/persist";

Expand Down
Loading

0 comments on commit 98a9de7

Please sign in to comment.