repos / starfx

supercharged async flow control library.
git clone https://github.com/neurosnap/starfx.git

commit
380ad6c
parent
bfbb83c
author
Eric Bower
date
2024-03-04 19:46:44 +0000 UTC
refactor: merge `starfx/store` into `starfx` (#43)

I'm looking for ways to simplify the API in order to make this lib more
ergonomic.

DEPRECATED: `configureStore` is now `createStore`
BREAKING CHANGE: removed `starfx/store`
31 files changed,  +299, -330
M mod.ts
M npm.ts
M deps.ts
+1, -0
1@@ -34,6 +34,7 @@ export {
2   spawn,
3   suspend,
4   useAbortSignal,
5+  useScope,
6 } from "https://deno.land/x/effection@3.0.0-beta.3/mod.ts";
7 
8 import React from "https://esm.sh/react@18.2.0?pin=v122";
M docs/posts/api.md
+0, -23
 1@@ -4,26 +4,3 @@ description: Our API for public consumption
 2 ---
 3 
 4 WIP
 5-
 6-# query
 7-
 8-# fx
 9-
10-## `parallel`
11-
12-This is `Promise.all` on steroids and doesn't cancel all other tasks when one
13-fails.
14-
15-It can wait for all tasks to complete, receive them in the order in which they
16-were created, or receive them in the order in which they were completed. They
17-are safe, meaning one task won't crash another one -- or the parent task.
18-
19-## `race`
20-
21-## `safe`
22-
23-# store
24-
25-# schema
26-
27-# react
M docs/posts/endpoints.md
+7, -4
 1@@ -8,11 +8,12 @@ a supervisor, it has a middleware stack, and it hijacks the unique id for our
 2 thunks and turns it into a router.
 3 
 4 ```ts
 5-import { createApi, mdw } from "starfx";
 6+import { createApi, createStore, mdw } from "starfx";
 7+import { initialState, schema } from "./schema";
 8 
 9 const api = createApi();
10 // composition of handy middleware for createApi to function
11-api.use(mdw.api());
12+api.use(mdw.api({ schema }));
13 api.use(api.routes());
14 // calls `window.fetch` with `ctx.request` and sets to `ctx.response`
15 api.use(mdw.fetch({ baseUrl: "https://jsonplaceholder.typicode.com" }));
16@@ -31,6 +32,9 @@ export const updateUser = api.post<{ id: string; name: string }>(
17   },
18 );
19 
20+const store = createStore(initialState);
21+store.run(api.bootup);
22+
23 store.dispatch(fetchUsers());
24 // now accessible with useCache(fetchUsers)
25 
26@@ -338,8 +342,7 @@ const [schema, initialState] = createSchema({
27 type WebState = typeof initialState;
28 
29 const api = createApi();
30-api.use(mdw.api());
31-api.use(storeMdw.store(schema));
32+api.use(mdw.api({ schema }));
33 api.use(api.routes());
34 
35 // do some request setup before making fetch call
M docs/posts/getting-started.md
+50, -54
  1@@ -5,29 +5,29 @@ description: Use starfx with deno, node, or the browser
  2 
  3 # motivation
  4 
  5-We've been sold a lie.  You think you need a react framework or server-side
  6-rendering because that's where money is being made.  If you are building a
  7-highly dynamic and interactive web application then you probably don't need
  8-SSR.  These frameworks sell you that they are an easier way to build web apps,
  9-but that's not true.  Just think of it this way: if you can build your web
 10-app using only static assets, isn't that simpler than having static assets and a
 11-react framework server?
 12+We've been sold a lie. You think you need a react framework or server-side
 13+rendering because that's where money is being made. If you are building a highly
 14+dynamic and interactive web application then you probably don't need SSR. These
 15+frameworks sell you that they are an easier way to build web apps, but that's
 16+not true. Just think of it this way: if you can build your web app using only
 17+static assets, isn't that simpler than having static assets and a react
 18+framework server?
 19 
 20 React hook-based fetching and caching libraries dramatically simplify data
 21-synchronization but are so tightly coupled to a component's life cycle that
 22-it creates waterfall fetches and loading spinners everywhere.  You also have
 23-the downside of not being able to normalize your cache which means you have to
 24-spend time thinking about how and when to invalidate your various caches that
 25-hold the same API entities.
 26-
 27-Further, all of these data caching libraries have sold you another lie.  In
 28-every library you are going to see a line similar to this: "Data normalization
 29-is hard and it isn't worth it."  Wrong.  Their libraries are not built with
 30-data normalization in mind so they claim it's an anti-feature.  Why do we want
 31-to normalize data in the backend but not the frontend?  Data normalization is
 32+synchronization but are so tightly coupled to a component's life cycle that it
 33+creates waterfall fetches and loading spinners everywhere. You also have the
 34+downside of not being able to normalize your cache which means you have to spend
 35+time thinking about how and when to invalidate your various caches that hold the
 36+identical API entities.
 37+
 38+Further, all of these data caching libraries have sold you another lie. In every
 39+library you are going to see a line similar to this: "Data normalization is hard
 40+and it isn't worth it." Wrong. Their libraries are not built with data
 41+normalization in mind so they claim it's an anti-feature. Why do we want to
 42+normalize data in the backend but not the frontend? Data normalization is
 43 critically important because it makes CRUD operations automatically update your
 44-web app without having to invalidate your cache so the app will refetch the
 45-data you already have.
 46+web app without having to invalidate your cache so the app will refetch the data
 47+you already have.
 48 
 49 So what if you are building a highly interactive web app that doesn't need SEO
 50 and you also need more control over data synchronization and caching?
 51@@ -42,12 +42,12 @@ Are you frustrated by the following issues in your react app?
 52 - State management boilerplate
 53 - Lack of async flow control tooling
 54 
 55-We built `starfx` because we looked at the web app landscape and felt like
 56-there was something missing.
 57+We built `starfx` because we looked at the web app landscape and felt like there
 58+was something missing.
 59 
 60 Do you want a library that:
 61 
 62-- Makes SPAs its only use case
 63+- Design for single-page applications (SPAs)
 64 - Has a powerful middleware system similar to express to handle requests and
 65   responses
 66 - Makes data normalization easy and straightforward
 67@@ -57,8 +57,8 @@ Do you want a library that:
 68 
 69 # when to use this library?
 70 
 71-The primary target for this library are single-page apps (SPAs). This is for an
 72-app that might be hosted inside an object store (like s3) or with a simple web
 73+The primary target for this library are single-page apps. This is for an app
 74+that might be hosted inside an object store (like s3) or with a simple web
 75 server that serves files and that's it.
 76 
 77 Is your app highly interactive, requiring it to persist data across pages? This
 78@@ -74,55 +74,51 @@ minutes**, mimicking the basic features of `react-query`.
 79 [Codesanbox](https://codesandbox.io/p/sandbox/starfx-simplest-dgqc9v?file=%2Fsrc%2Findex.tsx)
 80 
 81 ```tsx
 82-import ReactDOM from "react-dom/client";
 83-import { createApi, mdw, timer } from "starfx";
 84-import { configureStore, createSchema, slice, storeMdw } from "starfx/store";
 85+import { createApi, createSchema, createStore, mdw, timer } from "starfx";
 86 import { Provider, useCache } from "starfx/react";
 87 
 88-const [schema, initialState] = createSchema({
 89-  loaders: slice.loaders(),
 90-  cache: slice.table(),
 91-});
 92+const [schema, initialState] = createSchema();
 93+const store = createStore(initialState);
 94 
 95 const api = createApi();
 96-api.use(mdw.api());
 97-api.use(storeMdw.store(schema));
 98+// mdw = middleware
 99+api.use(mdw.api({ schema }));
100 api.use(api.routes());
101-api.use(mdw.fetch({ baseUrl: "https://jsonplaceholder.typicode.com" }));
102+api.use(mdw.fetch({ baseUrl: "https://api.github.com" }));
103 
104-const fetchUsers = api.get(
105-  "/users",
106+const fetchRepo = api.get(
107+  "/repos/neurosnap/starfx",
108   { supervisor: timer() },
109   api.cache(),
110 );
111 
112-const store = configureStore(initialState);
113-type WebState = typeof initialState;
114-
115 store.run(api.bootup);
116 
117 function App() {
118-  const { isLoading, data: users } = useCache(fetchUsers());
119+  return (
120+    <Provider schema={schema} store={store}>
121+      <App />
122+    </Provider>
123+  );
124+}
125 
126-  if (isLoading) {
127-    return <div>Loading ...</div>;
128-  }
129+function Example() {
130+  const { isLoading, isError, message, data = [] } = useCache(fetchRepo());
131+
132+  if (isLoading) return "Loading ...";
133+
134+  if (isError) return `An error has occurred: ${message}`;
135 
136   return (
137     <div>
138-      {users?.map(
139-        (user) => <div key={user.id}>{user.name}</div>,
140-      )}
141+      <h1>{data.name}</h1>
142+      <p>{data.description}</p>
143+      <strong>👀 {data.subscribers_count}</strong>{" "}
144+      <strong>✨ {data.stargazers_count}</strong>{" "}
145+      <strong>🍴 {data.forks_count}</strong>
146     </div>
147   );
148 }
149-
150-const root = document.getElementById("root") as HTMLElement;
151-ReactDOM.createRoot(root).render(
152-  <Provider schema={schema} store={store}>
153-    <App />
154-  </Provider>,
155-);
156 ```
157 
158 # install
M docs/posts/mdw.md
+5, -6
 1@@ -18,12 +18,11 @@ For example, the recommended mdw stack for `createApi()` looks like this:
 2 
 3 ```ts
 4 import { createApi, mdw } from "starfx";
 5-import { storeMdw } from "starfx/store";
 6+import { schema } from "./schema";
 7 
 8 // this api:
 9 const api = createApi();
10-api.use(mdw.api());
11-api.use(storeMdw.store());
12+api.use(mdw.api({ schema }));
13 api.use(api.routes());
14 api.use(mdw.fetch({ baseUrl: "https://api.com" }));
15 
16@@ -33,9 +32,9 @@ api.use(mdw.fetch({ baseUrl: "https://api.com" }));
17   mdw.queryCtx,
18   mdw.customKey,
19   mdw.nameParser,
20-  storeMdw.actions,
21-  storeMdw.loaderApi(),
22-  storeMdw.cache(props.cache),
23+  mdw.actions,
24+  mdw.loaderApi({ schema }),
25+  mdw.cache({ schema }),
26   api.routes(),
27   mdw.composeUrl("https://api.com"),
28   mdw.payload,
M docs/posts/store.md
+3, -3
 1@@ -39,7 +39,7 @@ This gets us closer to treating our store like a traditional database while
 2 still being flexible for our needs on the FE.
 3 
 4 ```ts
 5-import { configureStore, createSchema, select, slice } from "starfx/store";
 6+import { createSchema, createStore, select, slice } from "starfx";
 7 
 8 interface User {
 9   id: string;
10@@ -96,7 +96,7 @@ There are **three** ways to update state, each with varying degrees of type
11 safety:
12 
13 ```ts
14-import { updateStore } from "starfx/store";
15+import { updateStore } from "starfx";
16 
17 function*() {
18   // good types
19@@ -127,7 +127,7 @@ However, it is very easy to create a controller to do simple tasks like updating
20 state:
21 
22 ```ts
23-import type { StoreUpdater } from "starfx/store";
24+import type { StoreUpdater } from "starfx";
25 
26 const updater = thunks.create<StoreUpdater[]>("update", function* (ctx, next) {
27   yield* updateStore(ctx.payload);
R query/fetch.ts => mdw/fetch.ts
+2, -2
 1@@ -1,7 +1,7 @@
 2 import { sleep } from "../deps.ts";
 3 import { safe } from "../fx/mod.ts";
 4-import type { FetchCtx, FetchJsonCtx } from "./types.ts";
 5-import { isObject, noop } from "./util.ts";
 6+import type { FetchCtx, FetchJsonCtx } from "../query/mod.ts";
 7+import { isObject, noop } from "../query/util.ts";
 8 import type { Next } from "../types.ts";
 9 
10 /**
A mdw/mod.ts
+4, -0
1@@ -0,0 +1,4 @@
2+import * as queryMdw from "./query.ts";
3+import * as storeMdw from "./store.ts";
4+
5+export const mdw = { ...queryMdw, ...storeMdw };
R query/mdw.ts => mdw/query.ts
+6, -20
 1@@ -8,9 +8,9 @@ import type {
 2   PerfCtx,
 3   RequiredApiRequest,
 4   ThunkCtx,
 5-} from "./types.ts";
 6+} from "../query/types.ts";
 7 import type { AnyAction, Next } from "../types.ts";
 8-import { mergeRequest } from "./util.ts";
 9+import { mergeRequest } from "../query/util.ts";
10 import * as fetchMdw from "./fetch.ts";
11 import { call, Callable } from "../deps.ts";
12 import { put } from "../action.ts";
13@@ -33,11 +33,13 @@ export function* err<Ctx extends ThunkCtx = ThunkCtx>(
14 ) {
15   ctx.result = yield* safe(next);
16   if (!ctx.result.ok) {
17+    const message =
18+      `Error: ${ctx.result.error.message}.  Check the endpoint [${ctx.name}]`;
19+    console.error(message, ctx);
20     yield* put({
21       type: "error:query",
22       payload: {
23-        message:
24-          `Error: ${ctx.result.error.message}.  Check the endpoint [${ctx.name}]`,
25+        message,
26         ctx,
27       },
28     });
29@@ -108,22 +110,6 @@ export function* actions(ctx: { actions: AnyAction[] }, next: Next) {
30   yield* put(ctx.actions);
31 }
32 
33-/**
34- * This middleware is a composition of many middleware used to faciliate
35- * the {@link createApi}.
36- *
37- * It is not required, however, it is battle-tested and highly recommended.
38- */
39-export function api<Ctx extends ApiCtx = ApiCtx>() {
40-  return compose<Ctx>([
41-    err,
42-    actions,
43-    queryCtx,
44-    customKey,
45-    fetchMdw.nameParser,
46-  ]);
47-}
48-
49 /**
50  * This middleware will add `performance.now()` before and after your
51  * middleware pipeline.
R store/mdw.ts => mdw/store.ts
+71, -47
  1@@ -1,21 +1,43 @@
  2 import type { ApiCtx, ThunkCtx } from "../query/mod.ts";
  3 import { compose } from "../compose.ts";
  4 import type { AnyState, Next } from "../types.ts";
  5-import { select, updateStore } from "./fx.ts";
  6-import { LoaderOutput } from "./slice/loaders.ts";
  7-import { TableOutput } from "./slice/table.ts";
  8+import {
  9+  LoaderOutput,
 10+  select,
 11+  TableOutput,
 12+  updateStore,
 13+} from "../store/mod.ts";
 14+import { actions, customKey, err, queryCtx } from "./query.ts";
 15+import { nameParser } from "./fetch.ts";
 16 
 17-export function store<
 18+interface ApiMdwProps<
 19   Ctx extends ApiCtx = ApiCtx,
 20   M extends AnyState = AnyState,
 21->(props: {
 22-  loaders: LoaderOutput<M, AnyState>;
 23-  cache: TableOutput<any, AnyState>;
 24+> {
 25+  schema: {
 26+    loaders: LoaderOutput<M, AnyState>;
 27+    cache: TableOutput<any, AnyState>;
 28+  };
 29   errorFn?: (ctx: Ctx) => string;
 30-}) {
 31+}
 32+
 33+/**
 34+ * This middleware is a composition of many middleware used to faciliate
 35+ * the {@link createApi}.
 36+ *
 37+ * It is not required, however, it is battle-tested and highly recommended.
 38+ */
 39+export function api<Ctx extends ApiCtx = ApiCtx, S extends AnyState = AnyState>(
 40+  props: ApiMdwProps<Ctx, S>,
 41+) {
 42   return compose<Ctx>([
 43-    loaderApi(props.loaders, props.errorFn),
 44-    cache(props.cache),
 45+    err,
 46+    actions,
 47+    queryCtx,
 48+    customKey,
 49+    nameParser,
 50+    loaderApi(props),
 51+    cache(props.schema),
 52   ]);
 53 }
 54 
 55@@ -23,14 +45,16 @@ export function store<
 56  * This middleware will automatically cache any data found inside `ctx.json`
 57  * which is where we store JSON data from the {@link mdw.fetch} middleware.
 58  */
 59-export function cache<Ctx extends ApiCtx = ApiCtx>(
 60-  dataSchema: TableOutput<any, AnyState>,
 61-) {
 62-  return function* (
 63+export function cache<
 64+  Ctx extends ApiCtx = ApiCtx,
 65+>(schema: {
 66+  cache: TableOutput<any, AnyState>;
 67+}) {
 68+  return function* cache(
 69     ctx: Ctx,
 70     next: Next,
 71   ) {
 72-    ctx.cacheData = yield* select(dataSchema.selectById, { id: ctx.key });
 73+    ctx.cacheData = yield* select(schema.cache.selectById, { id: ctx.key });
 74     yield* next();
 75     if (!ctx.cache) return;
 76     let data;
 77@@ -39,7 +63,7 @@ export function cache<Ctx extends ApiCtx = ApiCtx>(
 78     } else {
 79       data = ctx.json.error;
 80     }
 81-    yield* updateStore(dataSchema.add({ [ctx.key]: data }));
 82+    yield* updateStore(schema.cache.add({ [ctx.key]: data }));
 83     ctx.cacheData = data;
 84   };
 85 }
 86@@ -47,66 +71,66 @@ export function cache<Ctx extends ApiCtx = ApiCtx>(
 87 /**
 88  * This middleware will track the status of a middleware fn
 89  */
 90-export function loader<
 91-  Ctx extends ThunkCtx = ThunkCtx,
 92-  M extends AnyState = AnyState,
 93->(
 94-  loaderSchema: LoaderOutput<M, AnyState>,
 95-) {
 96-  return function* (ctx: Ctx, next: Next) {
 97+export function loader<M extends AnyState = AnyState>(schema: {
 98+  loaders: LoaderOutput<M, AnyState>;
 99+}) {
100+  return function* <
101+    Ctx extends ThunkCtx = ThunkCtx,
102+  >(ctx: Ctx, next: Next) {
103     yield* updateStore([
104-      loaderSchema.start({ id: ctx.name }),
105-      loaderSchema.start({ id: ctx.key }),
106+      schema.loaders.start({ id: ctx.name }),
107+      schema.loaders.start({ id: ctx.key }),
108     ]);
109 
110     try {
111       yield* next();
112       yield* updateStore([
113-        loaderSchema.success({ id: ctx.name }),
114-        loaderSchema.success({ id: ctx.key }),
115+        schema.loaders.success({ id: ctx.name }),
116+        schema.loaders.success({ id: ctx.key }),
117       ]);
118     } catch (err) {
119       yield* updateStore([
120-        loaderSchema.error({
121+        schema.loaders.error({
122           id: ctx.name,
123           message: err.message,
124         }),
125-        loaderSchema.error({
126+        schema.loaders.error({
127           id: ctx.key,
128           message: err.message,
129         }),
130       ]);
131     } finally {
132       const loaders = yield* select((s: any) =>
133-        loaderSchema.selectByIds(s, { ids: [ctx.name, ctx.key] })
134+        schema.loaders.selectByIds(s, { ids: [ctx.name, ctx.key] })
135       );
136       const ids = loaders
137         .filter((loader) => loader.status === "loading")
138         .map((loader) => loader.id);
139-      yield* updateStore(loaderSchema.resetByIds(ids));
140+      yield* updateStore(schema.loaders.resetByIds(ids));
141     }
142   };
143 }
144 
145+function defaultErrorFn<Ctx extends ApiCtx = ApiCtx>(ctx: Ctx) {
146+  const jso = ctx.json;
147+  if (jso.ok) return "";
148+  return jso.error?.message || "";
149+}
150+
151 /**
152  * This middleware will track the status of a fetch request.
153  */
154 export function loaderApi<
155   Ctx extends ApiCtx = ApiCtx,
156-  M extends AnyState = AnyState,
157+  S extends AnyState = AnyState,
158 >(
159-  loaderSchema: LoaderOutput<M, AnyState>,
160-  errorFn: (ctx: Ctx) => string = (ctx) => {
161-    const jso = ctx.json;
162-    if (jso.ok) return "";
163-    return jso.error?.message || "";
164-  },
165+  { schema, errorFn = defaultErrorFn }: ApiMdwProps<Ctx, S>,
166 ) {
167   return function* trackLoading(ctx: Ctx, next: Next) {
168     try {
169       yield* updateStore([
170-        loaderSchema.start({ id: ctx.name }),
171-        loaderSchema.start({ id: ctx.key }),
172+        schema.loaders.start({ id: ctx.name }),
173+        schema.loaders.start({ id: ctx.key }),
174       ]);
175       if (!ctx.loader) ctx.loader = {} as any;
176 
177@@ -114,7 +138,7 @@ export function loaderApi<
178 
179       if (!ctx.response) {
180         yield* updateStore(
181-          loaderSchema.resetByIds([ctx.name, ctx.key]),
182+          schema.loaders.resetByIds([ctx.name, ctx.key]),
183         );
184         return;
185       }
186@@ -125,12 +149,12 @@ export function loaderApi<
187 
188       if (!ctx.response.ok) {
189         yield* updateStore([
190-          loaderSchema.error({
191+          schema.loaders.error({
192             id: ctx.name,
193             message: errorFn(ctx),
194             ...ctx.loader,
195           }),
196-          loaderSchema.error({
197+          schema.loaders.error({
198             id: ctx.key,
199             message: errorFn(ctx),
200             ...ctx.loader,
201@@ -140,17 +164,17 @@ export function loaderApi<
202       }
203 
204       yield* updateStore([
205-        loaderSchema.success({ id: ctx.name, ...ctx.loader }),
206-        loaderSchema.success({ id: ctx.key, ...ctx.loader }),
207+        schema.loaders.success({ id: ctx.name, ...ctx.loader }),
208+        schema.loaders.success({ id: ctx.key, ...ctx.loader }),
209       ]);
210     } finally {
211       const loaders = yield* select((s: any) =>
212-        loaderSchema.selectByIds(s, { ids: [ctx.name, ctx.key] })
213+        schema.loaders.selectByIds(s, { ids: [ctx.name, ctx.key] })
214       );
215       const ids = loaders
216         .filter((loader) => loader.status === "loading")
217         .map((loader) => loader.id);
218-      yield* updateStore(loaderSchema.resetByIds(ids));
219+      yield* updateStore(schema.loaders.resetByIds(ids));
220     }
221   };
222 }
M mod.ts
+3, -0
1@@ -1,5 +1,8 @@
2 export * from "./fx/mod.ts";
3 export * from "./query/mod.ts";
4+export * from "./store/mod.ts";
5+export * from "./mdw/mod.ts";
6+
7 export * from "./types.ts";
8 export * from "./compose.ts";
9 export * from "./action.ts";
M npm.ts
+0, -4
 1@@ -21,10 +21,6 @@ async function init() {
 2         name: "./react",
 3         path: "react.ts",
 4       },
 5-      {
 6-        name: "./store",
 7-        path: "./store/mod.ts",
 8-      },
 9     ],
10     mappings: {
11       "https://deno.land/x/effection@3.0.0-beta.3/mod.ts": {
M query/mod.ts
+1, -26
 1@@ -1,37 +1,12 @@
 2 import { createThunks, type ThunksApi } from "./thunk.ts";
 3-import * as mdw from "./mdw.ts";
 4 
 5 export * from "./api.ts";
 6 export * from "./types.ts";
 7 export * from "./create-key.ts";
 8 
 9-export { createThunks, mdw, ThunksApi };
10+export { createThunks, ThunksApi };
11 
12 /**
13  * @deprecated Use {@link createThunks} instead;
14  */
15 export const createPipe = createThunks;
16-/**
17- * @deprecated Use {@link mdw.err} instead;
18- */
19-export const errorHandler = mdw.err;
20-/**
21- * @deprecated Use {@link mdw.query} instead;
22- */
23-export const queryCtx = mdw.queryCtx;
24-/**
25- * @deprecated Use {@link fetchMdw.composeUrl} instead;
26- */
27-export const urlParser = mdw.composeUrl;
28-/**
29- * @deprecated Use {@link mdw.customKey} instead;
30- */
31-export const customKey = mdw.customKey;
32-/**
33- * @deprecated Use {@link mdw.api} instead;
34- */
35-export const requestMonitor = mdw.api;
36-/**
37- * @deprecated Use {@link mdw.fetch} instead;
38- */
39-export const fetcher = mdw.fetch;
M store/context.ts
+0, -1
1@@ -6,5 +6,4 @@ export const StoreUpdateContext = createContext<Channel<void, void>>(
2   "starfx:store:update",
3   createChannel<void, void>(),
4 );
5-
6 export const StoreContext = createContext<FxStore<AnyState>>("starfx:store");
M store/mod.ts
+0, -2
1@@ -7,5 +7,3 @@ export * from "./slice/mod.ts";
2 export * from "./schema.ts";
3 export * from "./batch.ts";
4 export * from "./persist.ts";
5-import * as storeMdw from "./mdw.ts";
6-export { storeMdw };
A store/run.ts
+21, -0
 1@@ -0,0 +1,21 @@
 2+import { Callable, Operation, Result, Scope, Task } from "../deps.ts";
 3+import { parallel, safe } from "../fx/mod.ts";
 4+
 5+export function createRun(scope: Scope) {
 6+  function run<T>(op: Callable<T>[]): Task<Result<T>[]>;
 7+  function run<T>(op: Callable<T>): Task<Result<T>>;
 8+  function run<T>(
 9+    op: Callable<T> | Callable<T>[],
10+  ): Task<Result<T> | Result<T>[]> {
11+    if (Array.isArray(op)) {
12+      return scope.run(function* (): Operation<Result<T>[]> {
13+        const group = yield* parallel(op);
14+        const result = yield* group;
15+        return result;
16+      });
17+    }
18+    return scope.run(() => safe(op));
19+  }
20+
21+  return run;
22+}
M store/schema.ts
+6, -1
 1@@ -1,11 +1,16 @@
 2 import { updateStore } from "./fx.ts";
 3+import { slice } from "./slice/mod.ts";
 4 import { FxMap, FxSchema, StoreUpdater } from "./types.ts";
 5 
 6+const defaultSchema = function <O>(): O {
 7+  return { cache: slice.table(), loaders: slice.loaders() } as O;
 8+};
 9+
10 export function createSchema<
11   O extends FxMap,
12   S extends { [key in keyof O]: ReturnType<O[key]>["initialState"] },
13 >(
14-  slices: O,
15+  slices: O = defaultSchema<O>(),
16 ): [FxSchema<S, O>, S] {
17   const db = Object.keys(slices).reduce<FxSchema<S, O>>((acc, key) => {
18     // deno-lint-ignore no-explicit-any
M store/store.ts
+8, -15
 1@@ -1,21 +1,18 @@
 2 import {
 3-  Callable,
 4   createScope,
 5   createSignal,
 6   enablePatches,
 7   Ok,
 8   produceWithPatches,
 9-  Result,
10   Scope,
11-  Task,
12 } from "../deps.ts";
13 import { BaseMiddleware, compose } from "../compose.ts";
14 import type { AnyAction, AnyState, Next } from "../types.ts";
15-import { safe } from "../fx/mod.ts";
16 import type { FxStore, Listener, StoreUpdater, UpdaterCtx } from "./types.ts";
17 import { StoreContext, StoreUpdateContext } from "./context.ts";
18 import { ActionContext, emit } from "../action.ts";
19 import { API_ACTION_PREFIX } from "../action.ts";
20+import { createRun } from "./run.ts";
21 
22 const stubMsg = "This is merely a stub, not implemented";
23 
24@@ -147,10 +144,6 @@ export function createStore<S extends AnyState>({
25     emit({ signal, action });
26   }
27 
28-  function run<T>(op: Callable<T>): Task<Result<T>> {
29-    return scope.run(() => safe(op));
30-  }
31-
32   function getInitialState() {
33     return initialState;
34   }
35@@ -168,13 +161,13 @@ export function createStore<S extends AnyState>({
36     });
37   }
38 
39-  return {
40+  const store = {
41     getScope,
42     getState,
43     subscribe,
44     update,
45     reset,
46-    run,
47+    run: createRun(scope),
48     // instead of actions relating to store mutation, they
49     // refer to pieces of business logic -- that can also mutate state
50     dispatch,
51@@ -188,13 +181,13 @@ export function createStore<S extends AnyState>({
52     getInitialState,
53     [Symbol.observable]: observable,
54   };
55-}
56 
57-export function configureStore<S extends AnyState>(
58-  props: CreateStore<S>,
59-): FxStore<S> {
60-  const store = createStore<S>(props);
61   // deno-lint-ignore no-explicit-any
62   store.getScope().set(StoreContext, store as any);
63   return store;
64 }
65+
66+/**
67+ * @deprecated use {@link createStore}
68+ */
69+export const configureStore = createStore;
M store/types.ts
+3, -9
 1@@ -1,15 +1,9 @@
 2 import type { LoaderOutput } from "./slice/loaders.ts";
 3 import type { TableOutput } from "./slice/table.ts";
 4-import type {
 5-  Callable,
 6-  Operation,
 7-  Patch,
 8-  Result,
 9-  Scope,
10-  Task,
11-} from "../deps.ts";
12+import type { Operation, Patch, Scope } from "../deps.ts";
13 import { BaseCtx } from "../mod.ts";
14 import type { AnyAction, AnyState } from "../types.ts";
15+import { createRun } from "./run.ts";
16 
17 export type StoreUpdater<S extends AnyState> = (s: S) => S | void;
18 
19@@ -54,7 +48,7 @@ export interface FxStore<S extends AnyState> {
20   subscribe: (fn: Listener) => () => void;
21   update: (u: StoreUpdater<S> | StoreUpdater<S>[]) => Operation<UpdaterCtx<S>>;
22   reset: (ignoreList?: (keyof S)[]) => Operation<UpdaterCtx<S>>;
23-  run: <T>(op: Callable<T>) => Task<Result<T>>;
24+  run: ReturnType<typeof createRun>;
25   // deno-lint-ignore no-explicit-any
26   dispatch: (a: AnyAction) => any;
27   replaceReducer: (r: (s: S, a: AnyAction) => S) => void;
M test/api.test.ts
+14, -18
  1@@ -1,10 +1,9 @@
  2 import { describe, expect, it } from "../test.ts";
  3 import {
  4-  configureStore,
  5   createSchema,
  6+  createStore,
  7   select,
  8   slice,
  9-  storeMdw,
 10   updateStore,
 11   waitForLoader,
 12 } from "../store/mod.ts";
 13@@ -38,7 +37,7 @@ const testStore = () => {
 14     loaders: slice.loaders(),
 15     cache: slice.table({ empty: {} }),
 16   });
 17-  const store = configureStore({ initialState });
 18+  const store = createStore({ initialState });
 19   return { schema, store };
 20 };
 21 
 22@@ -101,7 +100,7 @@ it(tests, "POST", async () => {
 23     },
 24   );
 25 
 26-  const store = configureStore({ initialState: { users: {} } });
 27+  const store = createStore({ initialState: { users: {} } });
 28   store.run(query.bootup);
 29 
 30   store.dispatch(createUser({ email: mockUser.email }));
 31@@ -158,7 +157,7 @@ it(tests, "POST with uri", () => {
 32     },
 33   );
 34 
 35-  const store = configureStore({ initialState: { users: {} } });
 36+  const store = createStore({ initialState: { users: {} } });
 37   store.run(query.bootup);
 38   store.dispatch(createUser({ email: mockUser.email }));
 39 });
 40@@ -178,7 +177,7 @@ it(tests, "middleware - with request fn", () => {
 41     { supervisor: takeEvery },
 42     query.request({ method: "POST" }),
 43   );
 44-  const store = configureStore({ initialState: { users: {} } });
 45+  const store = createStore({ initialState: { users: {} } });
 46   store.run(query.bootup);
 47   store.dispatch(createUser());
 48 });
 49@@ -206,7 +205,7 @@ it(tests, "run() on endpoint action - should run the effect", () => {
 50     },
 51   );
 52 
 53-  const store = configureStore({ initialState: { users: {} } });
 54+  const store = createStore({ initialState: { users: {} } });
 55   store.run(api.bootup);
 56   store.dispatch(action2());
 57 });
 58@@ -240,7 +239,7 @@ it(tests, "run() from a normal saga", () => {
 59     yield* takeEvery(`${action2}`, onAction);
 60   }
 61 
 62-  const store = configureStore({ initialState: { users: {} } });
 63+  const store = createStore({ initialState: { users: {} } });
 64   store.run(() => keepAlive([api.bootup, watchAction]));
 65   store.dispatch(action2());
 66 });
 67@@ -248,8 +247,7 @@ it(tests, "run() from a normal saga", () => {
 68 it(tests, "with hash key on a large post", async () => {
 69   const { store, schema } = testStore();
 70   const query = createApi();
 71-  query.use(mdw.api());
 72-  query.use(storeMdw.store(schema));
 73+  query.use(mdw.api({ schema }));
 74   query.use(query.routes());
 75   query.use(function* fetchApi(ctx, next) {
 76     const data = {
 77@@ -318,8 +316,7 @@ it(tests, "two identical endpoints", () => {
 78   const actual: string[] = [];
 79   const { store, schema } = testStore();
 80   const api = createApi();
 81-  api.use(mdw.api());
 82-  api.use(storeMdw.store(schema));
 83+  api.use(mdw.api({ schema }));
 84   api.use(api.routes());
 85 
 86   const first = api.get(
 87@@ -375,7 +372,7 @@ it(tests, "ensure types for get() endpoint", () => {
 88     },
 89   );
 90 
 91-  const store = configureStore({ initialState: { users: {} } });
 92+  const store = createStore({ initialState: { users: {} } });
 93   store.run(api.bootup);
 94 
 95   store.dispatch(action1({ id: "1" }));
 96@@ -413,7 +410,7 @@ it(tests, "ensure ability to cast `ctx` in function definition", () => {
 97     },
 98   );
 99 
100-  const store = configureStore({ initialState: { users: {} } });
101+  const store = createStore({ initialState: { users: {} } });
102   store.run(api.bootup);
103   store.dispatch(action1({ id: "1" }));
104   expect(acc).toEqual(["1", "wow"]);
105@@ -449,7 +446,7 @@ it(
106       },
107     );
108 
109-    const store = configureStore({ initialState: { users: {} } });
110+    const store = createStore({ initialState: { users: {} } });
111     store.run(api.bootup);
112     store.dispatch(action1());
113     expect(acc).toEqual(["wow"]);
114@@ -458,7 +455,7 @@ it(
115 
116 it(tests, "should bubble up error", () => {
117   let error: any = null;
118-  const { store, schema } = testStore();
119+  const { store } = testStore();
120   const api = createApi();
121   api.use(function* (_, next) {
122     try {
123@@ -468,7 +465,6 @@ it(tests, "should bubble up error", () => {
124     }
125   });
126   api.use(mdw.queryCtx);
127-  api.use(storeMdw.store(schema));
128   api.use(api.routes());
129 
130   const fetchUser = api.get(
131@@ -518,7 +514,7 @@ it(
132       },
133     );
134 
135-    const store = configureStore({ initialState: { users: {} } });
136+    const store = createStore({ initialState: { users: {} } });
137     store.run(api.bootup);
138 
139     function _App() {
M test/batch.test.ts
+2, -2
 1@@ -1,8 +1,8 @@
 2 import { describe, expect, it } from "../test.ts";
 3 import {
 4-  configureStore,
 5   createBatchMdw,
 6   createSchema,
 7+  createStore,
 8   slice,
 9 } from "../store/mod.ts";
10 import { parallel } from "../mod.ts";
11@@ -14,7 +14,7 @@ it(batch, "should batch notify subscribers based on mdw", async () => {
12     cache: slice.table({ empty: {} }),
13     loaders: slice.loaders(),
14   });
15-  const store = configureStore({
16+  const store = createStore({
17     initialState,
18     middleware: [createBatchMdw(queueMicrotask)],
19   });
R test/configureStore.test.ts => test/create-store.test.ts
+4, -4
 1@@ -1,8 +1,8 @@
 2 import { describe, expect, it } from "../test.ts";
 3-import { configureStore, select } from "../store/mod.ts";
 4+import { createStore, select } from "../store/mod.ts";
 5 import { call } from "../mod.ts";
 6 
 7-const tests = describe("configureStore()");
 8+const tests = describe("createStore()");
 9 
10 interface TestState {
11   user: { id: string };
12@@ -10,7 +10,7 @@ interface TestState {
13 
14 it(tests, "should be able to grab values from store", async () => {
15   let actual;
16-  const store = configureStore({ initialState: { user: { id: "1" } } });
17+  const store = createStore({ initialState: { user: { id: "1" } } });
18   await store.run(function* () {
19     actual = yield* select((s: TestState) => s.user);
20   });
21@@ -19,7 +19,7 @@ it(tests, "should be able to grab values from store", async () => {
22 
23 it(tests, "should be able to grab store from a nested call", async () => {
24   let actual;
25-  const store = configureStore({ initialState: { user: { id: "2" } } });
26+  const store = createStore({ initialState: { user: { id: "2" } } });
27   await store.run(function* () {
28     actual = yield* call(function* () {
29       return yield* select((s: TestState) => s.user);
M test/fetch.test.ts
+14, -27
  1@@ -1,9 +1,8 @@
  2 import { describe, expect, install, it, mock } from "../test.ts";
  3 import {
  4-  configureStore,
  5   createSchema,
  6+  createStore,
  7   slice,
  8-  storeMdw,
  9   waitForLoader,
 10   waitForLoaders,
 11 } from "../store/mod.ts";
 12@@ -19,7 +18,7 @@ const testStore = () => {
 13     loaders: slice.loaders(),
 14     cache: slice.table({ empty: {} }),
 15   });
 16-  const store = configureStore({ initialState });
 17+  const store = createStore({ initialState });
 18   return { schema, store };
 19 };
 20 
 21@@ -39,8 +38,7 @@ it(
 22 
 23     const { store, schema } = testStore();
 24     const api = createApi();
 25-    api.use(mdw.api());
 26-    api.use(storeMdw.store(schema));
 27+    api.use(mdw.api({ schema }));
 28     api.use(api.routes());
 29     api.use(mdw.headers);
 30     api.use(mdw.fetch({ baseUrl }));
 31@@ -87,8 +85,7 @@ it(
 32 
 33     const { store, schema } = testStore();
 34     const api = createApi();
 35-    api.use(mdw.api());
 36-    api.use(storeMdw.store(schema));
 37+    api.use(mdw.api({ schema }));
 38     api.use(api.routes());
 39     api.use(mdw.fetch({ baseUrl }));
 40 
 41@@ -124,8 +121,7 @@ it(tests, "error handling", async () => {
 42 
 43   const { schema, store } = testStore();
 44   const api = createApi();
 45-  api.use(mdw.api());
 46-  api.use(storeMdw.store(schema));
 47+  api.use(mdw.api({ schema }));
 48   api.use(api.routes());
 49   api.use(mdw.fetch({ baseUrl }));
 50 
 51@@ -160,8 +156,7 @@ it(tests, "status 204", async () => {
 52 
 53   const { schema, store } = testStore();
 54   const api = createApi();
 55-  api.use(mdw.api());
 56-  api.use(storeMdw.store(schema));
 57+  api.use(mdw.api({ schema }));
 58   api.use(api.routes());
 59   api.use(function* (ctx, next) {
 60     const url = ctx.req().url;
 61@@ -200,8 +195,7 @@ it(tests, "malformed json", async () => {
 62 
 63   const { schema, store } = testStore();
 64   const api = createApi();
 65-  api.use(mdw.api());
 66-  api.use(storeMdw.store(schema));
 67+  api.use(mdw.api({ schema }));
 68   api.use(api.routes());
 69   api.use(function* (ctx, next) {
 70     const url = ctx.req().url;
 71@@ -245,8 +239,7 @@ it(tests, "POST", async () => {
 72 
 73   const { schema, store } = testStore();
 74   const api = createApi();
 75-  api.use(mdw.api());
 76-  api.use(storeMdw.store(schema));
 77+  api.use(mdw.api({ schema }));
 78   api.use(api.routes());
 79   api.use(mdw.headers);
 80   api.use(mdw.fetch({ baseUrl }));
 81@@ -297,8 +290,7 @@ it(tests, "POST multiple endpoints with same uri", async () => {
 82 
 83   const { store, schema } = testStore();
 84   const api = createApi();
 85-  api.use(mdw.api());
 86-  api.use(storeMdw.store(schema));
 87+  api.use(mdw.api({ schema }));
 88   api.use(api.routes());
 89   api.use(mdw.headers);
 90   api.use(mdw.fetch({ baseUrl }));
 91@@ -385,8 +377,7 @@ it(
 92   () => {
 93     const { store, schema } = testStore();
 94     const api = createApi();
 95-    api.use(mdw.api());
 96-    api.use(storeMdw.store(schema));
 97+    api.use(mdw.api({ schema }));
 98     api.use(api.routes());
 99     api.use(mdw.fetch({ baseUrl }));
100     let actual = "";
101@@ -432,8 +423,7 @@ it(
102 
103     const { schema, store } = testStore();
104     const api = createApi();
105-    api.use(mdw.api());
106-    api.use(storeMdw.store(schema));
107+    api.use(mdw.api({ schema }));
108     api.use(api.routes());
109     api.use(mdw.fetch({ baseUrl }));
110 
111@@ -481,8 +471,7 @@ it(
112     const { schema, store } = testStore();
113     let actual = null;
114     const api = createApi();
115-    api.use(mdw.api());
116-    api.use(storeMdw.store(schema));
117+    api.use(mdw.api({ schema }));
118     api.use(api.routes());
119     api.use(mdw.fetch({ baseUrl }));
120 
121@@ -515,8 +504,7 @@ it(
122     const { schema, store } = testStore();
123     let actual = null;
124     const api = createApi();
125-    api.use(mdw.api());
126-    api.use(storeMdw.store(schema));
127+    api.use(mdw.api({ schema }));
128     api.use(api.routes());
129     api.use(mdw.fetch({ baseUrl }));
130 
131@@ -543,8 +531,7 @@ it(tests, "should use dynamic mdw to mock response", async () => {
132   const { schema, store } = testStore();
133   let actual = null;
134   const api = createApi();
135-  api.use(mdw.api());
136-  api.use(storeMdw.store(schema));
137+  api.use(mdw.api({ schema }));
138   api.use(api.routes());
139   api.use(mdw.fetch({ baseUrl }));
140 
M test/mdw.test.ts
+17, -27
  1@@ -1,9 +1,8 @@
  2 import { assertLike, asserts, describe, expect, it } from "../test.ts";
  3 import {
  4-  configureStore,
  5   createSchema,
  6+  createStore,
  7   slice,
  8-  storeMdw,
  9   updateStore,
 10   waitForLoader,
 11 } from "../store/mod.ts";
 12@@ -40,17 +39,16 @@ const testStore = () => {
 13     loaders: slice.loaders(),
 14     cache: slice.table({ empty: {} }),
 15   });
 16-  const store = configureStore({ initialState });
 17+  const store = createStore({ initialState });
 18   return { schema, store };
 19 };
 20 
 21 const tests = describe("middleware");
 22 
 23 it(tests, "basic", () => {
 24-  const { store } = testStore();
 25+  const { store, schema } = testStore();
 26   const query = createApi<ApiCtx>();
 27-  query.use(mdw.queryCtx);
 28-  query.use(mdw.api());
 29+  query.use(mdw.api({ schema }));
 30   query.use(query.routes());
 31   query.use(function* fetchApi(ctx, next) {
 32     if (`${ctx.req().url}`.startsWith("/users/")) {
 33@@ -113,8 +111,7 @@ it(tests, "basic", () => {
 34 it(tests, "with loader", () => {
 35   const { schema, store } = testStore();
 36   const api = createApi<ApiCtx>();
 37-  api.use(mdw.api());
 38-  api.use(storeMdw.store(schema));
 39+  api.use(mdw.api({ schema }));
 40   api.use(api.routes());
 41   api.use(function* fetchApi(ctx, next) {
 42     ctx.response = new Response(jsonBlob(mockUser), { status: 200 });
 43@@ -156,8 +153,7 @@ it(tests, "with loader", () => {
 44 it(tests, "with item loader", () => {
 45   const { store, schema } = testStore();
 46   const api = createApi<ApiCtx>();
 47-  api.use(mdw.api());
 48-  api.use(storeMdw.store(schema));
 49+  api.use(mdw.api({ schema }));
 50   api.use(api.routes());
 51   api.use(function* fetchApi(ctx, next) {
 52     ctx.response = new Response(jsonBlob(mockUser), { status: 200 });
 53@@ -200,9 +196,10 @@ it(tests, "with item loader", () => {
 54 });
 55 
 56 it(tests, "with POST", () => {
 57+  const { store, schema } = testStore();
 58   const query = createApi();
 59   query.use(mdw.queryCtx);
 60-  query.use(mdw.api());
 61+  query.use(mdw.api({ schema }));
 62   query.use(query.routes());
 63   query.use(function* fetchApi(ctx, next) {
 64     const request = ctx.req();
 65@@ -245,7 +242,6 @@ it(tests, "with POST", () => {
 66     },
 67   );
 68 
 69-  const { store } = testStore();
 70   store.run(query.bootup);
 71   store.dispatch(createUser({ email: mockUser.email }));
 72 });
 73@@ -253,8 +249,7 @@ it(tests, "with POST", () => {
 74 it(tests, "simpleCache", () => {
 75   const { store, schema } = testStore();
 76   const api = createApi<ApiCtx>();
 77-  api.use(mdw.api());
 78-  api.use(storeMdw.store(schema));
 79+  api.use(mdw.api({ schema }));
 80   api.use(api.routes());
 81   api.use(function* fetchApi(ctx, next) {
 82     const data = { users: [mockUser] };
 83@@ -283,8 +278,7 @@ it(tests, "simpleCache", () => {
 84 it(tests, "overriding default loader behavior", () => {
 85   const { store, schema } = testStore();
 86   const api = createApi<ApiCtx>();
 87-  api.use(mdw.api());
 88-  api.use(storeMdw.store(schema));
 89+  api.use(mdw.api({ schema }));
 90   api.use(api.routes());
 91   api.use(function* fetchApi(ctx, next) {
 92     const data = { users: [mockUser] };
 93@@ -340,8 +334,7 @@ it(tests, "mdw.api() - error handler", () => {
 94 
 95   const { schema, store } = testStore();
 96   const query = createApi<ApiCtx>();
 97-  query.use(mdw.api());
 98-  query.use(storeMdw.store(schema));
 99+  query.use(mdw.api({ schema }));
100   query.use(query.routes());
101   query.use(function* () {
102     throw new Error("something happened");
103@@ -356,8 +349,7 @@ it(tests, "mdw.api() - error handler", () => {
104 it(tests, "createApi with own key", async () => {
105   const { schema, store } = testStore();
106   const query = createApi();
107-  query.use(mdw.api());
108-  query.use(storeMdw.store(schema));
109+  query.use(mdw.api({ schema }));
110   query.use(query.routes());
111   query.use(mdw.customKey);
112   query.use(function* fetchApi(ctx, next) {
113@@ -429,8 +421,7 @@ it(tests, "createApi with own key", async () => {
114 it(tests, "createApi with custom key but no payload", async () => {
115   const { store, schema } = testStore();
116   const query = createApi();
117-  query.use(mdw.api());
118-  query.use(storeMdw.store(schema));
119+  query.use(mdw.api({ schema }));
120   query.use(query.routes());
121   query.use(mdw.customKey);
122   query.use(function* fetchApi(ctx, next) {
123@@ -539,7 +530,7 @@ it(tests, "errorHandler", () => {
124     },
125   );
126 
127-  const store = configureStore({
128+  const store = createStore({
129     initialState: {
130       users: {},
131     },
132@@ -554,12 +545,14 @@ it(tests, "errorHandler", () => {
133 
134 it(tests, "stub predicate", async () => {
135   let actual: { ok: boolean } = { ok: false };
136+  const { store, schema } = testStore();
137   const api = createApi();
138   api.use(function* (ctx, next) {
139     ctx.stub = true;
140     yield* next();
141   });
142-  api.use(mdw.api());
143+
144+  api.use(mdw.api({ schema }));
145   api.use(api.routes());
146   api.use(mdw.fetch({ baseUrl: "http://nowhere.com" }));
147 
148@@ -577,9 +570,6 @@ it(tests, "stub predicate", async () => {
149     }),
150   ]);
151 
152-  const store = configureStore({
153-    initialState: {},
154-  });
155   store.run(api.bootup);
156   store.dispatch(fetchUsers());
157 
M test/persist.test.ts
+3, -3
 1@@ -1,8 +1,8 @@
 2 import { asserts, describe, it } from "../test.ts";
 3 import {
 4-  configureStore,
 5   createPersistor,
 6   createSchema,
 7+  createStore,
 8   PERSIST_LOADER_ID,
 9   PersistAdapter,
10   persistStoreMdw,
11@@ -34,7 +34,7 @@ it(tests, "can persist to storage adapters", async () => {
12   };
13   const persistor = createPersistor<State>({ adapter, allowlist: ["token"] });
14   const mdw = persistStoreMdw(persistor);
15-  const store = configureStore({
16+  const store = createStore({
17     initialState,
18     middleware: [mdw],
19   });
20@@ -82,7 +82,7 @@ it(tests, "rehydrates state", async () => {
21   };
22   const persistor = createPersistor<State>({ adapter, allowlist: ["token"] });
23   const mdw = persistStoreMdw(persistor);
24-  const store = configureStore({
25+  const store = createStore({
26     initialState,
27     middleware: [mdw],
28   });
M test/put.test.ts
+5, -5
 1@@ -1,6 +1,6 @@
 2 import { describe, expect, it } from "../test.ts";
 3 import { ActionContext, each, put, sleep, spawn, take } from "../mod.ts";
 4-import { configureStore } from "../store/mod.ts";
 5+import { createStore } from "../store/mod.ts";
 6 
 7 const putTests = describe("put()");
 8 
 9@@ -26,7 +26,7 @@ it(putTests, "should send actions through channel", async () => {
10     yield* task;
11   }
12 
13-  const store = configureStore({ initialState: {} });
14+  const store = createStore({ initialState: {} });
15   await store.run(() => genFn("arg"));
16 
17   const expected = ["arg", "2"];
18@@ -56,7 +56,7 @@ it(putTests, "should handle nested puts", async () => {
19     yield* spawn(genA);
20   }
21 
22-  const store = configureStore({ initialState: {} });
23+  const store = createStore({ initialState: {} });
24   await store.run(() => root());
25 
26   const expected = ["put b", "put a"];
27@@ -74,7 +74,7 @@ it(
28       yield* sleep(0);
29     }
30 
31-    const store = configureStore({ initialState: {} });
32+    const store = createStore({ initialState: {} });
33     await store.run(() => root());
34     expect(true).toBe(true);
35   },
36@@ -102,7 +102,7 @@ it(
37       yield* tsk;
38     }
39 
40-    const store = configureStore({ initialState: {} });
41+    const store = createStore({ initialState: {} });
42     await store.run(root);
43     const expected = ["didn't get missed"];
44     expect(actual).toEqual(expected);
M test/schema.test.ts
+26, -3
 1@@ -1,5 +1,5 @@
 2 import { asserts, describe, it } from "../test.ts";
 3-import { configureStore, createSchema, select, slice } from "../store/mod.ts";
 4+import { createSchema, createStore, select, slice } from "../store/mod.ts";
 5 
 6 const tests = describe("createSchema()");
 7 
 8@@ -12,6 +12,29 @@ interface UserWithRoles extends User {
 9 }
10 
11 const emptyUser = { id: "", name: "" };
12+
13+it(tests, "default schema", async () => {
14+  const [schema, initialState] = createSchema();
15+  const store = createStore({ initialState });
16+  asserts.assertEquals(store.getState(), {
17+    cache: {},
18+    loaders: {},
19+  });
20+
21+  await store.run(function* () {
22+    yield* schema.update(schema.loaders.start({ id: "1" }));
23+    yield* schema.update(schema.cache.add({ "1": true }));
24+  });
25+
26+  asserts.assertEquals(schema.cache.selectTable(store.getState()), {
27+    "1": true,
28+  });
29+  asserts.assertEquals(
30+    schema.loaders.selectById(store.getState(), { id: "1" }).status,
31+    "loading",
32+  );
33+});
34+
35 it(tests, "general types and functionality", async () => {
36   const [db, initialState] = createSchema({
37     users: slice.table<User>({
38@@ -25,7 +48,7 @@ it(tests, "general types and functionality", async () => {
39     cache: slice.table({ empty: {} }),
40     loaders: slice.loaders(),
41   });
42-  const store = configureStore({ initialState });
43+  const store = createStore({ initialState });
44 
45   asserts.assertEquals(store.getState(), {
46     users: { "1": { id: "1", name: "wow" } },
47@@ -75,7 +98,7 @@ it(tests, "can work with a nested object", async () => {
48     cache: slice.table({ empty: {} }),
49     loaders: slice.loaders(),
50   });
51-  const store = configureStore({ initialState });
52+  const store = createStore({ initialState });
53   await store.run(function* () {
54     yield* db.update(db.currentUser.update({ key: "name", value: "vvv" }));
55     const curUser = yield* select(db.currentUser.select);
M test/store.test.ts
+3, -4
 1@@ -1,6 +1,5 @@
 2 import { asserts, describe, it } from "../test.ts";
 3 import {
 4-  configureStore,
 5   createStore,
 6   StoreContext,
 7   StoreUpdateContext,
 8@@ -94,7 +93,7 @@ it(tests, "update store and receives update from `subscribe()`", async () => {
 9     theme: "",
10     token: "",
11   };
12-  const store = configureStore({ initialState });
13+  const store = createStore({ initialState });
14 
15   store.subscribe(() => {
16     asserts.assertEquals(store.getState(), {
17@@ -117,7 +116,7 @@ it(tests, "emit Action and update store", async () => {
18     theme: "",
19     token: "",
20   };
21-  const store = configureStore({ initialState });
22+  const store = createStore({ initialState });
23 
24   await store.run(function* (): Operation<void> {
25     const result = yield* parallel([
26@@ -147,7 +146,7 @@ it(tests, "resets store", async () => {
27     theme: "",
28     token: "",
29   };
30-  const store = configureStore({ initialState });
31+  const store = createStore({ initialState });
32 
33   await store.run(function* () {
34     yield* store.update((s) => {
M test/take-helper.test.ts
+4, -4
 1@@ -1,5 +1,5 @@
 2 import { describe, expect, it } from "../test.ts";
 3-import { configureStore } from "../store/mod.ts";
 4+import { createStore } from "../store/mod.ts";
 5 import type { AnyAction } from "../mod.ts";
 6 import { sleep, take, takeEvery, takeLatest, takeLeading } from "../mod.ts";
 7 import { spawn } from "../deps.ts";
 8@@ -22,7 +22,7 @@ it(testLatest, "should cancel previous tasks and only use latest", async () => {
 9     yield* take("CANCEL_WATCHER");
10     yield* task.halt();
11   }
12-  const store = configureStore({ initialState: {} });
13+  const store = createStore({ initialState: {} });
14   const task = store.run(root);
15 
16   store.dispatch({ type: "ACTION", payload: "1" });
17@@ -49,7 +49,7 @@ it(testLeading, "should keep first action and discard the rest", async () => {
18     yield* sleep(150);
19     yield* task.halt();
20   }
21-  const store = configureStore({ initialState: {} });
22+  const store = createStore({ initialState: {} });
23   const task = store.run(root);
24 
25   store.dispatch({ type: "ACTION", payload: "1" });
26@@ -82,7 +82,7 @@ it(testEvery, "should receive all actions", async () => {
27     actual.push([arg1, arg2, action.payload]);
28   }
29 
30-  const store = configureStore({ initialState: {} });
31+  const store = createStore({ initialState: {} });
32   const task = store.run(root);
33 
34   for (let i = 1; i <= loop / 2; i += 1) {
M test/take.test.ts
+3, -3
 1@@ -1,7 +1,7 @@
 2 import { describe, expect, it } from "../test.ts";
 3 import type { AnyAction } from "../mod.ts";
 4 import { put, sleep, spawn, take } from "../mod.ts";
 5-import { configureStore } from "../store/mod.ts";
 6+import { createStore } from "../store/mod.ts";
 7 
 8 const takeTests = describe("take()");
 9 
10@@ -24,7 +24,7 @@ it(
11       actual.push(yield* take("action-1"));
12     }
13 
14-    const store = configureStore({ initialState: {} });
15+    const store = createStore({ initialState: {} });
16     await store.run(root);
17 
18     expect(actual).toEqual([
19@@ -84,7 +84,7 @@ it(takeTests, "take from default channel", async () => {
20     }
21   }
22 
23-  const store = configureStore({ initialState: {} });
24+  const store = createStore({ initialState: {} });
25   await store.run(genFn);
26 
27   const expected = [
M test/thunk.test.ts
+13, -13
  1@@ -1,5 +1,5 @@
  2 import { assertLike, asserts, describe, it } from "../test.ts";
  3-import { configureStore, updateStore } from "../store/mod.ts";
  4+import { createStore, updateStore } from "../store/mod.ts";
  5 import {
  6   call,
  7   createThunks,
  8@@ -135,7 +135,7 @@ it(
  9     api.use(processTickets);
 10     const fetchUsers = api.create(`/users`, { supervisor: takeEvery });
 11 
 12-    const store = configureStore<TestState>({
 13+    const store = createStore<TestState>({
 14       initialState: { users: {}, tickets: {} },
 15     });
 16     store.run(api.bootup);
 17@@ -172,7 +172,7 @@ it(
 18       yield* put(fetchUsers());
 19     });
 20 
 21-    const store = configureStore<TestState>({
 22+    const store = createStore<TestState>({
 23       initialState: { users: {}, tickets: {} },
 24     });
 25     store.run(api.bootup);
 26@@ -202,7 +202,7 @@ it(tests, "error handling", () => {
 27 
 28   const action = api.create(`/error`, { supervisor: takeEvery });
 29 
 30-  const store = configureStore({ initialState: {} });
 31+  const store = createStore({ initialState: {} });
 32   store.run(api.bootup);
 33   store.dispatch(action());
 34   asserts.assertStrictEquals(called, true);
 35@@ -227,7 +227,7 @@ it(tests, "error handling inside create", () => {
 36       }
 37     },
 38   );
 39-  const store = configureStore({ initialState: {} });
 40+  const store = createStore({ initialState: {} });
 41   store.run(api.bootup);
 42   store.dispatch(action());
 43   asserts.assertStrictEquals(called, true);
 44@@ -254,7 +254,7 @@ it(tests, "error inside endpoint mdw", () => {
 45     },
 46   );
 47 
 48-  const store = configureStore({
 49+  const store = createStore({
 50     initialState: {
 51       users: {},
 52     },
 53@@ -289,7 +289,7 @@ it(tests, "create fn is an array", () => {
 54     },
 55   ]);
 56 
 57-  const store = configureStore({ initialState: {} });
 58+  const store = createStore({ initialState: {} });
 59   store.run(api.bootup);
 60   store.dispatch(action());
 61 });
 62@@ -328,7 +328,7 @@ it(tests, "run() on endpoint action - should run the effect", () => {
 63     },
 64   );
 65 
 66-  const store = configureStore({ initialState: {} });
 67+  const store = createStore({ initialState: {} });
 68   store.run(api.bootup);
 69   store.dispatch(action2());
 70 });
 71@@ -370,7 +370,7 @@ it(
 72       },
 73     );
 74 
 75-    const store = configureStore({ initialState: {} });
 76+    const store = createStore({ initialState: {} });
 77     store.run(api.bootup);
 78     store.dispatch(action2());
 79   },
 80@@ -408,7 +408,7 @@ it(tests, "middleware order of execution", async () => {
 81     },
 82   );
 83 
 84-  const store = configureStore({ initialState: {} });
 85+  const store = createStore({ initialState: {} });
 86   store.run(api.bootup);
 87   store.dispatch(action());
 88 
 89@@ -440,7 +440,7 @@ it(tests, "retry with actionFn", async () => {
 90     },
 91   );
 92 
 93-  const store = configureStore({ initialState: {} });
 94+  const store = createStore({ initialState: {} });
 95   store.run(api.bootup);
 96   store.dispatch(action());
 97 
 98@@ -470,7 +470,7 @@ it(tests, "retry with actionFn with payload", async () => {
 99     },
100   );
101 
102-  const store = configureStore({ initialState: {} });
103+  const store = createStore({ initialState: {} });
104   store.run(api.bootup);
105   store.dispatch(action({ page: 1 }));
106 
107@@ -500,7 +500,7 @@ it(tests, "should only call thunk once", () => {
108     },
109   );
110 
111-  const store = configureStore({ initialState: {} });
112+  const store = createStore({ initialState: {} });
113   store.run(api.bootup);
114   store.dispatch(action2());
115   asserts.assertEquals(acc, "a");