use-entity 0.0.1-alpha → 0.1.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -6
- package/dist/index.cjs +78 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +89 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/package.json +22 -7
- package/.github/workflows/release-please.yml +0 -52
- package/CLAUDE.md +0 -111
- package/biome.json +0 -34
- package/bun.lock +0 -180
- package/bunfig.toml +0 -2
- package/happydom.ts +0 -3
- package/index.ts +0 -1
- package/lefthook.yml +0 -5
- package/matchers.d.ts +0 -7
- package/src/__tests__/useEntity.test.ts +0 -271
- package/src/types.ts +0 -48
- package/src/uncheckedindexed.ts +0 -16
- package/src/useEntity.ts +0 -128
- package/testing-library.ts +0 -10
- package/tsconfig.json +0 -29
package/README.md
CHANGED
|
@@ -1,15 +1,100 @@
|
|
|
1
1
|
# use-entity
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Fast, typed entity state for React with minimal boilerplate. Use it to manage
|
|
4
|
+
normalized collections with a ready-to-use hook, selector helpers, and adapter
|
|
5
|
+
actions that stay portable across state managers. Powered by Redux Toolkit's
|
|
6
|
+
[`createEntityAdapter`](https://redux-toolkit.js.org/api/createEntityAdapter).
|
|
7
|
+
|
|
8
|
+
## Install
|
|
4
9
|
|
|
5
10
|
```bash
|
|
6
|
-
|
|
11
|
+
npm install use-entity
|
|
7
12
|
```
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
## Quick Start (TanStack React Store)
|
|
10
15
|
|
|
11
|
-
```
|
|
12
|
-
|
|
16
|
+
```tsx
|
|
17
|
+
import { createEntityStoreTanstack } from "use-entity";
|
|
18
|
+
|
|
19
|
+
type Todo = { id: string; title: string; done: boolean };
|
|
20
|
+
|
|
21
|
+
const { useEntity } = createEntityStoreTanstack<Todo>([
|
|
22
|
+
{ id: "1", title: "Ship it", done: false },
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
function TodoList() {
|
|
26
|
+
const [state, actions] = useEntity();
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div>
|
|
30
|
+
<ul>
|
|
31
|
+
{state.all.map((todo) => (
|
|
32
|
+
<li key={todo.id}>{todo.title}</li>
|
|
33
|
+
))}
|
|
34
|
+
</ul>
|
|
35
|
+
<button
|
|
36
|
+
onClick={() =>
|
|
37
|
+
actions.addOne({ id: "2", title: "Celebrate", done: false })
|
|
38
|
+
}
|
|
39
|
+
>
|
|
40
|
+
Add
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Selecting State
|
|
48
|
+
|
|
49
|
+
`useEntity()` returns the full selector object by default. You can also ask for
|
|
50
|
+
a specific selector to reduce re-renders.
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
const [all] = useEntity("all");
|
|
54
|
+
const [ids] = useEntity("ids");
|
|
55
|
+
const [entities] = useEntity("entities");
|
|
56
|
+
const [total] = useEntity("total");
|
|
57
|
+
const [full] = useEntity("full");
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
const [state] = useEntity();
|
|
62
|
+
const todo = state.byId("2");
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The full selector object looks like:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
{
|
|
69
|
+
all: T[];
|
|
70
|
+
ids: string[];
|
|
71
|
+
entities: Record<string, T>;
|
|
72
|
+
total: number;
|
|
73
|
+
byId: (id: string) => T | undefined;
|
|
74
|
+
}
|
|
13
75
|
```
|
|
14
76
|
|
|
15
|
-
|
|
77
|
+
## Actions
|
|
78
|
+
|
|
79
|
+
All actions mirror Redux Toolkit's entity adapter API
|
|
80
|
+
([docs](https://redux-toolkit.js.org/api/createEntityAdapter#crud-functions)):
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
addOne, addMany,
|
|
84
|
+
setOne, setMany, setAll,
|
|
85
|
+
removeOne, removeMany, removeAll,
|
|
86
|
+
updateOne, updateMany,
|
|
87
|
+
upsertOne, upsertMany
|
|
88
|
+
```
|
|
89
|
+
## Exports
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
createEntityStoreTanstack<T>(initial?: T[])
|
|
93
|
+
entityStoreFactory<T>(initial?: T[])
|
|
94
|
+
getEntityActions<T>(store, adapter)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Notes
|
|
98
|
+
|
|
99
|
+
- `T` must include `id: string`.
|
|
100
|
+
- Peer deps: `react`, `@reduxjs/toolkit`, `@tanstack/react-store`, `typescript`.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var toolkit = require('@reduxjs/toolkit');
|
|
4
|
+
var reactStore = require('@tanstack/react-store');
|
|
5
|
+
var react = require('react');
|
|
6
|
+
|
|
7
|
+
// src/actions.ts
|
|
8
|
+
var getEntityActions = (adapter, setState) => ({
|
|
9
|
+
setOne: (entity) => setState((prev) => adapter.setOne(prev, entity)),
|
|
10
|
+
setMany: (entities) => setState((prev) => adapter.setMany(prev, entities)),
|
|
11
|
+
setAll: (entities) => setState((prev) => adapter.setAll(prev, entities)),
|
|
12
|
+
addOne: (entity) => setState((prev) => adapter.addOne(prev, entity)),
|
|
13
|
+
addMany: (entities) => setState((prev) => adapter.addMany(prev, entities)),
|
|
14
|
+
removeOne: (entityId) => setState((prev) => adapter.removeOne(prev, entityId)),
|
|
15
|
+
removeMany: (entityIds) => setState((prev) => adapter.removeMany(prev, entityIds)),
|
|
16
|
+
removeAll: () => setState((prev) => adapter.removeAll(prev)),
|
|
17
|
+
updateOne: (update) => setState((prev) => adapter.updateOne(prev, update)),
|
|
18
|
+
updateMany: (updates) => setState((prev) => adapter.updateMany(prev, updates)),
|
|
19
|
+
upsertOne: (entity) => setState((prev) => adapter.upsertOne(prev, entity)),
|
|
20
|
+
upsertMany: (entities) => setState((prev) => adapter.upsertMany(prev, entities))
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// src/selectors.ts
|
|
24
|
+
var getEntityStateTanstack = (entityState, selectors) => ({
|
|
25
|
+
all: selectors.selectAll(entityState),
|
|
26
|
+
byId: (id) => selectors.selectById(entityState, id),
|
|
27
|
+
ids: selectors.selectIds(entityState),
|
|
28
|
+
entities: selectors.selectEntities(entityState),
|
|
29
|
+
total: selectors.selectTotal(entityState)
|
|
30
|
+
});
|
|
31
|
+
var noSelect = (state) => state;
|
|
32
|
+
var getSelectors = (baseSelectors) => ({
|
|
33
|
+
all: baseSelectors.selectAll,
|
|
34
|
+
entities: baseSelectors.selectEntities,
|
|
35
|
+
ids: baseSelectors.selectIds,
|
|
36
|
+
total: baseSelectors.selectTotal,
|
|
37
|
+
full: (state) => getEntityStateTanstack(state, baseSelectors)
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// src/adapter/tanstack.ts
|
|
41
|
+
var entityStoreFactory = (initialState) => {
|
|
42
|
+
const adapter = toolkit.createEntityAdapter();
|
|
43
|
+
const store = new reactStore.Store(
|
|
44
|
+
initialState ? adapter.setAll(adapter.getInitialState(), initialState) : adapter.getInitialState()
|
|
45
|
+
);
|
|
46
|
+
const actions = getEntityActions(adapter, (updater) => store.setState(updater));
|
|
47
|
+
const selectors = getSelectors(adapter.getSelectors(noSelect));
|
|
48
|
+
return { store, adapter, selectors, actions };
|
|
49
|
+
};
|
|
50
|
+
var createEntityStoreTanstack = (initialState) => {
|
|
51
|
+
const { adapter, store, actions, selectors } = entityStoreFactory(initialState);
|
|
52
|
+
function useEntity(selector = "full") {
|
|
53
|
+
const entityState = reactStore.useStore(store, selectors[selector]);
|
|
54
|
+
return [entityState, actions];
|
|
55
|
+
}
|
|
56
|
+
return { useEntity, store, actions, adapter };
|
|
57
|
+
};
|
|
58
|
+
var adapterInstance = toolkit.createEntityAdapter();
|
|
59
|
+
function useStateEntity(initialState, selector) {
|
|
60
|
+
const adapter = adapterInstance;
|
|
61
|
+
const selectorKey = selector ?? "all";
|
|
62
|
+
const [state, setState] = react.useState(
|
|
63
|
+
() => initialState ? adapter.setAll(adapter.getInitialState(), initialState instanceof Function ? initialState() : initialState) : adapter.getInitialState()
|
|
64
|
+
);
|
|
65
|
+
const actions = react.useMemo(() => getEntityActions(adapter, setState), [adapter]);
|
|
66
|
+
const data = react.useMemo(
|
|
67
|
+
() => getSelectors(adapter.getSelectors(noSelect))[selectorKey](state),
|
|
68
|
+
[state, adapter.getSelectors, selectorKey]
|
|
69
|
+
);
|
|
70
|
+
return [data, actions];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
exports.createEntityStoreTanstack = createEntityStoreTanstack;
|
|
74
|
+
exports.entityStoreFactory = entityStoreFactory;
|
|
75
|
+
exports.getEntityActions = getEntityActions;
|
|
76
|
+
exports.useStateEntity = useStateEntity;
|
|
77
|
+
//# sourceMappingURL=index.cjs.map
|
|
78
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/actions.ts","../src/selectors.ts","../src/adapter/tanstack.ts","../src/adapter/useState.ts"],"names":["createEntityAdapter","Store","useStore","useState","useMemo"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { EntityAdapter, EntityState } from \"@reduxjs/toolkit\";\nimport type { EntityStateAdapter, IdItem } from \"./types.ts\";\n\nexport const getEntityActions = <T extends IdItem>(\n\tadapter: EntityAdapter<T, T[\"id\"]>,\n\tsetState: (updater: (prev: EntityState<T, T[\"id\"]>) => EntityState<T, T[\"id\"]>) => void,\n): EntityStateAdapter<T, T[\"id\"]> => ({\n\tsetOne: (entity) => setState((prev) => adapter.setOne(prev, entity)),\n\tsetMany: (entities) => setState((prev) => adapter.setMany(prev, entities)),\n\tsetAll: (entities) => setState((prev) => adapter.setAll(prev, entities)),\n\n\taddOne: (entity) => setState((prev) => adapter.addOne(prev, entity)),\n\taddMany: (entities) => setState((prev) => adapter.addMany(prev, entities)),\n\n\tremoveOne: (entityId) => setState((prev) => adapter.removeOne(prev, entityId)),\n\tremoveMany: (entityIds) => setState((prev) => adapter.removeMany(prev, entityIds)),\n\tremoveAll: () => setState((prev) => adapter.removeAll(prev)),\n\n\tupdateOne: (update) => setState((prev) => adapter.updateOne(prev, update)),\n\tupdateMany: (updates) => setState((prev) => adapter.updateMany(prev, updates)),\n\n\tupsertOne: (entity) => setState((prev) => adapter.upsertOne(prev, entity)),\n\tupsertMany: (entities) => setState((prev) => adapter.upsertMany(prev, entities)),\n});\n","import type { EntitySelectors, EntityState } from \"@reduxjs/toolkit\";\nimport type { EntitySelectorsData, IdItem } from \"./types.ts\";\n\nconst getEntityStateTanstack = <T extends IdItem>(\n\tentityState: EntityState<T, T[\"id\"]>,\n\tselectors: EntitySelectors<T, EntityState<T, T[\"id\"]>, T[\"id\"]>,\n): EntitySelectorsData<T, T[\"id\"]> => ({\n\tall: selectors.selectAll(entityState),\n\tbyId: (id) => selectors.selectById(entityState, id),\n\tids: selectors.selectIds(entityState) as T[\"id\"][],\n\tentities: selectors.selectEntities(entityState),\n\ttotal: selectors.selectTotal(entityState),\n});\n\nexport const noSelect = <T>(state: T): T => state;\n\nexport type DataSelectors<T extends IdItem> = {\n\tall: EntitySelectors<T, EntityState<T, T[\"id\"]>, T[\"id\"]>[\"selectAll\"];\n\tentities: EntitySelectors<T, EntityState<T, T[\"id\"]>, T[\"id\"]>[\"selectEntities\"];\n\tids: EntitySelectors<T, EntityState<T, T[\"id\"]>, T[\"id\"]>[\"selectIds\"];\n\ttotal: EntitySelectors<T, EntityState<T, T[\"id\"]>, T[\"id\"]>[\"selectTotal\"];\n\tfull: (state: EntityState<T, T[\"id\"]>) => EntitySelectorsData<T, T[\"id\"]>;\n};\n\nexport const getSelectors = <T extends IdItem>(\n\tbaseSelectors: EntitySelectors<T, EntityState<T, T[\"id\"]>, T[\"id\"]>,\n): DataSelectors<T> => ({\n\tall: baseSelectors.selectAll,\n\tentities: baseSelectors.selectEntities,\n\tids: baseSelectors.selectIds,\n\ttotal: baseSelectors.selectTotal,\n\tfull: (state) => getEntityStateTanstack(state, baseSelectors),\n});\n","import { createEntityAdapter, type EntityState } from \"@reduxjs/toolkit\";\nimport { Store, useStore } from \"@tanstack/react-store\";\nimport { getEntityActions } from \"../actions.ts\";\nimport { getSelectors, noSelect } from \"../selectors.ts\";\nimport type { EntityStateAdapter, IdItem } from \"../types.ts\";\n\nexport const entityStoreFactory = <T extends IdItem>(initialState?: T[]) => {\n\tconst adapter = createEntityAdapter<T>();\n\tconst store = new Store(\n\t\tinitialState ? adapter.setAll(adapter.getInitialState(), initialState) : adapter.getInitialState(),\n\t);\n\tconst actions = getEntityActions(adapter, (updater) => store.setState(updater));\n\tconst selectors = getSelectors(adapter.getSelectors<EntityState<T, T[\"id\"]>>(noSelect));\n\treturn { store, adapter, selectors, actions };\n};\nexport const createEntityStoreTanstack = <T extends IdItem>(initialState?: T[]) => {\n\tconst { adapter, store, actions, selectors } = entityStoreFactory(initialState);\n\n\ttype SelectorKey = keyof typeof selectors;\n\ttype SelectorReturn<K extends SelectorKey> = ReturnType<(typeof selectors)[K]>;\n\n\tfunction useEntity(): [SelectorReturn<\"full\">, EntityStateAdapter<T, T[\"id\"]>];\n\tfunction useEntity(selector: \"full\"): [SelectorReturn<\"full\">, EntityStateAdapter<T, T[\"id\"]>];\n\tfunction useEntity<K extends Exclude<SelectorKey, \"full\">>(\n\t\tselector: K,\n\t): [SelectorReturn<K>, EntityStateAdapter<T, T[\"id\"]>];\n\n\tfunction useEntity<K extends SelectorKey>(selector: K = \"full\" as K) {\n\t\tconst entityState = useStore(store, selectors[selector] as () => SelectorReturn<K>);\n\n\t\treturn [entityState, actions] satisfies [SelectorReturn<K>, EntityStateAdapter<T, T[\"id\"]>];\n\t}\n\n\treturn { useEntity, store, actions, adapter };\n};\n","import { createEntityAdapter, type EntityState } from \"@reduxjs/toolkit\";\nimport { useMemo, useState } from \"react\";\nimport { getEntityActions } from \"../actions.ts\";\nimport { type DataSelectors, getSelectors, noSelect } from \"../selectors.ts\";\nimport type { EntityStateAdapter, IdItem, InitialEntityState } from \"../types.ts\";\n\n/**\n * Creates a stable adapter instance outside the hook.\n * Since adapters are stateless configuration objects, we don't need\n * to recreate them or store them in React state.\n */\n// biome-ignore lint/suspicious/noExplicitAny: fine here\nconst adapterInstance = createEntityAdapter<any>();\n\n/**\n * react useState hook integrated with an entity adapter.\n * Provides entity state and actions to manipulate that state.\n *\n * @example\n * const [] = useStateWithEntity<{ id: string; name: string }>();\n * const [] = useStateWithEntity<{ id: string; name: string }>([]);\n * const [] = useStateWithEntity<{ id: string; name: string }>([], \"total\");\n * const [] = useStateWithEntity<{ id: string; name: string }>([], \"full\");\n * const [] = useStateWithEntity([] as { id: string; name: string }[], \"full\");\n * action.addOne({ id: \"1\", name: \"Demo\" });\n */\ntype SelectorKey<T extends IdItem> = keyof DataSelectors<T>;\ntype SelectorReturn<T extends IdItem, K extends SelectorKey<T>> = ReturnType<DataSelectors<T>[K]>;\ntype SelectorOrFull<T extends IdItem, K> = K extends SelectorKey<T> ? K : \"full\";\n\ntype Return<T extends IdItem, S extends SelectorKey<T>> = [SelectorReturn<T, S>, EntityStateAdapter<T, T[\"id\"]>];\n\n// biome-ignore format: no selector => full\nexport function useStateEntity<T extends IdItem>(): Return<T, \"all\">;\n\n// biome-ignore format: initial state => full selector\nexport function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>): Return<T, \"all\">;\n\n// biome-ignore format: initial state + selector\nexport function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: \"all\"): Return<T, \"all\">;\n\n// biome-ignore format: initial state + selector\nexport function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: \"entities\"): Return<T, \"entities\">;\n\n// biome-ignore format: initial state + selector\nexport function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: \"ids\"): Return<T, \"ids\">;\n\n// biome-ignore format: initial state + selector\nexport function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: \"total\"): Return<T, \"total\">;\n\n// biome-ignore format: initial state + selector\nexport function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: \"full\"): Return<T, \"full\">;\n\n// Keep it short\nexport function useStateEntity<T extends IdItem, K extends SelectorKey<T>>(\n\tinitialState?: InitialEntityState<T>,\n\tselector?: K,\n): [SelectorReturn<T, SelectorOrFull<T, K>>, EntityStateAdapter<T, T[\"id\"]>] {\n\t// Cast the generic adapter for type safety within the hook\n\tconst adapter = adapterInstance as ReturnType<typeof createEntityAdapter<T>>;\n\tconst selectorKey = (selector ?? \"all\") as SelectorKey<T>;\n\n\tconst [state, setState] = useState(() =>\n\t\tinitialState\n\t\t\t? adapter.setAll(adapter.getInitialState(), initialState instanceof Function ? initialState() : initialState)\n\t\t\t: adapter.getInitialState(),\n\t);\n\n\tconst actions = useMemo(() => getEntityActions(adapter, setState), [adapter]);\n\n\tconst data = useMemo(\n\t\t() =>\n\t\t\tgetSelectors(adapter.getSelectors<EntityState<T, T[\"id\"]>>(noSelect))[selectorKey](state) as SelectorReturn<\n\t\t\t\tT,\n\t\t\t\tSelectorOrFull<T, K>\n\t\t\t>,\n\t\t[state, adapter.getSelectors, selectorKey],\n\t);\n\n\treturn [data, actions];\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as _reduxjs_toolkit from '@reduxjs/toolkit';
|
|
2
|
+
import { EntityId, Update, EntityAdapter, EntityState, EntitySelectors } from '@reduxjs/toolkit';
|
|
3
|
+
import { Store } from '@tanstack/react-store';
|
|
4
|
+
|
|
5
|
+
type IfMaybeUndefined<T, True, False> = [undefined] extends [T] ? True : False;
|
|
6
|
+
declare const testAccess: 0 | undefined;
|
|
7
|
+
type IfUncheckedIndexedAccess<True, False> = IfMaybeUndefined<typeof testAccess, True, False>;
|
|
8
|
+
type UncheckedIndexedAccess<T> = IfUncheckedIndexedAccess<T | undefined, T>;
|
|
9
|
+
|
|
10
|
+
interface EntityStateAdapter<T, Id extends EntityId> {
|
|
11
|
+
addOne(entity: T): void;
|
|
12
|
+
addMany(entities: readonly T[] | Record<Id, T>): void;
|
|
13
|
+
setOne(entity: T): void;
|
|
14
|
+
setMany(entities: readonly T[] | Record<Id, T>): void;
|
|
15
|
+
setAll(entities: readonly T[] | Record<Id, T>): void;
|
|
16
|
+
removeOne(key: Id): void;
|
|
17
|
+
removeMany(keys: readonly Id[]): void;
|
|
18
|
+
removeAll(): void;
|
|
19
|
+
updateOne(update: Update<T, Id>): void;
|
|
20
|
+
updateMany(updates: ReadonlyArray<Update<T, Id>>): void;
|
|
21
|
+
upsertOne(entity: T): void;
|
|
22
|
+
upsertMany(entities: readonly T[] | Record<Id, T>): void;
|
|
23
|
+
}
|
|
24
|
+
type IdItem = {
|
|
25
|
+
id: string;
|
|
26
|
+
};
|
|
27
|
+
type InitialEntityState<T extends IdItem> = T[] | Record<T["id"], T> | (() => T[] | Record<T["id"], T>) | undefined;
|
|
28
|
+
type Id<T> = {
|
|
29
|
+
[K in keyof T]: T[K];
|
|
30
|
+
} & {};
|
|
31
|
+
interface EntitySelectorsData<T, IdType extends EntityId> {
|
|
32
|
+
ids: IdType[];
|
|
33
|
+
entities: Record<IdType, T>;
|
|
34
|
+
all: T[];
|
|
35
|
+
total: number;
|
|
36
|
+
byId: (id: IdType) => Id<UncheckedIndexedAccess<T>>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
declare const getEntityActions: <T extends IdItem>(adapter: EntityAdapter<T, T["id"]>, setState: (updater: (prev: EntityState<T, T["id"]>) => EntityState<T, T["id"]>) => void) => EntityStateAdapter<T, T["id"]>;
|
|
40
|
+
|
|
41
|
+
type DataSelectors<T extends IdItem> = {
|
|
42
|
+
all: EntitySelectors<T, EntityState<T, T["id"]>, T["id"]>["selectAll"];
|
|
43
|
+
entities: EntitySelectors<T, EntityState<T, T["id"]>, T["id"]>["selectEntities"];
|
|
44
|
+
ids: EntitySelectors<T, EntityState<T, T["id"]>, T["id"]>["selectIds"];
|
|
45
|
+
total: EntitySelectors<T, EntityState<T, T["id"]>, T["id"]>["selectTotal"];
|
|
46
|
+
full: (state: EntityState<T, T["id"]>) => EntitySelectorsData<T, T["id"]>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
declare const entityStoreFactory: <T extends IdItem>(initialState?: T[]) => {
|
|
50
|
+
store: Store<EntityState<T, T["id"]>, (cb: EntityState<T, T["id"]>) => EntityState<T, T["id"]>>;
|
|
51
|
+
adapter: _reduxjs_toolkit.EntityAdapter<T, T["id"]>;
|
|
52
|
+
selectors: DataSelectors<T>;
|
|
53
|
+
actions: EntityStateAdapter<T, T["id"]>;
|
|
54
|
+
};
|
|
55
|
+
declare const createEntityStoreTanstack: <T extends IdItem>(initialState?: T[]) => {
|
|
56
|
+
useEntity: {
|
|
57
|
+
(): [EntitySelectorsData<T, T["id"]>, EntityStateAdapter<T, T["id"]>];
|
|
58
|
+
(selector: "full"): [EntitySelectorsData<T, T["id"]>, EntityStateAdapter<T, T["id"]>];
|
|
59
|
+
<K extends Exclude<keyof DataSelectors<T>, "full">>(selector: K): [ReturnType<DataSelectors<T>[K]>, EntityStateAdapter<T, T["id"]>];
|
|
60
|
+
};
|
|
61
|
+
store: Store<EntityState<T, T["id"]>, (cb: EntityState<T, T["id"]>) => EntityState<T, T["id"]>>;
|
|
62
|
+
actions: EntityStateAdapter<T, T["id"]>;
|
|
63
|
+
adapter: _reduxjs_toolkit.EntityAdapter<T, T["id"]>;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* react useState hook integrated with an entity adapter.
|
|
68
|
+
* Provides entity state and actions to manipulate that state.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* const [] = useStateWithEntity<{ id: string; name: string }>();
|
|
72
|
+
* const [] = useStateWithEntity<{ id: string; name: string }>([]);
|
|
73
|
+
* const [] = useStateWithEntity<{ id: string; name: string }>([], "total");
|
|
74
|
+
* const [] = useStateWithEntity<{ id: string; name: string }>([], "full");
|
|
75
|
+
* const [] = useStateWithEntity([] as { id: string; name: string }[], "full");
|
|
76
|
+
* action.addOne({ id: "1", name: "Demo" });
|
|
77
|
+
*/
|
|
78
|
+
type SelectorKey<T extends IdItem> = keyof DataSelectors<T>;
|
|
79
|
+
type SelectorReturn<T extends IdItem, K extends SelectorKey<T>> = ReturnType<DataSelectors<T>[K]>;
|
|
80
|
+
type Return<T extends IdItem, S extends SelectorKey<T>> = [SelectorReturn<T, S>, EntityStateAdapter<T, T["id"]>];
|
|
81
|
+
declare function useStateEntity<T extends IdItem>(): Return<T, "all">;
|
|
82
|
+
declare function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>): Return<T, "all">;
|
|
83
|
+
declare function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: "all"): Return<T, "all">;
|
|
84
|
+
declare function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: "entities"): Return<T, "entities">;
|
|
85
|
+
declare function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: "ids"): Return<T, "ids">;
|
|
86
|
+
declare function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: "total"): Return<T, "total">;
|
|
87
|
+
declare function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: "full"): Return<T, "full">;
|
|
88
|
+
|
|
89
|
+
export { createEntityStoreTanstack, entityStoreFactory, getEntityActions, useStateEntity };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as _reduxjs_toolkit from '@reduxjs/toolkit';
|
|
2
|
+
import { EntityId, Update, EntityAdapter, EntityState, EntitySelectors } from '@reduxjs/toolkit';
|
|
3
|
+
import { Store } from '@tanstack/react-store';
|
|
4
|
+
|
|
5
|
+
type IfMaybeUndefined<T, True, False> = [undefined] extends [T] ? True : False;
|
|
6
|
+
declare const testAccess: 0 | undefined;
|
|
7
|
+
type IfUncheckedIndexedAccess<True, False> = IfMaybeUndefined<typeof testAccess, True, False>;
|
|
8
|
+
type UncheckedIndexedAccess<T> = IfUncheckedIndexedAccess<T | undefined, T>;
|
|
9
|
+
|
|
10
|
+
interface EntityStateAdapter<T, Id extends EntityId> {
|
|
11
|
+
addOne(entity: T): void;
|
|
12
|
+
addMany(entities: readonly T[] | Record<Id, T>): void;
|
|
13
|
+
setOne(entity: T): void;
|
|
14
|
+
setMany(entities: readonly T[] | Record<Id, T>): void;
|
|
15
|
+
setAll(entities: readonly T[] | Record<Id, T>): void;
|
|
16
|
+
removeOne(key: Id): void;
|
|
17
|
+
removeMany(keys: readonly Id[]): void;
|
|
18
|
+
removeAll(): void;
|
|
19
|
+
updateOne(update: Update<T, Id>): void;
|
|
20
|
+
updateMany(updates: ReadonlyArray<Update<T, Id>>): void;
|
|
21
|
+
upsertOne(entity: T): void;
|
|
22
|
+
upsertMany(entities: readonly T[] | Record<Id, T>): void;
|
|
23
|
+
}
|
|
24
|
+
type IdItem = {
|
|
25
|
+
id: string;
|
|
26
|
+
};
|
|
27
|
+
type InitialEntityState<T extends IdItem> = T[] | Record<T["id"], T> | (() => T[] | Record<T["id"], T>) | undefined;
|
|
28
|
+
type Id<T> = {
|
|
29
|
+
[K in keyof T]: T[K];
|
|
30
|
+
} & {};
|
|
31
|
+
interface EntitySelectorsData<T, IdType extends EntityId> {
|
|
32
|
+
ids: IdType[];
|
|
33
|
+
entities: Record<IdType, T>;
|
|
34
|
+
all: T[];
|
|
35
|
+
total: number;
|
|
36
|
+
byId: (id: IdType) => Id<UncheckedIndexedAccess<T>>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
declare const getEntityActions: <T extends IdItem>(adapter: EntityAdapter<T, T["id"]>, setState: (updater: (prev: EntityState<T, T["id"]>) => EntityState<T, T["id"]>) => void) => EntityStateAdapter<T, T["id"]>;
|
|
40
|
+
|
|
41
|
+
type DataSelectors<T extends IdItem> = {
|
|
42
|
+
all: EntitySelectors<T, EntityState<T, T["id"]>, T["id"]>["selectAll"];
|
|
43
|
+
entities: EntitySelectors<T, EntityState<T, T["id"]>, T["id"]>["selectEntities"];
|
|
44
|
+
ids: EntitySelectors<T, EntityState<T, T["id"]>, T["id"]>["selectIds"];
|
|
45
|
+
total: EntitySelectors<T, EntityState<T, T["id"]>, T["id"]>["selectTotal"];
|
|
46
|
+
full: (state: EntityState<T, T["id"]>) => EntitySelectorsData<T, T["id"]>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
declare const entityStoreFactory: <T extends IdItem>(initialState?: T[]) => {
|
|
50
|
+
store: Store<EntityState<T, T["id"]>, (cb: EntityState<T, T["id"]>) => EntityState<T, T["id"]>>;
|
|
51
|
+
adapter: _reduxjs_toolkit.EntityAdapter<T, T["id"]>;
|
|
52
|
+
selectors: DataSelectors<T>;
|
|
53
|
+
actions: EntityStateAdapter<T, T["id"]>;
|
|
54
|
+
};
|
|
55
|
+
declare const createEntityStoreTanstack: <T extends IdItem>(initialState?: T[]) => {
|
|
56
|
+
useEntity: {
|
|
57
|
+
(): [EntitySelectorsData<T, T["id"]>, EntityStateAdapter<T, T["id"]>];
|
|
58
|
+
(selector: "full"): [EntitySelectorsData<T, T["id"]>, EntityStateAdapter<T, T["id"]>];
|
|
59
|
+
<K extends Exclude<keyof DataSelectors<T>, "full">>(selector: K): [ReturnType<DataSelectors<T>[K]>, EntityStateAdapter<T, T["id"]>];
|
|
60
|
+
};
|
|
61
|
+
store: Store<EntityState<T, T["id"]>, (cb: EntityState<T, T["id"]>) => EntityState<T, T["id"]>>;
|
|
62
|
+
actions: EntityStateAdapter<T, T["id"]>;
|
|
63
|
+
adapter: _reduxjs_toolkit.EntityAdapter<T, T["id"]>;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* react useState hook integrated with an entity adapter.
|
|
68
|
+
* Provides entity state and actions to manipulate that state.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* const [] = useStateWithEntity<{ id: string; name: string }>();
|
|
72
|
+
* const [] = useStateWithEntity<{ id: string; name: string }>([]);
|
|
73
|
+
* const [] = useStateWithEntity<{ id: string; name: string }>([], "total");
|
|
74
|
+
* const [] = useStateWithEntity<{ id: string; name: string }>([], "full");
|
|
75
|
+
* const [] = useStateWithEntity([] as { id: string; name: string }[], "full");
|
|
76
|
+
* action.addOne({ id: "1", name: "Demo" });
|
|
77
|
+
*/
|
|
78
|
+
type SelectorKey<T extends IdItem> = keyof DataSelectors<T>;
|
|
79
|
+
type SelectorReturn<T extends IdItem, K extends SelectorKey<T>> = ReturnType<DataSelectors<T>[K]>;
|
|
80
|
+
type Return<T extends IdItem, S extends SelectorKey<T>> = [SelectorReturn<T, S>, EntityStateAdapter<T, T["id"]>];
|
|
81
|
+
declare function useStateEntity<T extends IdItem>(): Return<T, "all">;
|
|
82
|
+
declare function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>): Return<T, "all">;
|
|
83
|
+
declare function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: "all"): Return<T, "all">;
|
|
84
|
+
declare function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: "entities"): Return<T, "entities">;
|
|
85
|
+
declare function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: "ids"): Return<T, "ids">;
|
|
86
|
+
declare function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: "total"): Return<T, "total">;
|
|
87
|
+
declare function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: "full"): Return<T, "full">;
|
|
88
|
+
|
|
89
|
+
export { createEntityStoreTanstack, entityStoreFactory, getEntityActions, useStateEntity };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createEntityAdapter } from '@reduxjs/toolkit';
|
|
2
|
+
import { Store, useStore } from '@tanstack/react-store';
|
|
3
|
+
import { useState, useMemo } from 'react';
|
|
4
|
+
|
|
5
|
+
// src/actions.ts
|
|
6
|
+
var getEntityActions = (adapter, setState) => ({
|
|
7
|
+
setOne: (entity) => setState((prev) => adapter.setOne(prev, entity)),
|
|
8
|
+
setMany: (entities) => setState((prev) => adapter.setMany(prev, entities)),
|
|
9
|
+
setAll: (entities) => setState((prev) => adapter.setAll(prev, entities)),
|
|
10
|
+
addOne: (entity) => setState((prev) => adapter.addOne(prev, entity)),
|
|
11
|
+
addMany: (entities) => setState((prev) => adapter.addMany(prev, entities)),
|
|
12
|
+
removeOne: (entityId) => setState((prev) => adapter.removeOne(prev, entityId)),
|
|
13
|
+
removeMany: (entityIds) => setState((prev) => adapter.removeMany(prev, entityIds)),
|
|
14
|
+
removeAll: () => setState((prev) => adapter.removeAll(prev)),
|
|
15
|
+
updateOne: (update) => setState((prev) => adapter.updateOne(prev, update)),
|
|
16
|
+
updateMany: (updates) => setState((prev) => adapter.updateMany(prev, updates)),
|
|
17
|
+
upsertOne: (entity) => setState((prev) => adapter.upsertOne(prev, entity)),
|
|
18
|
+
upsertMany: (entities) => setState((prev) => adapter.upsertMany(prev, entities))
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// src/selectors.ts
|
|
22
|
+
var getEntityStateTanstack = (entityState, selectors) => ({
|
|
23
|
+
all: selectors.selectAll(entityState),
|
|
24
|
+
byId: (id) => selectors.selectById(entityState, id),
|
|
25
|
+
ids: selectors.selectIds(entityState),
|
|
26
|
+
entities: selectors.selectEntities(entityState),
|
|
27
|
+
total: selectors.selectTotal(entityState)
|
|
28
|
+
});
|
|
29
|
+
var noSelect = (state) => state;
|
|
30
|
+
var getSelectors = (baseSelectors) => ({
|
|
31
|
+
all: baseSelectors.selectAll,
|
|
32
|
+
entities: baseSelectors.selectEntities,
|
|
33
|
+
ids: baseSelectors.selectIds,
|
|
34
|
+
total: baseSelectors.selectTotal,
|
|
35
|
+
full: (state) => getEntityStateTanstack(state, baseSelectors)
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// src/adapter/tanstack.ts
|
|
39
|
+
var entityStoreFactory = (initialState) => {
|
|
40
|
+
const adapter = createEntityAdapter();
|
|
41
|
+
const store = new Store(
|
|
42
|
+
initialState ? adapter.setAll(adapter.getInitialState(), initialState) : adapter.getInitialState()
|
|
43
|
+
);
|
|
44
|
+
const actions = getEntityActions(adapter, (updater) => store.setState(updater));
|
|
45
|
+
const selectors = getSelectors(adapter.getSelectors(noSelect));
|
|
46
|
+
return { store, adapter, selectors, actions };
|
|
47
|
+
};
|
|
48
|
+
var createEntityStoreTanstack = (initialState) => {
|
|
49
|
+
const { adapter, store, actions, selectors } = entityStoreFactory(initialState);
|
|
50
|
+
function useEntity(selector = "full") {
|
|
51
|
+
const entityState = useStore(store, selectors[selector]);
|
|
52
|
+
return [entityState, actions];
|
|
53
|
+
}
|
|
54
|
+
return { useEntity, store, actions, adapter };
|
|
55
|
+
};
|
|
56
|
+
var adapterInstance = createEntityAdapter();
|
|
57
|
+
function useStateEntity(initialState, selector) {
|
|
58
|
+
const adapter = adapterInstance;
|
|
59
|
+
const selectorKey = selector ?? "all";
|
|
60
|
+
const [state, setState] = useState(
|
|
61
|
+
() => initialState ? adapter.setAll(adapter.getInitialState(), initialState instanceof Function ? initialState() : initialState) : adapter.getInitialState()
|
|
62
|
+
);
|
|
63
|
+
const actions = useMemo(() => getEntityActions(adapter, setState), [adapter]);
|
|
64
|
+
const data = useMemo(
|
|
65
|
+
() => getSelectors(adapter.getSelectors(noSelect))[selectorKey](state),
|
|
66
|
+
[state, adapter.getSelectors, selectorKey]
|
|
67
|
+
);
|
|
68
|
+
return [data, actions];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { createEntityStoreTanstack, entityStoreFactory, getEntityActions, useStateEntity };
|
|
72
|
+
//# sourceMappingURL=index.js.map
|
|
73
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/actions.ts","../src/selectors.ts","../src/adapter/tanstack.ts","../src/adapter/useState.ts"],"names":["createEntityAdapter"],"mappings":"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","file":"index.js","sourcesContent":["import type { EntityAdapter, EntityState } from \"@reduxjs/toolkit\";\nimport type { EntityStateAdapter, IdItem } from \"./types.ts\";\n\nexport const getEntityActions = <T extends IdItem>(\n\tadapter: EntityAdapter<T, T[\"id\"]>,\n\tsetState: (updater: (prev: EntityState<T, T[\"id\"]>) => EntityState<T, T[\"id\"]>) => void,\n): EntityStateAdapter<T, T[\"id\"]> => ({\n\tsetOne: (entity) => setState((prev) => adapter.setOne(prev, entity)),\n\tsetMany: (entities) => setState((prev) => adapter.setMany(prev, entities)),\n\tsetAll: (entities) => setState((prev) => adapter.setAll(prev, entities)),\n\n\taddOne: (entity) => setState((prev) => adapter.addOne(prev, entity)),\n\taddMany: (entities) => setState((prev) => adapter.addMany(prev, entities)),\n\n\tremoveOne: (entityId) => setState((prev) => adapter.removeOne(prev, entityId)),\n\tremoveMany: (entityIds) => setState((prev) => adapter.removeMany(prev, entityIds)),\n\tremoveAll: () => setState((prev) => adapter.removeAll(prev)),\n\n\tupdateOne: (update) => setState((prev) => adapter.updateOne(prev, update)),\n\tupdateMany: (updates) => setState((prev) => adapter.updateMany(prev, updates)),\n\n\tupsertOne: (entity) => setState((prev) => adapter.upsertOne(prev, entity)),\n\tupsertMany: (entities) => setState((prev) => adapter.upsertMany(prev, entities)),\n});\n","import type { EntitySelectors, EntityState } from \"@reduxjs/toolkit\";\nimport type { EntitySelectorsData, IdItem } from \"./types.ts\";\n\nconst getEntityStateTanstack = <T extends IdItem>(\n\tentityState: EntityState<T, T[\"id\"]>,\n\tselectors: EntitySelectors<T, EntityState<T, T[\"id\"]>, T[\"id\"]>,\n): EntitySelectorsData<T, T[\"id\"]> => ({\n\tall: selectors.selectAll(entityState),\n\tbyId: (id) => selectors.selectById(entityState, id),\n\tids: selectors.selectIds(entityState) as T[\"id\"][],\n\tentities: selectors.selectEntities(entityState),\n\ttotal: selectors.selectTotal(entityState),\n});\n\nexport const noSelect = <T>(state: T): T => state;\n\nexport type DataSelectors<T extends IdItem> = {\n\tall: EntitySelectors<T, EntityState<T, T[\"id\"]>, T[\"id\"]>[\"selectAll\"];\n\tentities: EntitySelectors<T, EntityState<T, T[\"id\"]>, T[\"id\"]>[\"selectEntities\"];\n\tids: EntitySelectors<T, EntityState<T, T[\"id\"]>, T[\"id\"]>[\"selectIds\"];\n\ttotal: EntitySelectors<T, EntityState<T, T[\"id\"]>, T[\"id\"]>[\"selectTotal\"];\n\tfull: (state: EntityState<T, T[\"id\"]>) => EntitySelectorsData<T, T[\"id\"]>;\n};\n\nexport const getSelectors = <T extends IdItem>(\n\tbaseSelectors: EntitySelectors<T, EntityState<T, T[\"id\"]>, T[\"id\"]>,\n): DataSelectors<T> => ({\n\tall: baseSelectors.selectAll,\n\tentities: baseSelectors.selectEntities,\n\tids: baseSelectors.selectIds,\n\ttotal: baseSelectors.selectTotal,\n\tfull: (state) => getEntityStateTanstack(state, baseSelectors),\n});\n","import { createEntityAdapter, type EntityState } from \"@reduxjs/toolkit\";\nimport { Store, useStore } from \"@tanstack/react-store\";\nimport { getEntityActions } from \"../actions.ts\";\nimport { getSelectors, noSelect } from \"../selectors.ts\";\nimport type { EntityStateAdapter, IdItem } from \"../types.ts\";\n\nexport const entityStoreFactory = <T extends IdItem>(initialState?: T[]) => {\n\tconst adapter = createEntityAdapter<T>();\n\tconst store = new Store(\n\t\tinitialState ? adapter.setAll(adapter.getInitialState(), initialState) : adapter.getInitialState(),\n\t);\n\tconst actions = getEntityActions(adapter, (updater) => store.setState(updater));\n\tconst selectors = getSelectors(adapter.getSelectors<EntityState<T, T[\"id\"]>>(noSelect));\n\treturn { store, adapter, selectors, actions };\n};\nexport const createEntityStoreTanstack = <T extends IdItem>(initialState?: T[]) => {\n\tconst { adapter, store, actions, selectors } = entityStoreFactory(initialState);\n\n\ttype SelectorKey = keyof typeof selectors;\n\ttype SelectorReturn<K extends SelectorKey> = ReturnType<(typeof selectors)[K]>;\n\n\tfunction useEntity(): [SelectorReturn<\"full\">, EntityStateAdapter<T, T[\"id\"]>];\n\tfunction useEntity(selector: \"full\"): [SelectorReturn<\"full\">, EntityStateAdapter<T, T[\"id\"]>];\n\tfunction useEntity<K extends Exclude<SelectorKey, \"full\">>(\n\t\tselector: K,\n\t): [SelectorReturn<K>, EntityStateAdapter<T, T[\"id\"]>];\n\n\tfunction useEntity<K extends SelectorKey>(selector: K = \"full\" as K) {\n\t\tconst entityState = useStore(store, selectors[selector] as () => SelectorReturn<K>);\n\n\t\treturn [entityState, actions] satisfies [SelectorReturn<K>, EntityStateAdapter<T, T[\"id\"]>];\n\t}\n\n\treturn { useEntity, store, actions, adapter };\n};\n","import { createEntityAdapter, type EntityState } from \"@reduxjs/toolkit\";\nimport { useMemo, useState } from \"react\";\nimport { getEntityActions } from \"../actions.ts\";\nimport { type DataSelectors, getSelectors, noSelect } from \"../selectors.ts\";\nimport type { EntityStateAdapter, IdItem, InitialEntityState } from \"../types.ts\";\n\n/**\n * Creates a stable adapter instance outside the hook.\n * Since adapters are stateless configuration objects, we don't need\n * to recreate them or store them in React state.\n */\n// biome-ignore lint/suspicious/noExplicitAny: fine here\nconst adapterInstance = createEntityAdapter<any>();\n\n/**\n * react useState hook integrated with an entity adapter.\n * Provides entity state and actions to manipulate that state.\n *\n * @example\n * const [] = useStateWithEntity<{ id: string; name: string }>();\n * const [] = useStateWithEntity<{ id: string; name: string }>([]);\n * const [] = useStateWithEntity<{ id: string; name: string }>([], \"total\");\n * const [] = useStateWithEntity<{ id: string; name: string }>([], \"full\");\n * const [] = useStateWithEntity([] as { id: string; name: string }[], \"full\");\n * action.addOne({ id: \"1\", name: \"Demo\" });\n */\ntype SelectorKey<T extends IdItem> = keyof DataSelectors<T>;\ntype SelectorReturn<T extends IdItem, K extends SelectorKey<T>> = ReturnType<DataSelectors<T>[K]>;\ntype SelectorOrFull<T extends IdItem, K> = K extends SelectorKey<T> ? K : \"full\";\n\ntype Return<T extends IdItem, S extends SelectorKey<T>> = [SelectorReturn<T, S>, EntityStateAdapter<T, T[\"id\"]>];\n\n// biome-ignore format: no selector => full\nexport function useStateEntity<T extends IdItem>(): Return<T, \"all\">;\n\n// biome-ignore format: initial state => full selector\nexport function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>): Return<T, \"all\">;\n\n// biome-ignore format: initial state + selector\nexport function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: \"all\"): Return<T, \"all\">;\n\n// biome-ignore format: initial state + selector\nexport function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: \"entities\"): Return<T, \"entities\">;\n\n// biome-ignore format: initial state + selector\nexport function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: \"ids\"): Return<T, \"ids\">;\n\n// biome-ignore format: initial state + selector\nexport function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: \"total\"): Return<T, \"total\">;\n\n// biome-ignore format: initial state + selector\nexport function useStateEntity<T extends IdItem>(initialState: InitialEntityState<T>, selector: \"full\"): Return<T, \"full\">;\n\n// Keep it short\nexport function useStateEntity<T extends IdItem, K extends SelectorKey<T>>(\n\tinitialState?: InitialEntityState<T>,\n\tselector?: K,\n): [SelectorReturn<T, SelectorOrFull<T, K>>, EntityStateAdapter<T, T[\"id\"]>] {\n\t// Cast the generic adapter for type safety within the hook\n\tconst adapter = adapterInstance as ReturnType<typeof createEntityAdapter<T>>;\n\tconst selectorKey = (selector ?? \"all\") as SelectorKey<T>;\n\n\tconst [state, setState] = useState(() =>\n\t\tinitialState\n\t\t\t? adapter.setAll(adapter.getInitialState(), initialState instanceof Function ? initialState() : initialState)\n\t\t\t: adapter.getInitialState(),\n\t);\n\n\tconst actions = useMemo(() => getEntityActions(adapter, setState), [adapter]);\n\n\tconst data = useMemo(\n\t\t() =>\n\t\t\tgetSelectors(adapter.getSelectors<EntityState<T, T[\"id\"]>>(noSelect))[selectorKey](state) as SelectorReturn<\n\t\t\t\tT,\n\t\t\t\tSelectorOrFull<T, K>\n\t\t\t>,\n\t\t[state, adapter.getSelectors, selectorKey],\n\t);\n\n\treturn [data, actions];\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "use-entity",
|
|
3
|
-
"
|
|
3
|
+
"version": "0.1.0-alpha",
|
|
4
|
+
"main": "dist/index.cjs",
|
|
5
|
+
"module": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"sideEffects": false,
|
|
4
18
|
"type": "module",
|
|
5
|
-
"version": "0.0.1-alpha",
|
|
6
19
|
"scripts": {
|
|
7
20
|
"biome:check": "biome check",
|
|
8
21
|
"biome:fix": "biome check --write --unsafe",
|
|
9
22
|
"biome:format": "biome format --write",
|
|
10
23
|
"test": "bun test",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"build:dev": "tsup --watch --clean false",
|
|
26
|
+
"typecheck": "tsc"
|
|
13
27
|
},
|
|
14
28
|
"devDependencies": {
|
|
15
29
|
"@biomejs/biome": "2.3.11",
|
|
@@ -18,12 +32,13 @@
|
|
|
18
32
|
"@tanstack/react-store": "0.8.0",
|
|
19
33
|
"@testing-library/dom": "10.4.1",
|
|
20
34
|
"@testing-library/jest-dom": "6.9.1",
|
|
21
|
-
"@testing-library/react": "16.3.
|
|
35
|
+
"@testing-library/react": "16.3.2",
|
|
22
36
|
"@types/bun": "latest",
|
|
23
|
-
"@types/react": "19.2.
|
|
37
|
+
"@types/react": "19.2.9",
|
|
24
38
|
"@types/react-dom": "19.2.3",
|
|
25
39
|
"lefthook": "2.0.15",
|
|
26
|
-
"react": "^19.2.3"
|
|
40
|
+
"react": "^19.2.3",
|
|
41
|
+
"tsup": "8.5.1"
|
|
27
42
|
},
|
|
28
43
|
"peerDependencies": {
|
|
29
44
|
"typescript": "^5",
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
name: release-please
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- "**"
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
release-please:
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
timeout-minutes: 5
|
|
12
|
-
env:
|
|
13
|
-
NODE_ENV: production
|
|
14
|
-
CI: true
|
|
15
|
-
|
|
16
|
-
steps:
|
|
17
|
-
- uses: googleapis/release-please-action@v4
|
|
18
|
-
id: release
|
|
19
|
-
with:
|
|
20
|
-
token: ${{ secrets.RELEASE_PLEASE_GH_TOKEN }}
|
|
21
|
-
release-type: node
|
|
22
|
-
|
|
23
|
-
- uses: actions/checkout@v6
|
|
24
|
-
|
|
25
|
-
- name: Set up Bun
|
|
26
|
-
uses: oven-sh/setup-bun@v2
|
|
27
|
-
with:
|
|
28
|
-
bun-version: "latest"
|
|
29
|
-
|
|
30
|
-
- name: Install dependencies
|
|
31
|
-
run: bun install
|
|
32
|
-
|
|
33
|
-
- name: Run typecheck
|
|
34
|
-
run: bun run typecheck
|
|
35
|
-
|
|
36
|
-
- name: Run lint
|
|
37
|
-
run: bun run biome:check
|
|
38
|
-
|
|
39
|
-
- name: Run test
|
|
40
|
-
run: bun test
|
|
41
|
-
|
|
42
|
-
- name: Run build
|
|
43
|
-
run: bun run build
|
|
44
|
-
|
|
45
|
-
- run: bunx pkg-pr-new publish
|
|
46
|
-
continue-on-error: true
|
|
47
|
-
|
|
48
|
-
- name: Publish to npm
|
|
49
|
-
run: bun publish --access public
|
|
50
|
-
env:
|
|
51
|
-
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
52
|
-
if: ${{ steps.release.outputs.release_created }}
|