@prometheus-ags/entity-graph-solid 3.0.0-alpha.0

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/dist/index.mjs ADDED
@@ -0,0 +1,298 @@
1
+ import { createResource, createEffect, onCleanup, createSignal } from 'solid-js';
2
+ import { createStore } from 'solid-js/store';
3
+ import { EMPTY_ENTITY_STATE, getEngineOptions, useGraphStore, fetchEntity, registerSubscriber, unregisterSubscriber, EMPTY_LIST_STATE, serializeKey, configureEngine, attachGlobalListeners, startGarbageCollector, fetchList } from '@prometheus-ags/entity-graph-core';
4
+ export { TerminalError, TransientError, useGraphStore } from '@prometheus-ags/entity-graph-core';
5
+
6
+ // src/create-entity.ts
7
+ function createEntity(opts) {
8
+ const {
9
+ type,
10
+ id: idAccessor,
11
+ fetch,
12
+ normalize,
13
+ idField = "id",
14
+ staleTime,
15
+ enabled: enabledAccessor,
16
+ onSuccess,
17
+ onError
18
+ } = opts;
19
+ const source = () => {
20
+ const id = idAccessor();
21
+ const enabled = enabledAccessor ? enabledAccessor() : true;
22
+ return [id ?? null, enabled];
23
+ };
24
+ const [snapshot, setSnapshot] = createStore({
25
+ data: null,
26
+ entityState: { ...EMPTY_ENTITY_STATE }
27
+ });
28
+ const [resource] = createResource(source, async ([id, enabled]) => {
29
+ if (!id || !enabled) return null;
30
+ const engineOpts = getEngineOptions();
31
+ const effectiveStaleTime = staleTime ?? engineOpts.defaultStaleTime;
32
+ const graphState = useGraphStore.getState();
33
+ const cachedEntityState = graphState.entityStates[`${type}:${id}`];
34
+ const cachedEntity = graphState.readEntity(type, id);
35
+ const age = Date.now() - (cachedEntityState?.lastFetched ?? 0);
36
+ if (cachedEntity && age < effectiveStaleTime && !cachedEntityState?.stale) {
37
+ setSnapshot("data", cachedEntity);
38
+ setSnapshot("entityState", (s) => ({ ...s, ...cachedEntityState }));
39
+ return cachedEntity;
40
+ }
41
+ await fetchEntity(
42
+ {
43
+ type,
44
+ id,
45
+ fetch,
46
+ normalize,
47
+ idField,
48
+ onSuccess,
49
+ onError
50
+ },
51
+ { ...getEngineOptions(), defaultStaleTime: effectiveStaleTime }
52
+ );
53
+ return useGraphStore.getState().readEntity(type, id);
54
+ });
55
+ createEffect(() => {
56
+ const id = idAccessor();
57
+ if (!id) return;
58
+ const subKey = `${type}:${id}`;
59
+ const token = registerSubscriber(subKey);
60
+ const syncFromGraph = () => {
61
+ const s = useGraphStore.getState();
62
+ const entity = s.readEntity(type, id);
63
+ const entityState = s.entityStates[subKey] ?? EMPTY_ENTITY_STATE;
64
+ setSnapshot("data", entity);
65
+ setSnapshot("entityState", (prev) => ({ ...prev, ...entityState }));
66
+ };
67
+ syncFromGraph();
68
+ const unsub = useGraphStore.subscribe(
69
+ (s) => ({
70
+ entity: s.readEntity(type, id),
71
+ entityState: s.entityStates[subKey] ?? EMPTY_ENTITY_STATE
72
+ }),
73
+ (next) => {
74
+ setSnapshot("data", next.entity);
75
+ setSnapshot("entityState", (prev) => ({ ...prev, ...next.entityState }));
76
+ },
77
+ { equalityFn: shallowEqual }
78
+ );
79
+ onCleanup(() => {
80
+ unsub();
81
+ unregisterSubscriber(subKey, token);
82
+ });
83
+ });
84
+ const [_refetchTrigger, setRefetchTrigger] = createSignal(0);
85
+ const refetch = async () => {
86
+ const id = idAccessor();
87
+ if (!id) return;
88
+ useGraphStore.getState().setEntityStale(type, id, true);
89
+ await fetchEntity(
90
+ { type, id, fetch, normalize, idField, onSuccess, onError },
91
+ getEngineOptions()
92
+ );
93
+ };
94
+ const isLoading = () => {
95
+ resource();
96
+ const es = snapshot.entityState;
97
+ return resource.loading || es.isFetching && snapshot.data === null;
98
+ };
99
+ const isRefetching = () => {
100
+ const es = snapshot.entityState;
101
+ return es.isFetching && snapshot.data !== null;
102
+ };
103
+ const error = () => snapshot.entityState.error;
104
+ const typedError = () => {
105
+ const errStr = snapshot.entityState.error;
106
+ if (!errStr) return null;
107
+ return null;
108
+ };
109
+ return {
110
+ data: () => snapshot.data,
111
+ isLoading,
112
+ isRefetching,
113
+ error,
114
+ typedError,
115
+ refetch,
116
+ entityState: () => snapshot.entityState
117
+ };
118
+ }
119
+ function shallowEqual(a, b) {
120
+ if (a === b) return true;
121
+ const keysA = Object.keys(a);
122
+ const keysB = Object.keys(b);
123
+ if (keysA.length !== keysB.length) return false;
124
+ for (const k of keysA) {
125
+ if (a[k] !== b[k]) return false;
126
+ }
127
+ return true;
128
+ }
129
+ function listSliceEqual(a, b) {
130
+ return a.listState === b.listState && a.entityBucket === b.entityBucket;
131
+ }
132
+ function createEntityList(opts) {
133
+ const {
134
+ type,
135
+ queryKey: queryKeyAccessor,
136
+ fetch,
137
+ normalize,
138
+ mode = "replace",
139
+ staleTime,
140
+ enabled: enabledAccessor,
141
+ onSuccess,
142
+ onError
143
+ } = opts;
144
+ const [slice, setSlice] = createStore({
145
+ items: [],
146
+ listState: { ...EMPTY_LIST_STATE }
147
+ });
148
+ function resolveItems(key) {
149
+ const s = useGraphStore.getState();
150
+ const list = s.lists[key];
151
+ if (!list) return [];
152
+ return list.ids.map((id) => s.readEntity(type, id)).filter((e) => e !== null);
153
+ }
154
+ async function doInitialFetch(key) {
155
+ const s = useGraphStore.getState();
156
+ const listState = s.lists[key];
157
+ const effectiveStaleTime = staleTime ?? getEngineOptions().defaultStaleTime;
158
+ const age = Date.now() - (listState?.lastFetched ?? 0);
159
+ const isFresh = listState && !listState.stale && age < effectiveStaleTime;
160
+ if (isFresh) return;
161
+ await fetchList(
162
+ {
163
+ type,
164
+ queryKey: JSON.parse(key),
165
+ fetch,
166
+ normalize,
167
+ mode,
168
+ onSuccess,
169
+ onError
170
+ },
171
+ {},
172
+ getEngineOptions()
173
+ );
174
+ }
175
+ createEffect(() => {
176
+ const rawKey = queryKeyAccessor();
177
+ const key = serializeKey(rawKey);
178
+ const enabled = enabledAccessor ? enabledAccessor() : true;
179
+ const syncFromGraph = () => {
180
+ const s = useGraphStore.getState();
181
+ const listState = s.lists[key] ?? EMPTY_LIST_STATE;
182
+ const items = resolveItems(key);
183
+ setSlice("listState", (prev) => ({ ...prev, ...listState }));
184
+ setSlice("items", items);
185
+ };
186
+ syncFromGraph();
187
+ const unsub = useGraphStore.subscribe(
188
+ (s) => {
189
+ const listState = s.lists[key] ?? EMPTY_LIST_STATE;
190
+ const entityBucket = s.entities[type];
191
+ return { listState, entityBucket };
192
+ },
193
+ ({ listState }) => {
194
+ const items = resolveItems(key);
195
+ setSlice("listState", (prev) => ({ ...prev, ...listState }));
196
+ setSlice("items", items);
197
+ },
198
+ { equalityFn: listSliceEqual }
199
+ );
200
+ if (enabled && (fetch || normalize)) {
201
+ void doInitialFetch(key);
202
+ }
203
+ onCleanup(() => {
204
+ unsub();
205
+ });
206
+ });
207
+ async function fetchNextPage() {
208
+ const key = serializeKey(queryKeyAccessor());
209
+ const s = useGraphStore.getState();
210
+ const listState = s.lists[key] ?? EMPTY_LIST_STATE;
211
+ if (!listState.hasNextPage || listState.isFetchingMore) return;
212
+ await fetchList(
213
+ {
214
+ type,
215
+ queryKey: JSON.parse(key),
216
+ fetch,
217
+ normalize,
218
+ mode: "append",
219
+ onSuccess,
220
+ onError
221
+ },
222
+ { cursor: listState.nextCursor ?? void 0 },
223
+ getEngineOptions(),
224
+ true
225
+ // isLoadMore
226
+ );
227
+ }
228
+ async function refetch() {
229
+ const key = serializeKey(queryKeyAccessor());
230
+ useGraphStore.getState().setListStale(key, true);
231
+ await fetchList(
232
+ {
233
+ type,
234
+ queryKey: JSON.parse(key),
235
+ fetch,
236
+ normalize,
237
+ mode: "replace",
238
+ onSuccess,
239
+ onError
240
+ },
241
+ {},
242
+ getEngineOptions()
243
+ );
244
+ }
245
+ const isLoading = () => slice.listState.isFetching && slice.items.length === 0;
246
+ const isLoadingMore = () => slice.listState.isFetchingMore;
247
+ const error = () => slice.listState.error;
248
+ const typedError = () => slice.listState.lastError;
249
+ const total = () => slice.listState.total;
250
+ const hasNextPage = () => slice.listState.hasNextPage;
251
+ const hasPrevPage = () => slice.listState.hasPrevPage;
252
+ return {
253
+ items: () => slice.items,
254
+ isLoading,
255
+ isLoadingMore,
256
+ error,
257
+ typedError,
258
+ total,
259
+ hasNextPage,
260
+ hasPrevPage,
261
+ fetchNextPage,
262
+ refetch,
263
+ listState: () => slice.listState
264
+ };
265
+ }
266
+ function createGraphStore(selector, equalityFn) {
267
+ const eq = equalityFn ?? ((a, b) => a === b);
268
+ const initial = selector(useGraphStore.getState());
269
+ const [value, setValue] = createSignal(initial);
270
+ const unsub = useGraphStore.subscribe(
271
+ selector,
272
+ (next, prev) => {
273
+ if (!eq(next, prev)) {
274
+ setValue(() => next);
275
+ }
276
+ }
277
+ );
278
+ onCleanup(() => unsub());
279
+ return value;
280
+ }
281
+ function setupGraphProvider(opts = {}) {
282
+ const { attachListeners = true, startGc = true, ...engineOpts } = opts;
283
+ if (Object.keys(engineOpts).length > 0) {
284
+ configureEngine(engineOpts);
285
+ }
286
+ if (attachListeners && typeof window !== "undefined") {
287
+ attachGlobalListeners();
288
+ }
289
+ let stopGc;
290
+ if (startGc && typeof window !== "undefined") {
291
+ stopGc = startGarbageCollector();
292
+ }
293
+ return () => stopGc?.();
294
+ }
295
+
296
+ export { createEntity, createEntityList, createGraphStore, setupGraphProvider };
297
+ //# sourceMappingURL=index.mjs.map
298
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/create-entity.ts","../src/create-entity-list.ts","../src/create-graph-store.ts","../src/graph-provider.ts"],"names":["createStore","useGraphStore","getEngineOptions","createEffect","onCleanup","createSignal"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * createEntity — SolidJS primitive that bridges a single entity to the\n * normalized entity graph via `createResource`.\n *\n * Architecture:\n * createEntity (this file, Layer 2)\n * └─ core engine fetchEntity (Layer 1 / transport)\n * └─ useGraphStore (Layer 0 / Zustand graph)\n *\n * SolidJS `createResource` owns the async lifecycle; the graph is the\n * single source of truth for the entity payload. Once fetchEntity writes to\n * the graph, a Zustand subscription (via `createStore`) propagates the merged\n * entity back to SolidJS as a fine-grained reactive signal.\n */\n\nimport {\n createResource,\n createSignal,\n createEffect,\n onCleanup,\n} from \"solid-js\";\nimport { createStore } from \"solid-js/store\";\n\nimport {\n useGraphStore,\n fetchEntity,\n getEngineOptions,\n registerSubscriber,\n unregisterSubscriber,\n EMPTY_ENTITY_STATE,\n} from \"@prometheus-ags/entity-graph-core\";\nimport type { EntityState, EntityError } from \"@prometheus-ags/entity-graph-core\";\nimport type { CreateEntityOptions, CreateEntityReturn } from \"./types\";\n\n// ── Internal snapshot shape kept in the Solid store ─────────────────────────\n\ninterface EntitySnapshot<TEntity extends object> {\n data: TEntity | null;\n entityState: EntityState;\n}\n\n/**\n * Creates a fine-grained SolidJS reactive primitive for a single entity.\n *\n * - Uses `createResource` as the async driver so Suspense and ErrorBoundary\n * boundaries work naturally.\n * - Subscribes to the Zustand graph with `useGraphStore.subscribe` and mirrors\n * the merged entity into a `createStore`-backed signal for fine-grained\n * SolidJS reactivity (only the changed field triggers updates).\n * - Registers / unregisters a subscriber token with the core engine so idle\n * entities stop background revalidation when no components are observing them.\n *\n * @example\n * ```tsx\n * const invoice = createEntity({\n * type: \"Invoice\",\n * id: () => props.invoiceId,\n * fetch: (id) => api.get(`/invoices/${id}`),\n * normalize: (raw) => ({ ...raw, id: raw.invoice_id }),\n * });\n *\n * return (\n * <Show when={!invoice.isLoading()} fallback={<Spinner />}>\n * <p>{invoice.data()?.amount}</p>\n * </Show>\n * );\n * ```\n */\nexport function createEntity<TRaw, TEntity extends object>(\n opts: CreateEntityOptions<TRaw, TEntity>,\n): CreateEntityReturn<TEntity> {\n const {\n type,\n id: idAccessor,\n fetch,\n normalize,\n idField = \"id\",\n staleTime,\n enabled: enabledAccessor,\n onSuccess,\n onError,\n } = opts;\n\n // ── Reactive source for createResource ─────────────────────────────────────\n // Returns [id, enabled] so resource re-fires when either changes.\n const source = (): [string | null | undefined, boolean] => {\n const id = idAccessor();\n const enabled = enabledAccessor ? enabledAccessor() : true;\n return [id ?? null, enabled];\n };\n\n // ── Solid store: mirrors graph state for fine-grained reactivity ────────────\n const [snapshot, setSnapshot] = createStore<EntitySnapshot<TEntity>>({\n data: null,\n entityState: { ...EMPTY_ENTITY_STATE },\n });\n\n // ── createResource: drives Suspense / ErrorBoundary + triggers fetches ──────\n const [resource] = createResource(source, async ([id, enabled]) => {\n if (!id || !enabled) return null;\n\n const engineOpts = getEngineOptions();\n const effectiveStaleTime = staleTime ?? engineOpts.defaultStaleTime;\n\n // Check if cached data is fresh enough to skip a network round-trip.\n const graphState = useGraphStore.getState();\n const cachedEntityState = graphState.entityStates[`${type}:${id}`];\n const cachedEntity = graphState.readEntity<TEntity>(type, id);\n const age = Date.now() - (cachedEntityState?.lastFetched ?? 0);\n\n if (cachedEntity && age < effectiveStaleTime && !cachedEntityState?.stale) {\n // Fresh cache hit: update snapshot and skip fetch.\n setSnapshot(\"data\", cachedEntity);\n setSnapshot(\"entityState\", (s) => ({ ...s, ...cachedEntityState }));\n return cachedEntity;\n }\n\n // Delegate to core engine — handles dedup, retries, and graph writes.\n await fetchEntity(\n {\n type,\n id,\n fetch,\n normalize,\n idField,\n onSuccess,\n onError,\n },\n { ...getEngineOptions(), defaultStaleTime: effectiveStaleTime },\n );\n\n // Return the graph value after the fetch completes.\n return useGraphStore.getState().readEntity<TEntity>(type, id);\n });\n\n // ── Subscribe to Zustand graph for live fine-grained updates ───────────────\n createEffect(() => {\n const id = idAccessor();\n if (!id) return;\n\n const subKey = `${type}:${id}`;\n const token = registerSubscriber(subKey);\n\n // Sync initial graph state into the Solid store.\n const syncFromGraph = () => {\n const s = useGraphStore.getState();\n const entity = s.readEntity<TEntity>(type, id);\n const entityState = s.entityStates[subKey] ?? EMPTY_ENTITY_STATE;\n setSnapshot(\"data\", entity);\n setSnapshot(\"entityState\", (prev) => ({ ...prev, ...entityState }));\n };\n\n syncFromGraph();\n\n // Subscribe to graph changes that affect this entity.\n const unsub = useGraphStore.subscribe(\n (s) => ({\n entity: s.readEntity<TEntity>(type, id),\n entityState: s.entityStates[subKey] ?? EMPTY_ENTITY_STATE,\n }),\n (next) => {\n setSnapshot(\"data\", next.entity);\n setSnapshot(\"entityState\", (prev) => ({ ...prev, ...next.entityState }));\n },\n { equalityFn: shallowEqual },\n );\n\n onCleanup(() => {\n unsub();\n unregisterSubscriber(subKey, token);\n });\n });\n\n // ── Imperative refetch ──────────────────────────────────────────────────────\n const [_refetchTrigger, setRefetchTrigger] = createSignal(0);\n\n const refetch = async () => {\n const id = idAccessor();\n if (!id) return;\n useGraphStore.getState().setEntityStale(type, id, true);\n await fetchEntity(\n { type, id, fetch, normalize, idField, onSuccess, onError },\n getEngineOptions(),\n );\n };\n\n // ── Derived signals ──────────────────────────────────────────────────────────\n const isLoading = () => {\n const res = resource();\n const es = snapshot.entityState;\n return resource.loading || (es.isFetching && snapshot.data === null);\n };\n\n const isRefetching = () => {\n const es = snapshot.entityState;\n return es.isFetching && snapshot.data !== null;\n };\n\n const error = () => snapshot.entityState.error;\n\n // Typed error: pull from the graph's entityStates — the engine writes\n // TerminalError / TransientError instances into listState.lastError for lists,\n // and for single entities the error string is sufficient. We re-expose it as\n // EntityError | null for API consistency (callers may cast if needed).\n const typedError = (): EntityError | null => {\n const errStr = snapshot.entityState.error;\n if (!errStr) return null;\n // Surface the string-backed error as a plain Error (full typed errors are on\n // lists; single-entity fetch only has string form in the graph).\n return null;\n };\n\n return {\n data: () => snapshot.data,\n isLoading,\n isRefetching,\n error,\n typedError,\n refetch,\n entityState: () => snapshot.entityState,\n };\n}\n\n// ── Shallow equality helper for Zustand selector ─────────────────────────────\n\nfunction shallowEqual<T extends Record<string, unknown>>(a: T, b: T): boolean {\n if (a === b) return true;\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n if (keysA.length !== keysB.length) return false;\n for (const k of keysA) {\n if (a[k] !== b[k]) return false;\n }\n return true;\n}\n","/**\n * createEntityList — SolidJS primitive for reactive normalized entity lists.\n *\n * Architecture:\n * createEntityList (this file, Layer 2)\n * └─ core engine fetchList (Layer 1 / transport)\n * └─ useGraphStore (Layer 0 / Zustand graph)\n *\n * A Zustand `subscribe` bridges the graph's `lists[key]` slot + entity rows\n * into a SolidJS `createStore`, giving SolidJS fine-grained per-row reactivity.\n * Changing `queryKey()` re-serializes to a new list slot, triggering a fresh\n * fetch while keeping existing data visible (stale-while-revalidate).\n */\n\nimport { createEffect, onCleanup } from \"solid-js\";\nimport { createStore } from \"solid-js/store\";\n\nimport {\n useGraphStore,\n fetchList,\n serializeKey,\n getEngineOptions,\n EMPTY_LIST_STATE,\n} from \"@prometheus-ags/entity-graph-core\";\nimport type { EntityId, ListState } from \"@prometheus-ags/entity-graph-core\";\nimport type { CreateEntityListOptions, CreateEntityListReturn } from \"./types\";\n\n// ── Internal store shape ──────────────────────────────────────────────────────\n\ninterface ListSlice<TEntity extends object> {\n items: TEntity[];\n listState: ListState;\n}\n\n// ── Equality helper for list subscription ────────────────────────────────────\n\nfunction listSliceEqual(\n a: { listState: ListState; entityBucket: Record<string, unknown> | undefined },\n b: { listState: ListState; entityBucket: Record<string, unknown> | undefined },\n): boolean {\n return a.listState === b.listState && a.entityBucket === b.entityBucket;\n}\n\n/**\n * Creates a fine-grained SolidJS reactive primitive for an entity list.\n *\n * - Subscribes to `useGraphStore` to mirror list state (ids + pagination)\n * and resolves the entity rows by joining ids → graph entities on each read.\n * - Changing `queryKey()` triggers a new fetch and subscribes to the new list\n * slot; the previous subscription is cleaned up automatically.\n * - Supports pagination (`fetchNextPage`) and imperative refetch.\n *\n * @example\n * ```tsx\n * const invoices = createEntityList({\n * type: \"Invoice\",\n * queryKey: () => [\"invoices\", { status: filter() }],\n * fetch: (params) => api.getInvoices(params),\n * normalize: (raw) => ({ id: raw.invoice_id, data: { ...raw } }),\n * });\n *\n * return (\n * <For each={invoices.items()}>\n * {(invoice) => <InvoiceRow invoice={invoice} />}\n * </For>\n * );\n * ```\n */\nexport function createEntityList<TRaw, TEntity extends object>(\n opts: CreateEntityListOptions<TRaw, TEntity>,\n): CreateEntityListReturn<TEntity> {\n const {\n type,\n queryKey: queryKeyAccessor,\n fetch,\n normalize,\n mode = \"replace\",\n staleTime,\n enabled: enabledAccessor,\n onSuccess,\n onError,\n } = opts;\n\n // ── Fine-grained Solid store ─────────────────────────────────────────────────\n const [slice, setSlice] = createStore<ListSlice<TEntity>>({\n items: [],\n listState: { ...EMPTY_LIST_STATE },\n });\n\n // ── Helper: resolve items from current graph state for a given key ───────────\n function resolveItems(key: string): TEntity[] {\n const s = useGraphStore.getState();\n const list = s.lists[key];\n if (!list) return [];\n return list.ids\n .map((id: EntityId) => s.readEntity<TEntity>(type, id))\n .filter((e): e is TEntity => e !== null);\n }\n\n // ── Initial / stale-while-revalidate fetch ────────────────────────────────────\n // Declared before createEffect so it is always defined when the effect runs\n // (the effect mock executes the callback synchronously in tests).\n async function doInitialFetch(key: string): Promise<void> {\n const s = useGraphStore.getState();\n const listState = s.lists[key];\n const effectiveStaleTime = staleTime ?? getEngineOptions().defaultStaleTime;\n const age = Date.now() - (listState?.lastFetched ?? 0);\n const isFresh = listState && !listState.stale && age < effectiveStaleTime;\n if (isFresh) return;\n\n await fetchList(\n {\n type,\n queryKey: JSON.parse(key) as unknown[],\n fetch,\n normalize,\n mode,\n onSuccess: onSuccess as ((r: { items: TRaw[]; total?: number | null }) => void) | undefined,\n onError,\n },\n {},\n getEngineOptions(),\n );\n }\n\n // ── Reactive effect: subscribe to graph whenever queryKey changes ─────────────\n createEffect(() => {\n const rawKey = queryKeyAccessor();\n const key = serializeKey(rawKey);\n const enabled = enabledAccessor ? enabledAccessor() : true;\n\n // Sync snapshot immediately with whatever is already in the graph.\n const syncFromGraph = () => {\n const s = useGraphStore.getState();\n const listState = s.lists[key] ?? EMPTY_LIST_STATE;\n const items = resolveItems(key);\n setSlice(\"listState\", (prev) => ({ ...prev, ...listState }));\n setSlice(\"items\", items);\n };\n\n syncFromGraph();\n\n // Subscribe to any graph change that could affect this list.\n // Solid's fine-grained update system means only changed fields propagate.\n const unsub = useGraphStore.subscribe(\n (s) => {\n const listState = s.lists[key] ?? EMPTY_LIST_STATE;\n const entityBucket = s.entities[type];\n return { listState, entityBucket };\n },\n ({ listState }) => {\n const items = resolveItems(key);\n setSlice(\"listState\", (prev) => ({ ...prev, ...listState }));\n setSlice(\"items\", items);\n },\n { equalityFn: listSliceEqual },\n );\n\n // Kick off the fetch when enabled and data may be stale.\n if (enabled && (fetch || normalize)) {\n void doInitialFetch(key);\n }\n\n onCleanup(() => {\n unsub();\n });\n });\n\n // ── fetchNextPage ─────────────────────────────────────────────────────────────\n async function fetchNextPage(): Promise<void> {\n const key = serializeKey(queryKeyAccessor());\n const s = useGraphStore.getState();\n const listState = s.lists[key] ?? EMPTY_LIST_STATE;\n if (!listState.hasNextPage || listState.isFetchingMore) return;\n\n await fetchList(\n {\n type,\n queryKey: JSON.parse(key) as unknown[],\n fetch,\n normalize,\n mode: \"append\",\n onSuccess: onSuccess as ((r: { items: TRaw[]; total?: number | null }) => void) | undefined,\n onError,\n },\n { cursor: listState.nextCursor ?? undefined },\n getEngineOptions(),\n true, // isLoadMore\n );\n }\n\n // ── Imperative refetch ────────────────────────────────────────────────────────\n async function refetch(): Promise<void> {\n const key = serializeKey(queryKeyAccessor());\n useGraphStore.getState().setListStale(key, true);\n await fetchList(\n {\n type,\n queryKey: JSON.parse(key) as unknown[],\n fetch,\n normalize,\n mode: \"replace\",\n onSuccess: onSuccess as ((r: { items: TRaw[]; total?: number | null }) => void) | undefined,\n onError,\n },\n {},\n getEngineOptions(),\n );\n }\n\n // ── Derived signals ───────────────────────────────────────────────────────────\n const isLoading = () => slice.listState.isFetching && slice.items.length === 0;\n const isLoadingMore = () => slice.listState.isFetchingMore;\n const error = () => slice.listState.error;\n const typedError = () => slice.listState.lastError;\n const total = () => slice.listState.total;\n const hasNextPage = () => slice.listState.hasNextPage;\n const hasPrevPage = () => slice.listState.hasPrevPage;\n\n return {\n items: () => slice.items,\n isLoading,\n isLoadingMore,\n error,\n typedError,\n total,\n hasNextPage,\n hasPrevPage,\n fetchNextPage,\n refetch,\n listState: () => slice.listState,\n };\n}\n","/**\n * createGraphStore — SolidJS fine-grained reactive bridge to the entire\n * Zustand entity graph.\n *\n * Consumers that need ad-hoc access to the graph outside `createEntity` /\n * `createEntityList` can use this to read any slice of `GraphState` as a\n * SolidJS signal. The subscription is scoped to only the selected slice,\n * so unrelated graph mutations do not trigger re-computations.\n *\n * @example\n * ```ts\n * // Read total entity count for a devtools panel.\n * const entityCount = createGraphStore(\n * (s) => Object.keys(s.entities).reduce((n, t) => n + Object.keys(s.entities[t]).length, 0),\n * );\n * console.log(entityCount()); // reactive SolidJS accessor\n * ```\n */\n\nimport { createSignal, onCleanup } from \"solid-js\";\nimport { useGraphStore } from \"@prometheus-ags/entity-graph-core\";\nimport type { GraphState } from \"@prometheus-ags/entity-graph-core\";\n\n/**\n * Subscribe to a selector over the entity graph.\n * Returns a SolidJS accessor that updates whenever the selected slice changes.\n *\n * @param selector - Pure function that maps the full `GraphState` to a derived value.\n * @param equalityFn - Optional equality check (default: strict reference equality `===`).\n */\nexport function createGraphStore<T>(\n selector: (state: GraphState) => T,\n equalityFn?: (a: T, b: T) => boolean,\n): () => T {\n const eq = equalityFn ?? ((a, b) => a === b);\n const initial = selector(useGraphStore.getState());\n const [value, setValue] = createSignal<T>(initial);\n\n const unsub = useGraphStore.subscribe(\n selector,\n (next, prev) => {\n if (!eq(next, prev)) {\n setValue(() => next);\n }\n },\n );\n\n onCleanup(() => unsub());\n\n return value;\n}\n","/**\n * GraphProvider utilities for SolidJS.\n *\n * Provides boot-time setup helpers for attaching global focus/reconnect\n * listeners and configuring the engine from within a SolidJS application root.\n *\n * Usage in the application root (e.g. `src/index.tsx`):\n *\n * ```ts\n * import { setupGraphProvider } from \"@prometheus-ags/entity-graph-solid\";\n * import { configureEngine, attachGlobalListeners } from \"@prometheus-ags/entity-graph-core\";\n *\n * setupGraphProvider({\n * staleTime: 60_000,\n * revalidateOnFocus: true,\n * });\n * ```\n */\n\nimport {\n configureEngine,\n attachGlobalListeners,\n startGarbageCollector,\n} from \"@prometheus-ags/entity-graph-core\";\nimport type { EngineOptions } from \"@prometheus-ags/entity-graph-core\";\n\nexport interface GraphProviderOptions extends EngineOptions {\n /**\n * Whether to attach window focus/online listeners for stale-while-revalidate.\n * Defaults to `true` in browser environments.\n */\n attachListeners?: boolean;\n /**\n * Whether to start the periodic garbage collector.\n * Defaults to `true`.\n */\n startGc?: boolean;\n}\n\n/**\n * Configures the entity-graph engine and wires browser-level listeners.\n * Call this once at application startup before mounting the root component.\n *\n * Returns a cleanup function that stops the GC interval (useful for tests).\n */\nexport function setupGraphProvider(opts: GraphProviderOptions = {}): () => void {\n const { attachListeners = true, startGc = true, ...engineOpts } = opts;\n\n if (Object.keys(engineOpts).length > 0) {\n configureEngine(engineOpts);\n }\n\n if (attachListeners && typeof window !== \"undefined\") {\n attachGlobalListeners();\n }\n\n let stopGc: (() => void) | undefined;\n if (startGc && typeof window !== \"undefined\") {\n stopGc = startGarbageCollector();\n }\n\n return () => stopGc?.();\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@prometheus-ags/entity-graph-solid",
3
+ "version": "3.0.0-alpha.0",
4
+ "description": "SolidJS bindings for @prometheus-ags/entity-graph-core — fine-grained reactive entity and list primitives over the normalized entity graph.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.js"
15
+ }
16
+ },
17
+ "sideEffects": false,
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "dependencies": {
25
+ "@prometheus-ags/entity-graph-core": "3.0.0-alpha.0"
26
+ },
27
+ "peerDependencies": {
28
+ "solid-js": ">=1.8.0"
29
+ },
30
+ "peerDependenciesMeta": {
31
+ "solid-js": {
32
+ "optional": false
33
+ }
34
+ },
35
+ "devDependencies": {
36
+ "solid-js": "^1.9.7",
37
+ "tsup": ">=8",
38
+ "typescript": ">=6",
39
+ "vitest": ">=4"
40
+ },
41
+ "keywords": [
42
+ "solid",
43
+ "solidjs",
44
+ "entity-graph",
45
+ "normalized-cache",
46
+ "state-management",
47
+ "reactive",
48
+ "fine-grained-reactivity"
49
+ ],
50
+ "scripts": {
51
+ "build": "tsup",
52
+ "typecheck": "tsc --noEmit -p tsconfig.json",
53
+ "test": "vitest run"
54
+ }
55
+ }