Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
213fb30
store updater api
jbolda Sep 25, 2025
fbfce31
beginning of yjs example
jbolda Sep 25, 2025
7883a04
lint and fix command
jbolda Sep 25, 2025
f523c98
fmt again?
jbolda Sep 25, 2025
109dbf2
Merge branch 'main' into custom-store-updater
jbolda Nov 1, 2025
2fecfdd
use node 20 for publish
jbolda Nov 1, 2025
ab499db
Merge branch 'main' into custom-store-updater
jbolda Nov 5, 2025
f415a2a
try re-exporting full effection
jbolda Nov 15, 2025
0ad91fe
Merge branch 'main' into custom-store-updater
jbolda Nov 15, 2025
8471bf1
lint
jbolda Nov 15, 2025
674fad0
lift signals
jbolda Nov 18, 2025
806a08d
initialize is sequential
jbolda Nov 18, 2025
ef94711
pass scope to initialize
jbolda Nov 18, 2025
369d267
Merge branch 'main' into custom-store-updater
jbolda Dec 24, 2025
a9b0abd
store.manage()
jbolda Jan 3, 2026
86f4e94
spike out createSchema 'holding' the storage
jbolda Jan 23, 2026
fd7f968
Merge branch 'main' into custom-store-updater
jbolda Jan 24, 2026
04d709b
type test
jbolda Feb 1, 2026
781b139
full type refactor removing low value generics and overloads
jbolda Feb 24, 2026
b0daef9
Merge branch 'main' into custom-store-updater
jbolda Feb 25, 2026
01d1986
merge states, run initialize through resource loop
jbolda Feb 27, 2026
7b6c641
pass initialize through schema
jbolda Feb 27, 2026
a642825
move up watch init
jbolda Feb 27, 2026
550f31a
refine loader and react types
jbolda Apr 10, 2026
a5a7f74
tasks arg and manage helper
jbolda Apr 10, 2026
841e402
Merge branch 'main' into custom-store-updater
jbolda Apr 10, 2026
0e5a0f2
types massaging
jbolda Apr 13, 2026
5d7fe3f
add started to parallel
jbolda Apr 14, 2026
d13d5a5
examples and lints
jbolda Apr 14, 2026
962022b
switch to vitest in rtl test example
jbolda Apr 14, 2026
e119961
remove resource from direct store integration
jbolda Apr 14, 2026
cbbff1c
lint
jbolda Apr 14, 2026
190e536
remove vitest example alias
jbolda Apr 14, 2026
5d6918a
dedupe
jbolda Apr 14, 2026
82a5cd4
store runs produce
jbolda Apr 16, 2026
a54b1bd
fix yjs example
jbolda Apr 17, 2026
e7f7581
multi-schema as keyed object
jbolda May 6, 2026
cef07db
linted
jbolda May 6, 2026
f1c98cf
patience tested
jbolda May 6, 2026
6e59366
Provider handles multi-schema store
jbolda May 9, 2026
83fcfb9
cache helper, rework base schema slice type
jbolda May 15, 2026
338a588
docs update
jbolda May 17, 2026
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
2 changes: 2 additions & 0 deletions .github/workflows/examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ jobs:
fail-fast: false
matrix:
example:
- ./examples/basic
- ./examples/vite-react
- ./examples/parcel-react
- ./examples/tests-rtl
- ./examples/yjs

steps:
- name: checkout
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ docs/public/*
!docs/public/.gitkeep
dist/
src/package.json
.playwright*
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ Features:
import { createApi, createSchema, createStore, mdw, timer } from "starfx";
import { Provider, useCache } from "starfx/react";

const [schema, initialState] = createSchema();
const store = createStore({ initialState });
const schema = createSchema();

const api = createApi();
// mdw = middleware
Expand All @@ -36,7 +35,7 @@ const fetchRepo = api.get(
api.cache()
);

store.run(api.register);
const store = createStore({ schema, tasks: [api.register] });

function App() {
return (
Expand Down
4 changes: 2 additions & 2 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"rules": {
"recommended": true,
"suspicious": {
"noExplicitAny": "off",
"noImplicitAnyLet": "off",
"noExplicitAny": "warn",
"noImplicitAnyLet": "warn",
"noConfusingVoidType": "off"
},
"correctness": {
Expand Down
6 changes: 2 additions & 4 deletions docs/posts/dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@ The type signature of `dispatch`:
type Dispatch = (a: Action | Action[]) => any;
```

Within `starfx`, the `dispatch` function lives on the store.

```ts
const { createSchema, createStore } from "starfx";
const [schema, initialState] = createSchema();
const store = createStore({ initialState });
const schema = createSchema();
const store = createStore({ schema });

store.dispatch({ type: "action", payload: {} });
```
Expand Down
13 changes: 6 additions & 7 deletions docs/posts/endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ title: Endpoints
description: endpoints are tasks for managing HTTP requests
---

An endpoint is just a specialized thunk designed to manage http requests. It has
An endpoint is a specialized thunk designed to manage http requests. It has
a supervisor, it has a middleware stack, and it hijacks the unique id for our
thunks and turns it into a router.

```ts
import { createApi, createStore, mdw } from "starfx";
import { initialState, schema } from "./schema";
import { schema } from "./schema";

const api = createApi();
// composition of handy middleware for createApi to function
Expand All @@ -32,8 +32,7 @@ export const updateUser = api.post<{ id: string; name: string }>(
},
);

const store = createStore(initialState);
store.run(api.register);
const store = createStore({ schema, tasks: [api.register] });

store.dispatch(fetchUsers());
// now accessible with useCache(fetchUsers)
Expand Down Expand Up @@ -331,15 +330,15 @@ function deserializeComment(com: any): Comment {
};
}

const [schema, initialState] = createSchema({
cache: slice.table(),
const schema = createSchema({
cache: slice.cache(),
loaders: slice.loaders(),
token: slice.str(),
articles: slice.table<Article>(),
people: slice.table<Person>(),
comments: slice.table<Comment>(),
});
type WebState = typeof initialState;
type WebState = typeof schema.initialState;

const api = createApi();
api.use(mdw.api({ schema }));
Expand Down
5 changes: 2 additions & 3 deletions docs/posts/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,7 @@ every **5 minutes**, mimicking the basic features of `react-query`.
import { createApi, createSchema, createStore, mdw, timer } from "starfx";
import { Provider, useCache } from "starfx/react";

const [schema, initialState] = createSchema();
const store = createStore({ initialState });
const schema = createSchema();

const api = createApi();
// mdw = middleware
Expand All @@ -102,7 +101,7 @@ const fetchRepo = api.get(
api.cache(),
);

store.run(api.register);
const store = createStore({ schema, tasks: [api.register] });

function App() {
return (
Expand Down
4 changes: 2 additions & 2 deletions docs/posts/loaders.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ For thunks you can use `mdw.loader()` which will track the status of a thunk.
```ts
import { createThunks, mdw } from "starfx";
// imaginary schema
import { initialState, schema } from "./schema";
import { schema } from "./schema";

const thunks = createThunks();
thunks.use(mdw.loader(schema));
Expand All @@ -45,7 +45,7 @@ const go = thunks.create("go", function* (ctx, next) {
throw new Error("boom!");
});

const store = createStore({ initialState });
const store = createStore({ schema });
store.dispatch(go());
schema.loaders.selectById(store.getState(), { id: `${go}` });
// status = "error"; message = "boom!"
Expand Down
8 changes: 4 additions & 4 deletions docs/posts/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ a place for `starfx` and third-party functionality to hold their state.
```ts
import { createSchema, slice } from "starfx";

const [schema, initialState] = createSchema({
cache: slice.table(),
const schema = createSchema({
cache: slice.cache(),
loaders: slice.loaders(),
});
```
Expand Down Expand Up @@ -249,10 +249,10 @@ export function counter(initialState?: number) {
return (name: string) => createCounter<AnyState>({ name, initialState });
}

const [schema, initialState] = createSchema({
const schema = createSchema({
counter: counter(100),
});
const store = createStore(initialState);
const store = createStore({ schema });

store.run(function* () {
yield* schema.update([
Expand Down
23 changes: 9 additions & 14 deletions docs/posts/store.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ interface User {
}

// app-wide database for ui, api data, or anything that needs reactivity
const [schema, initialState] = createSchema({
cache: slice.table(),
const schema = createSchema({
cache: slice.cache(),
loaders: slice.loaders(),
users: slice.table<User>(),
});
type WebState = typeof initialState;
type WebState = typeof schema.initialState;

// just a normal endpoint
const fetchUsers = api.get<never, User[]>(
Expand Down Expand Up @@ -93,15 +93,14 @@ const fetchUsers = api.get<never, User[]>(
},
);

const store = createStore(schema);
store.run(api.register);
const store = createStore({ schema, tasks: [api.register] });
store.dispatch(fetchUsers());
```

# How to update state

There are **three** ways to update state, each with varying degrees of type
safety:
There are **two** common ways to update state, each with varying degrees of
type safety:

```ts
import { updateStore } from "starfx";
Expand All @@ -112,16 +111,12 @@ function*() {
// no types
yield* updateStore([/* ... */]);
}

store.run(function*() {
// no types
yield* store.update([/* ... */]);
});
```

`schema.update` has the highest type safety because it knows your state shape.
The other methods are more generic and the user will have to provide types to
them manually.
`updateStore` is more generic and the user will have to provide types to it
manually. For lower-level integration, the store also exposes `setState([...])`
directly.

# Updater function

Expand Down
32 changes: 18 additions & 14 deletions examples/basic/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import ReactDOM from "react-dom/client";
import { createApi, createSchema, createStore, mdw, timer } from "starfx";
import {
createApi,
createSchema,
createStore,
mdw,
slice,
timer,
} from "starfx";
import { Provider, useCache } from "starfx/react";

const [schema, initialState] = createSchema();
const store = createStore({ initialState });

const api = createApi();
const schema = createSchema({
cache: slice.table(),
loaders: slice.loaders(),
});
const store = createStore({ schema, tasks: [api.register] });

// mdw = middleware
api.use(mdw.api({ schema }));
api.use(api.routes());
Expand All @@ -14,17 +24,11 @@ api.use(mdw.fetch({ baseUrl: "https://api.github.com" }));
const fetchRepo = api.get(
"/repos/neurosnap/starfx",
{ supervisor: timer() },
api.cache(),
api.cache()
);

store.run(api.register);

function App() {
return (
<Provider schema={schema} store={store}>
<Example />
</Provider>
);
return <Example />;
}

function Example() {
Expand All @@ -47,7 +51,7 @@ function Example() {

const root = document.getElementById("root") as HTMLElement;
ReactDOM.createRoot(root).render(
<Provider schema={schema} store={store}>
<Provider store={store}>
<App />
</Provider>,
</Provider>
);
7 changes: 7 additions & 0 deletions examples/basic/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"compilerOptions": {
"baseUrl": ".",
"forceConsistentCasingInFileNames": true,
"ignoreDeprecations": "5.0",
"target": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"module": "ESNext",
Expand All @@ -12,6 +15,10 @@
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"paths": {
"starfx": ["../../src/index.ts"],
"starfx/react": ["../../src/react.ts"]
},

/* Linting */
"strict": true,
Expand Down
7 changes: 0 additions & 7 deletions examples/tests-rtl/babel.config.js

This file was deleted.

11 changes: 0 additions & 11 deletions examples/tests-rtl/jest.config.js

This file was deleted.

14 changes: 6 additions & 8 deletions examples/tests-rtl/package.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
{
"name": "tests-rtl",
"scripts": {
"test": "jest"
"test": "vitest run"
},
"dependencies": {
"starfx": "file:../.."
},
"devDependencies": {
"@babel/core": "^7.28.0",
"@babel/preset-env": "^7.28.0",
"@babel/preset-react": "^7.27.1",
"@babel/preset-typescript": "^7.27.1",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"babel-jest": "^30.0.4",
"jest": "^30.0.4",
"jest-environment-jsdom": "^30.0.4",
"jsdom": "^26.1.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-redux": "^9.1.2",
"vitest": "^4.0.7",
"whatwg-fetch": "^3.6.20"
}
}
30 changes: 17 additions & 13 deletions examples/tests-rtl/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { createApi, createSchema, mdw, slice } from "starfx";
import { createTypedHooks } from "starfx/react";

const emptyUser = { id: "", name: "" };
export const [schema, initialState] = createSchema({
type User = typeof emptyUser;
export const schema = createSchema({
users: slice.table({ empty: emptyUser }),
cache: slice.table(),
loaders: slice.loaders(),
Expand All @@ -12,20 +14,22 @@ api.use(mdw.api({ schema }));
api.use(api.routes());
api.use(mdw.fetch({ baseUrl: "https://jsonplaceholder.typicode.com" }));

export const fetchUsers = api.get(
"/users",
function* (ctx, next) {
yield* next();
export const fetchUsers = api.get("/users", function* (ctx, next) {
yield* next();

if (!ctx.json.ok) {
return;
}
if (!ctx.json.ok) {
return;
}

const users = ctx.json.value.reduce((acc, user) => {
const users = (ctx.json.value as User[]).reduce<Record<string, User>>(
(acc, user) => {
acc[user.id] = user;
return acc;
}, {});
},
{},
);

yield* schema.update(schema.users.add(users));
},
);
yield* schema.update(schema.users.add(users));
});
const { useSelector } = createTypedHooks(schema);
export { useSelector };
Loading
Loading