@prometheus-ags/prometheus-entity-management 1.0.0 → 1.1.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/CHANGELOG.md +21 -0
- package/README.md +31 -3
- package/dist/index.d.mts +162 -1
- package/dist/index.d.ts +162 -1
- package/dist/index.js +331 -58
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +325 -59
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,6 +23,11 @@ var EMPTY_ENTITY_STATE = {
|
|
|
23
23
|
error: null,
|
|
24
24
|
stale: false
|
|
25
25
|
};
|
|
26
|
+
var EMPTY_SYNC_METADATA = {
|
|
27
|
+
synced: true,
|
|
28
|
+
origin: "server",
|
|
29
|
+
updatedAt: null
|
|
30
|
+
};
|
|
26
31
|
var EMPTY_LIST_STATE = {
|
|
27
32
|
ids: EMPTY_IDS,
|
|
28
33
|
total: null,
|
|
@@ -41,6 +46,9 @@ var EMPTY_LIST_STATE = {
|
|
|
41
46
|
function defaultEntityState() {
|
|
42
47
|
return { ...EMPTY_ENTITY_STATE };
|
|
43
48
|
}
|
|
49
|
+
function defaultSyncMetadata() {
|
|
50
|
+
return { ...EMPTY_SYNC_METADATA };
|
|
51
|
+
}
|
|
44
52
|
function defaultListState() {
|
|
45
53
|
return { ...EMPTY_LIST_STATE, ids: [] };
|
|
46
54
|
}
|
|
@@ -53,24 +61,33 @@ var useGraphStore = zustand.create()(
|
|
|
53
61
|
entities: {},
|
|
54
62
|
patches: {},
|
|
55
63
|
entityStates: {},
|
|
64
|
+
syncMetadata: {},
|
|
56
65
|
lists: {},
|
|
57
66
|
upsertEntity: (type, id, data) => set((s) => {
|
|
58
67
|
if (!s.entities[type]) s.entities[type] = {};
|
|
59
68
|
s.entities[type][id] = { ...s.entities[type][id] ?? {}, ...data };
|
|
69
|
+
const key = ek(type, id);
|
|
70
|
+
if (!s.syncMetadata[key]) s.syncMetadata[key] = defaultSyncMetadata();
|
|
60
71
|
}),
|
|
61
72
|
upsertEntities: (type, entries) => set((s) => {
|
|
62
73
|
if (!s.entities[type]) s.entities[type] = {};
|
|
63
|
-
for (const { id, data } of entries)
|
|
74
|
+
for (const { id, data } of entries) {
|
|
64
75
|
s.entities[type][id] = { ...s.entities[type][id] ?? {}, ...data };
|
|
76
|
+
const key = ek(type, id);
|
|
77
|
+
if (!s.syncMetadata[key]) s.syncMetadata[key] = defaultSyncMetadata();
|
|
78
|
+
}
|
|
65
79
|
}),
|
|
66
80
|
replaceEntity: (type, id, data) => set((s) => {
|
|
67
81
|
if (!s.entities[type]) s.entities[type] = {};
|
|
68
82
|
s.entities[type][id] = data;
|
|
83
|
+
const key = ek(type, id);
|
|
84
|
+
if (!s.syncMetadata[key]) s.syncMetadata[key] = defaultSyncMetadata();
|
|
69
85
|
}),
|
|
70
86
|
removeEntity: (type, id) => set((s) => {
|
|
71
87
|
delete s.entities[type]?.[id];
|
|
72
88
|
delete s.patches[type]?.[id];
|
|
73
89
|
delete s.entityStates[ek(type, id)];
|
|
90
|
+
delete s.syncMetadata[ek(type, id)];
|
|
74
91
|
}),
|
|
75
92
|
patchEntity: (type, id, patch) => set((s) => {
|
|
76
93
|
if (!s.patches[type]) s.patches[type] = {};
|
|
@@ -102,12 +119,20 @@ var useGraphStore = zustand.create()(
|
|
|
102
119
|
s.entityStates[k].isFetching = false;
|
|
103
120
|
s.entityStates[k].error = null;
|
|
104
121
|
s.entityStates[k].stale = false;
|
|
122
|
+
s.syncMetadata[k] = { ...s.syncMetadata[k] ?? defaultSyncMetadata(), synced: true, origin: "server", updatedAt: Date.now() };
|
|
105
123
|
}),
|
|
106
124
|
setEntityStale: (type, id, stale) => set((s) => {
|
|
107
125
|
const k = ek(type, id);
|
|
108
126
|
if (!s.entityStates[k]) s.entityStates[k] = defaultEntityState();
|
|
109
127
|
s.entityStates[k].stale = stale;
|
|
110
128
|
}),
|
|
129
|
+
setEntitySyncMetadata: (type, id, metadata) => set((s) => {
|
|
130
|
+
const k = ek(type, id);
|
|
131
|
+
s.syncMetadata[k] = { ...s.syncMetadata[k] ?? defaultSyncMetadata(), ...metadata };
|
|
132
|
+
}),
|
|
133
|
+
clearEntitySyncMetadata: (type, id) => set((s) => {
|
|
134
|
+
delete s.syncMetadata[ek(type, id)];
|
|
135
|
+
}),
|
|
111
136
|
setListResult: (key, ids, meta) => set((s) => {
|
|
112
137
|
const ex = s.lists[key] ?? defaultListState();
|
|
113
138
|
s.lists[key] = { ...ex, ...meta, ids, isFetching: false, isFetchingMore: false, error: null, stale: false, lastFetched: Date.now() };
|
|
@@ -179,11 +204,264 @@ var useGraphStore = zustand.create()(
|
|
|
179
204
|
if (!base) return null;
|
|
180
205
|
const patch = s.patches[type]?.[id];
|
|
181
206
|
return patch ? { ...base, ...patch } : base;
|
|
207
|
+
},
|
|
208
|
+
readEntitySnapshot: (type, id) => {
|
|
209
|
+
const s = get();
|
|
210
|
+
const base = s.entities[type]?.[id];
|
|
211
|
+
if (!base) return null;
|
|
212
|
+
const patch = s.patches[type]?.[id];
|
|
213
|
+
const metadata = s.syncMetadata[ek(type, id)] ?? EMPTY_SYNC_METADATA;
|
|
214
|
+
return {
|
|
215
|
+
...patch ? { ...base, ...patch } : base,
|
|
216
|
+
$synced: metadata.synced,
|
|
217
|
+
$origin: metadata.origin,
|
|
218
|
+
$updatedAt: metadata.updatedAt
|
|
219
|
+
};
|
|
182
220
|
}
|
|
183
221
|
}))
|
|
184
222
|
)
|
|
185
223
|
);
|
|
186
224
|
|
|
225
|
+
// src/graph-query.ts
|
|
226
|
+
function queryOnce(opts) {
|
|
227
|
+
const store = useGraphStore.getState();
|
|
228
|
+
const ids = resolveCandidateIds(store, opts);
|
|
229
|
+
let rows = ids.map((id) => store.readEntitySnapshot(opts.type, id)).filter((row) => row !== null);
|
|
230
|
+
if (opts.where) rows = rows.filter(opts.where);
|
|
231
|
+
if (opts.sort) rows = [...rows].sort(opts.sort);
|
|
232
|
+
const projected = rows.map((row) => applySelection(projectRow(row, opts.include, store), opts.select));
|
|
233
|
+
if (opts.id) return projected[0] ?? null;
|
|
234
|
+
return projected;
|
|
235
|
+
}
|
|
236
|
+
var selectGraph = queryOnce;
|
|
237
|
+
function resolveCandidateIds(store, opts) {
|
|
238
|
+
if (opts.id) return [opts.id];
|
|
239
|
+
if (opts.ids) return opts.ids;
|
|
240
|
+
if (opts.listKey) return store.lists[opts.listKey]?.ids ?? [];
|
|
241
|
+
return Object.keys(store.entities[opts.type] ?? {});
|
|
242
|
+
}
|
|
243
|
+
function projectRow(row, include, store) {
|
|
244
|
+
if (!include) return row;
|
|
245
|
+
const projected = { ...row };
|
|
246
|
+
for (const [key, relation] of Object.entries(include)) {
|
|
247
|
+
const related = resolveRelation(row, relation, store);
|
|
248
|
+
projected[key] = related;
|
|
249
|
+
}
|
|
250
|
+
return projected;
|
|
251
|
+
}
|
|
252
|
+
function resolveRelation(entity, relation, store) {
|
|
253
|
+
const include = relation.include;
|
|
254
|
+
switch (relation.via.kind) {
|
|
255
|
+
case "field": {
|
|
256
|
+
const relatedId = entity[relation.via.field];
|
|
257
|
+
if (typeof relatedId !== "string") return null;
|
|
258
|
+
const related = store.readEntitySnapshot(relation.type, relatedId);
|
|
259
|
+
return related ? projectRow(related, include, store) : null;
|
|
260
|
+
}
|
|
261
|
+
case "array": {
|
|
262
|
+
const ids = entity[relation.via.field];
|
|
263
|
+
if (!Array.isArray(ids)) return [];
|
|
264
|
+
return ids.map((id) => typeof id === "string" ? store.readEntitySnapshot(relation.type, id) : null).filter((row) => row !== null).map((row) => projectRow(row, include, store));
|
|
265
|
+
}
|
|
266
|
+
case "list": {
|
|
267
|
+
const key = typeof relation.via.key === "function" ? relation.via.key(entity) : relation.via.key;
|
|
268
|
+
if (!key) return [];
|
|
269
|
+
const ids = store.lists[key]?.ids ?? [];
|
|
270
|
+
return ids.map((id) => store.readEntitySnapshot(relation.type, id)).filter((row) => row !== null).map((row) => projectRow(row, include, store));
|
|
271
|
+
}
|
|
272
|
+
case "resolver": {
|
|
273
|
+
const resolved = relation.via.resolve(entity, store);
|
|
274
|
+
if (Array.isArray(resolved)) {
|
|
275
|
+
return resolved.map((id) => store.readEntitySnapshot(relation.type, id)).filter((row) => row !== null).map((row) => projectRow(row, include, store));
|
|
276
|
+
}
|
|
277
|
+
if (typeof resolved !== "string") return null;
|
|
278
|
+
const related = store.readEntitySnapshot(relation.type, resolved);
|
|
279
|
+
return related ? projectRow(related, include, store) : null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function applySelection(row, select) {
|
|
284
|
+
if (!select) return row;
|
|
285
|
+
if (typeof select === "function") {
|
|
286
|
+
const result = select(row);
|
|
287
|
+
return result && typeof result === "object" ? result : { value: result };
|
|
288
|
+
}
|
|
289
|
+
const picked = {};
|
|
290
|
+
for (const key of select) {
|
|
291
|
+
if (key in row) picked[key] = row[key];
|
|
292
|
+
}
|
|
293
|
+
return picked;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/graph-actions.ts
|
|
297
|
+
function createGraphTransaction() {
|
|
298
|
+
const baseline = cloneGraphData();
|
|
299
|
+
let closed = false;
|
|
300
|
+
const tx = {
|
|
301
|
+
upsertEntity(type, id, data) {
|
|
302
|
+
useGraphStore.getState().upsertEntity(type, id, data);
|
|
303
|
+
return tx;
|
|
304
|
+
},
|
|
305
|
+
replaceEntity(type, id, data) {
|
|
306
|
+
useGraphStore.getState().replaceEntity(type, id, data);
|
|
307
|
+
return tx;
|
|
308
|
+
},
|
|
309
|
+
removeEntity(type, id) {
|
|
310
|
+
useGraphStore.getState().removeEntity(type, id);
|
|
311
|
+
return tx;
|
|
312
|
+
},
|
|
313
|
+
patchEntity(type, id, patch) {
|
|
314
|
+
useGraphStore.getState().patchEntity(type, id, patch);
|
|
315
|
+
return tx;
|
|
316
|
+
},
|
|
317
|
+
clearPatch(type, id) {
|
|
318
|
+
useGraphStore.getState().clearPatch(type, id);
|
|
319
|
+
return tx;
|
|
320
|
+
},
|
|
321
|
+
insertIdInList(key, id, position) {
|
|
322
|
+
useGraphStore.getState().insertIdInList(key, id, position);
|
|
323
|
+
return tx;
|
|
324
|
+
},
|
|
325
|
+
removeIdFromAllLists(type, id) {
|
|
326
|
+
useGraphStore.getState().removeIdFromAllLists(type, id);
|
|
327
|
+
return tx;
|
|
328
|
+
},
|
|
329
|
+
setEntitySyncMetadata(type, id, metadata) {
|
|
330
|
+
useGraphStore.getState().setEntitySyncMetadata(type, id, metadata);
|
|
331
|
+
return tx;
|
|
332
|
+
},
|
|
333
|
+
markEntityPending(type, id, origin = "optimistic") {
|
|
334
|
+
useGraphStore.getState().setEntitySyncMetadata(type, id, {
|
|
335
|
+
synced: false,
|
|
336
|
+
origin,
|
|
337
|
+
updatedAt: Date.now()
|
|
338
|
+
});
|
|
339
|
+
return tx;
|
|
340
|
+
},
|
|
341
|
+
markEntitySynced(type, id, origin = "server") {
|
|
342
|
+
useGraphStore.getState().setEntitySyncMetadata(type, id, {
|
|
343
|
+
synced: true,
|
|
344
|
+
origin,
|
|
345
|
+
updatedAt: Date.now()
|
|
346
|
+
});
|
|
347
|
+
return tx;
|
|
348
|
+
},
|
|
349
|
+
commit() {
|
|
350
|
+
closed = true;
|
|
351
|
+
},
|
|
352
|
+
rollback() {
|
|
353
|
+
if (closed) return;
|
|
354
|
+
useGraphStore.setState(cloneGraphData(baseline));
|
|
355
|
+
closed = true;
|
|
356
|
+
},
|
|
357
|
+
snapshot() {
|
|
358
|
+
return cloneGraphData();
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
return tx;
|
|
362
|
+
}
|
|
363
|
+
function createGraphAction(opts) {
|
|
364
|
+
return async (input) => {
|
|
365
|
+
const tx = createGraphTransaction();
|
|
366
|
+
try {
|
|
367
|
+
opts.optimistic?.(tx, input);
|
|
368
|
+
const result = await opts.run(tx, input);
|
|
369
|
+
opts.onSuccess?.(result, input, tx);
|
|
370
|
+
tx.commit();
|
|
371
|
+
return result;
|
|
372
|
+
} catch (error) {
|
|
373
|
+
tx.rollback();
|
|
374
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
375
|
+
opts.onError?.(normalized, input);
|
|
376
|
+
throw normalized;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
function cloneGraphData(source = useGraphStore.getState()) {
|
|
381
|
+
return {
|
|
382
|
+
entities: structuredClone(source.entities),
|
|
383
|
+
patches: structuredClone(source.patches),
|
|
384
|
+
entityStates: structuredClone(source.entityStates),
|
|
385
|
+
syncMetadata: structuredClone(source.syncMetadata),
|
|
386
|
+
lists: structuredClone(source.lists)
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/graph-effects.ts
|
|
391
|
+
function createGraphEffect(opts) {
|
|
392
|
+
const getKey = opts.getKey ?? defaultGetKey;
|
|
393
|
+
const isEqual = opts.isEqual ?? defaultIsEqual;
|
|
394
|
+
let initialized = false;
|
|
395
|
+
let previous = /* @__PURE__ */ new Map();
|
|
396
|
+
const evaluate = () => {
|
|
397
|
+
const nextValues = normalizeQueryResult(opts.query());
|
|
398
|
+
const next = /* @__PURE__ */ new Map();
|
|
399
|
+
nextValues.forEach((value, index) => {
|
|
400
|
+
next.set(getKey(value, index), value);
|
|
401
|
+
});
|
|
402
|
+
if (!initialized) {
|
|
403
|
+
initialized = true;
|
|
404
|
+
previous = next;
|
|
405
|
+
if (opts.skipInitial) return;
|
|
406
|
+
}
|
|
407
|
+
for (const [key, value] of next.entries()) {
|
|
408
|
+
const previousValue = previous.get(key);
|
|
409
|
+
if (previousValue === void 0) {
|
|
410
|
+
opts.onEnter?.({ key, value });
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (!isEqual(previousValue, value)) {
|
|
414
|
+
opts.onUpdate?.({ key, value, previousValue });
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
for (const [key, previousValue] of previous.entries()) {
|
|
418
|
+
if (!next.has(key)) opts.onExit?.({ key, previousValue });
|
|
419
|
+
}
|
|
420
|
+
previous = next;
|
|
421
|
+
};
|
|
422
|
+
evaluate();
|
|
423
|
+
const unsubscribe = useGraphStore.subscribe(() => {
|
|
424
|
+
evaluate();
|
|
425
|
+
});
|
|
426
|
+
return {
|
|
427
|
+
dispose: () => {
|
|
428
|
+
unsubscribe();
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
function normalizeQueryResult(value) {
|
|
433
|
+
if (value == null) return [];
|
|
434
|
+
return Array.isArray(value) ? value : [value];
|
|
435
|
+
}
|
|
436
|
+
function defaultGetKey(value, index) {
|
|
437
|
+
if (value && typeof value === "object") {
|
|
438
|
+
const record = value;
|
|
439
|
+
if (typeof record.id === "string") return record.id;
|
|
440
|
+
if (typeof record.$key === "string") return record.$key;
|
|
441
|
+
}
|
|
442
|
+
return String(index);
|
|
443
|
+
}
|
|
444
|
+
function defaultIsEqual(previousValue, nextValue) {
|
|
445
|
+
return JSON.stringify(previousValue) === JSON.stringify(nextValue);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/ai-interop.ts
|
|
449
|
+
function exportGraphSnapshot(opts) {
|
|
450
|
+
const payload = {
|
|
451
|
+
scope: opts.scope,
|
|
452
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
453
|
+
data: opts.data
|
|
454
|
+
};
|
|
455
|
+
return JSON.stringify(payload, null, opts.pretty === false ? 0 : 2);
|
|
456
|
+
}
|
|
457
|
+
function createGraphTool(handler) {
|
|
458
|
+
return (input) => handler(input, {
|
|
459
|
+
store: useGraphStore.getState(),
|
|
460
|
+
queryOnce,
|
|
461
|
+
exportGraphSnapshot
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
187
465
|
// src/engine.ts
|
|
188
466
|
function serializeKey(key) {
|
|
189
467
|
return JSON.stringify(key, (_, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).sort()) : v);
|
|
@@ -451,10 +729,7 @@ function useEntity(opts) {
|
|
|
451
729
|
normalizeRef.current = opts.normalize;
|
|
452
730
|
const data = zustand.useStore(useGraphStore, shallow.useShallow((state) => {
|
|
453
731
|
if (!id) return null;
|
|
454
|
-
|
|
455
|
-
if (!base) return null;
|
|
456
|
-
const patch = state.patches[type]?.[id];
|
|
457
|
-
return patch ? { ...base, ...patch } : base;
|
|
732
|
+
return state.readEntitySnapshot(type, id);
|
|
458
733
|
}));
|
|
459
734
|
const entityState = zustand.useStore(useGraphStore, React5.useCallback(
|
|
460
735
|
(state) => state.entityStates[`${type}:${id}`] ?? EMPTY_ENTITY_STATE,
|
|
@@ -492,12 +767,7 @@ function useEntityList(opts) {
|
|
|
492
767
|
useGraphStore,
|
|
493
768
|
shallow.useShallow((state) => {
|
|
494
769
|
const ids = state.lists[key]?.ids ?? EMPTY_IDS;
|
|
495
|
-
return ids.map((id) =>
|
|
496
|
-
const base = state.entities[type]?.[id];
|
|
497
|
-
if (!base) return null;
|
|
498
|
-
const patch = state.patches[type]?.[id];
|
|
499
|
-
return patch ? { ...base, ...patch } : base;
|
|
500
|
-
}).filter((x) => x !== null);
|
|
770
|
+
return ids.map((id) => state.readEntitySnapshot(type, id)).filter((x) => x !== null);
|
|
501
771
|
})
|
|
502
772
|
);
|
|
503
773
|
const doFetch = React5.useCallback((params = {}) => {
|
|
@@ -534,18 +804,28 @@ function useEntityMutation(opts) {
|
|
|
534
804
|
const { id, patch } = opt;
|
|
535
805
|
const store = useGraphStore.getState();
|
|
536
806
|
const previous = { ...store.patches[type]?.[id] };
|
|
807
|
+
const previousSync = store.syncMetadata[`${type}:${id}`];
|
|
537
808
|
store.patchEntity(type, id, patch);
|
|
538
|
-
|
|
809
|
+
store.setEntitySyncMetadata(type, id, { synced: false, origin: "optimistic", updatedAt: Date.now() });
|
|
810
|
+
rollback = () => {
|
|
811
|
+
const currentStore = useGraphStore.getState();
|
|
812
|
+
if (Object.keys(previous).length > 0) currentStore.patchEntity(type, id, previous);
|
|
813
|
+
else currentStore.clearPatch(type, id);
|
|
814
|
+
if (previousSync) currentStore.setEntitySyncMetadata(type, id, previousSync);
|
|
815
|
+
else currentStore.clearEntitySyncMetadata(type, id);
|
|
816
|
+
};
|
|
539
817
|
}
|
|
540
818
|
}
|
|
541
819
|
try {
|
|
542
820
|
const result = await apiFn(input);
|
|
543
821
|
if (normalize) {
|
|
544
822
|
const { id, data } = normalize(result, input);
|
|
545
|
-
useGraphStore.getState()
|
|
823
|
+
const store = useGraphStore.getState();
|
|
824
|
+
store.upsertEntity(type, id, data);
|
|
825
|
+
store.setEntitySyncMetadata(type, id, { synced: true, origin: "server", updatedAt: Date.now() });
|
|
546
826
|
if (optimistic) {
|
|
547
827
|
const opt = optimistic(input);
|
|
548
|
-
if (opt)
|
|
828
|
+
if (opt) store.clearPatch(type, opt.id);
|
|
549
829
|
}
|
|
550
830
|
}
|
|
551
831
|
if (invalidateLists) for (const k of invalidateLists) useGraphStore.getState().invalidateLists(k);
|
|
@@ -1047,12 +1327,7 @@ function useEntityView(opts) {
|
|
|
1047
1327
|
shallow.useShallow((state) => {
|
|
1048
1328
|
const list = state.lists[baseKey] ?? EMPTY_LIST_STATE;
|
|
1049
1329
|
const sourceIds = completenessMode !== "remote" && remoteResultKey ? state.lists[remoteResultKey]?.ids ?? EMPTY_IDS : list.ids;
|
|
1050
|
-
const getEntity = (id) =>
|
|
1051
|
-
const base = state.entities[type]?.[id];
|
|
1052
|
-
if (!base) return null;
|
|
1053
|
-
const patch = state.patches[type]?.[id];
|
|
1054
|
-
return patch ? { ...base, ...patch } : base;
|
|
1055
|
-
};
|
|
1330
|
+
const getEntity = (id) => state.readEntitySnapshot(type, id);
|
|
1056
1331
|
return applyView(
|
|
1057
1332
|
sourceIds,
|
|
1058
1333
|
getEntity,
|
|
@@ -1065,12 +1340,7 @@ function useEntityView(opts) {
|
|
|
1065
1340
|
const items = zustand.useStore(
|
|
1066
1341
|
useGraphStore,
|
|
1067
1342
|
shallow.useShallow(
|
|
1068
|
-
(state) => localViewIds.map((id) =>
|
|
1069
|
-
const base = state.entities[type]?.[id];
|
|
1070
|
-
if (!base) return null;
|
|
1071
|
-
const patch = state.patches[type]?.[id];
|
|
1072
|
-
return patch ? { ...base, ...patch } : base;
|
|
1073
|
-
}).filter((item) => item !== null)
|
|
1343
|
+
(state) => localViewIds.map((id) => state.readEntitySnapshot(type, id)).filter((item) => item !== null)
|
|
1074
1344
|
)
|
|
1075
1345
|
);
|
|
1076
1346
|
const fireRemoteFetch = React5.useCallback(async (view, cursor) => {
|
|
@@ -1125,17 +1395,11 @@ function useEntityView(opts) {
|
|
|
1125
1395
|
const isPresent = id in newEntities;
|
|
1126
1396
|
if (!isPresent) continue;
|
|
1127
1397
|
const entity = newEntities[id];
|
|
1128
|
-
const
|
|
1129
|
-
const merged = patch ? { ...entity, ...patch } : entity;
|
|
1398
|
+
const merged = store.readEntitySnapshot(type, id) ?? entity;
|
|
1130
1399
|
const matches = (!view.filter || matchesFilter(merged, view.filter)) && (!view.search?.query || matchesSearch(merged, view.search.query, view.search.fields));
|
|
1131
1400
|
if (matches && !list.ids.includes(id)) {
|
|
1132
1401
|
if (view.sort && view.sort.length > 0) {
|
|
1133
|
-
const idx = findInsertionIndex(merged, list.ids, (eid) =>
|
|
1134
|
-
const b = store.entities[type]?.[eid];
|
|
1135
|
-
if (!b) return null;
|
|
1136
|
-
const p = store.patches[type]?.[eid];
|
|
1137
|
-
return p ? { ...b, ...p } : b;
|
|
1138
|
-
}, view.sort);
|
|
1402
|
+
const idx = findInsertionIndex(merged, list.ids, (eid) => store.readEntitySnapshot(type, eid), view.sort);
|
|
1139
1403
|
store.insertIdInList(baseKey, id, idx);
|
|
1140
1404
|
} else store.insertIdInList(baseKey, id, "start");
|
|
1141
1405
|
}
|
|
@@ -1313,25 +1577,33 @@ function useEntityCRUD(opts) {
|
|
|
1313
1577
|
}, [resetBuffer, selectedId]);
|
|
1314
1578
|
const applyOptimistic = React5.useCallback(() => {
|
|
1315
1579
|
if (!selectedId) return;
|
|
1316
|
-
useGraphStore.getState()
|
|
1580
|
+
const store = useGraphStore.getState();
|
|
1581
|
+
store.patchEntity(type, selectedId, editBuffer);
|
|
1582
|
+
store.setEntitySyncMetadata(type, selectedId, { synced: false, origin: "optimistic", updatedAt: Date.now() });
|
|
1317
1583
|
}, [type, selectedId, editBuffer]);
|
|
1318
1584
|
const save = React5.useCallback(async () => {
|
|
1319
1585
|
if (!selectedId || !onUpdate) return null;
|
|
1320
1586
|
setIsSaving(true);
|
|
1321
1587
|
setSaveError(null);
|
|
1322
|
-
const
|
|
1323
|
-
|
|
1588
|
+
const store = useGraphStore.getState();
|
|
1589
|
+
const previous = store.readEntity(type, selectedId);
|
|
1590
|
+
const previousSync = store.syncMetadata[`${type}:${selectedId}`];
|
|
1591
|
+
store.upsertEntity(type, selectedId, editBuffer);
|
|
1592
|
+
store.setEntitySyncMetadata(type, selectedId, { synced: false, origin: "optimistic", updatedAt: Date.now() });
|
|
1324
1593
|
try {
|
|
1325
1594
|
const result = await onUpdate(selectedId, editBuffer);
|
|
1326
1595
|
const { id, data } = normalize(result);
|
|
1327
|
-
|
|
1328
|
-
|
|
1596
|
+
store.replaceEntity(type, id, data);
|
|
1597
|
+
store.clearPatch(type, id);
|
|
1598
|
+
store.setEntitySyncMetadata(type, id, { synced: true, origin: "server", updatedAt: Date.now() });
|
|
1329
1599
|
cascadeInvalidation({ type, id: selectedId, previous, next: data, op: "update" });
|
|
1330
1600
|
setMode("detail");
|
|
1331
1601
|
optsRef.current.onUpdateSuccess?.(result);
|
|
1332
1602
|
return result;
|
|
1333
1603
|
} catch (err) {
|
|
1334
|
-
if (previous)
|
|
1604
|
+
if (previous) store.replaceEntity(type, selectedId, previous);
|
|
1605
|
+
if (previousSync) store.setEntitySyncMetadata(type, selectedId, previousSync);
|
|
1606
|
+
else store.clearEntitySyncMetadata(type, selectedId);
|
|
1335
1607
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
1336
1608
|
setSaveError(error.message);
|
|
1337
1609
|
optsRef.current.onError?.("update", error);
|
|
@@ -1362,15 +1634,17 @@ function useEntityCRUD(opts) {
|
|
|
1362
1634
|
setCreateError(null);
|
|
1363
1635
|
const tempId = `__temp__${Date.now()}`;
|
|
1364
1636
|
const optimisticData = { ...createBuffer, id: tempId, _optimistic: true };
|
|
1365
|
-
useGraphStore.getState()
|
|
1366
|
-
|
|
1637
|
+
const store = useGraphStore.getState();
|
|
1638
|
+
store.upsertEntity(type, tempId, optimisticData);
|
|
1639
|
+
store.setEntitySyncMetadata(type, tempId, { synced: false, origin: "optimistic", updatedAt: Date.now() });
|
|
1640
|
+
store.insertIdInList(serializeKey(listQueryKey), tempId, "start");
|
|
1367
1641
|
try {
|
|
1368
1642
|
const result = await onCreate(createBuffer);
|
|
1369
1643
|
const { id: realId, data } = normalize(result);
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1644
|
+
store.removeEntity(type, tempId);
|
|
1645
|
+
store.upsertEntity(type, realId, data);
|
|
1646
|
+
store.setEntityFetched(type, realId);
|
|
1647
|
+
store.setEntitySyncMetadata(type, realId, { synced: true, origin: "server", updatedAt: Date.now() });
|
|
1374
1648
|
for (const key of Object.keys(store.lists)) {
|
|
1375
1649
|
const list2 = store.lists[key];
|
|
1376
1650
|
const idx = list2.ids.indexOf(tempId);
|
|
@@ -1388,8 +1662,8 @@ function useEntityCRUD(opts) {
|
|
|
1388
1662
|
optsRef.current.onCreateSuccess?.(result);
|
|
1389
1663
|
return result;
|
|
1390
1664
|
} catch (err) {
|
|
1391
|
-
|
|
1392
|
-
|
|
1665
|
+
store.removeEntity(type, tempId);
|
|
1666
|
+
store.removeIdFromAllLists(type, tempId);
|
|
1393
1667
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
1394
1668
|
setCreateError(error.message);
|
|
1395
1669
|
optsRef.current.onError?.("create", error);
|
|
@@ -2193,10 +2467,7 @@ function useGQLEntity(opts) {
|
|
|
2193
2467
|
optsRef.current = opts;
|
|
2194
2468
|
const data = zustand.useStore(useGraphStore, shallow.useShallow((s) => {
|
|
2195
2469
|
if (!id) return null;
|
|
2196
|
-
|
|
2197
|
-
if (!base) return null;
|
|
2198
|
-
const patch = s.patches[type]?.[id];
|
|
2199
|
-
return patch ? { ...base, ...patch } : base;
|
|
2470
|
+
return s.readEntitySnapshot(type, id);
|
|
2200
2471
|
}));
|
|
2201
2472
|
const entityState = zustand.useStore(useGraphStore, React5.useCallback(
|
|
2202
2473
|
(s) => s.entityStates[`${type}:${id}`] ?? EMPTY_ENTITY_STATE,
|
|
@@ -2242,12 +2513,7 @@ function useGQLList(opts) {
|
|
|
2242
2513
|
useGraphStore,
|
|
2243
2514
|
shallow.useShallow((s) => {
|
|
2244
2515
|
const ids = s.lists[key]?.ids ?? EMPTY_IDS;
|
|
2245
|
-
return ids.map((id) =>
|
|
2246
|
-
const base = s.entities[type]?.[id];
|
|
2247
|
-
if (!base) return null;
|
|
2248
|
-
const p = s.patches[type]?.[id];
|
|
2249
|
-
return p ? { ...base, ...p } : base;
|
|
2250
|
-
}).filter((x) => x !== null);
|
|
2516
|
+
return ids.map((id) => s.readEntitySnapshot(type, id)).filter((x) => x !== null);
|
|
2251
2517
|
})
|
|
2252
2518
|
);
|
|
2253
2519
|
const doFetch = React5.useCallback((cursor, append = false) => {
|
|
@@ -7978,7 +8244,11 @@ exports.configureEngine = configureEngine;
|
|
|
7978
8244
|
exports.createConvexAdapter = createConvexAdapter;
|
|
7979
8245
|
exports.createElectricAdapter = createElectricAdapter;
|
|
7980
8246
|
exports.createGQLClient = createGQLClient;
|
|
8247
|
+
exports.createGraphAction = createGraphAction;
|
|
8248
|
+
exports.createGraphEffect = createGraphEffect;
|
|
7981
8249
|
exports.createGraphQLSubscriptionAdapter = createGraphQLSubscriptionAdapter;
|
|
8250
|
+
exports.createGraphTool = createGraphTool;
|
|
8251
|
+
exports.createGraphTransaction = createGraphTransaction;
|
|
7982
8252
|
exports.createPresetStore = createPresetStore;
|
|
7983
8253
|
exports.createPrismaEntityConfig = createPrismaEntityConfig;
|
|
7984
8254
|
exports.createRow = createRow;
|
|
@@ -7991,6 +8261,7 @@ exports.deleteAction = deleteAction;
|
|
|
7991
8261
|
exports.editAction = editAction;
|
|
7992
8262
|
exports.enumColumn = enumColumn;
|
|
7993
8263
|
exports.executeGQL = executeGQL;
|
|
8264
|
+
exports.exportGraphSnapshot = exportGraphSnapshot;
|
|
7994
8265
|
exports.fetchEntity = fetchEntity;
|
|
7995
8266
|
exports.fetchList = fetchList;
|
|
7996
8267
|
exports.flattenClauses = flattenClauses;
|
|
@@ -8019,9 +8290,11 @@ exports.pureEnumColumn = enumColumn2;
|
|
|
8019
8290
|
exports.pureNumberColumn = numberColumn2;
|
|
8020
8291
|
exports.pureSelectionColumn = selectionColumn2;
|
|
8021
8292
|
exports.pureTextColumn = textColumn2;
|
|
8293
|
+
exports.queryOnce = queryOnce;
|
|
8022
8294
|
exports.readRelations = readRelations;
|
|
8023
8295
|
exports.registerSchema = registerSchema;
|
|
8024
8296
|
exports.resetRealtimeManager = resetRealtimeManager;
|
|
8297
|
+
exports.selectGraph = selectGraph;
|
|
8025
8298
|
exports.selectionColumn = selectionColumn;
|
|
8026
8299
|
exports.serializeKey = serializeKey;
|
|
8027
8300
|
exports.startGarbageCollector = startGarbageCollector;
|