@prometheus-ags/prometheus-entity-management 1.0.0 → 1.2.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 +40 -0
- package/README.md +55 -3
- package/dist/index.d.mts +738 -408
- package/dist/index.d.ts +738 -408
- package/dist/index.js +1101 -339
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +836 -95
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { create, createStore, useStore } from 'zustand';
|
|
2
2
|
import { subscribeWithSelector, persist } from 'zustand/middleware';
|
|
3
3
|
import { immer } from 'zustand/middleware/immer';
|
|
4
|
-
import
|
|
4
|
+
import React6, { createContext, useMemo, useSyncExternalStore, useRef, useCallback, useEffect, useState, useContext, useId } from 'react';
|
|
5
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
6
|
import { useShallow } from 'zustand/react/shallow';
|
|
6
7
|
import { useReactTable, getSortedRowModel, getCoreRowModel, flexRender } from '@tanstack/react-table';
|
|
7
8
|
import { Search, X, Loader2, RefreshCw, ChevronLeft, ChevronRight, Pencil, Trash2 } from 'lucide-react';
|
|
8
9
|
import { clsx } from 'clsx';
|
|
9
10
|
import { twMerge } from 'tailwind-merge';
|
|
10
|
-
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
11
11
|
|
|
12
12
|
// src/graph.ts
|
|
13
13
|
var EMPTY_IDS = [];
|
|
@@ -17,6 +17,11 @@ var EMPTY_ENTITY_STATE = {
|
|
|
17
17
|
error: null,
|
|
18
18
|
stale: false
|
|
19
19
|
};
|
|
20
|
+
var EMPTY_SYNC_METADATA = {
|
|
21
|
+
synced: true,
|
|
22
|
+
origin: "server",
|
|
23
|
+
updatedAt: null
|
|
24
|
+
};
|
|
20
25
|
var EMPTY_LIST_STATE = {
|
|
21
26
|
ids: EMPTY_IDS,
|
|
22
27
|
total: null,
|
|
@@ -35,6 +40,9 @@ var EMPTY_LIST_STATE = {
|
|
|
35
40
|
function defaultEntityState() {
|
|
36
41
|
return { ...EMPTY_ENTITY_STATE };
|
|
37
42
|
}
|
|
43
|
+
function defaultSyncMetadata() {
|
|
44
|
+
return { ...EMPTY_SYNC_METADATA };
|
|
45
|
+
}
|
|
38
46
|
function defaultListState() {
|
|
39
47
|
return { ...EMPTY_LIST_STATE, ids: [] };
|
|
40
48
|
}
|
|
@@ -47,24 +55,33 @@ var useGraphStore = create()(
|
|
|
47
55
|
entities: {},
|
|
48
56
|
patches: {},
|
|
49
57
|
entityStates: {},
|
|
58
|
+
syncMetadata: {},
|
|
50
59
|
lists: {},
|
|
51
60
|
upsertEntity: (type, id, data) => set((s) => {
|
|
52
61
|
if (!s.entities[type]) s.entities[type] = {};
|
|
53
62
|
s.entities[type][id] = { ...s.entities[type][id] ?? {}, ...data };
|
|
63
|
+
const key = ek(type, id);
|
|
64
|
+
if (!s.syncMetadata[key]) s.syncMetadata[key] = defaultSyncMetadata();
|
|
54
65
|
}),
|
|
55
66
|
upsertEntities: (type, entries) => set((s) => {
|
|
56
67
|
if (!s.entities[type]) s.entities[type] = {};
|
|
57
|
-
for (const { id, data } of entries)
|
|
68
|
+
for (const { id, data } of entries) {
|
|
58
69
|
s.entities[type][id] = { ...s.entities[type][id] ?? {}, ...data };
|
|
70
|
+
const key = ek(type, id);
|
|
71
|
+
if (!s.syncMetadata[key]) s.syncMetadata[key] = defaultSyncMetadata();
|
|
72
|
+
}
|
|
59
73
|
}),
|
|
60
74
|
replaceEntity: (type, id, data) => set((s) => {
|
|
61
75
|
if (!s.entities[type]) s.entities[type] = {};
|
|
62
76
|
s.entities[type][id] = data;
|
|
77
|
+
const key = ek(type, id);
|
|
78
|
+
if (!s.syncMetadata[key]) s.syncMetadata[key] = defaultSyncMetadata();
|
|
63
79
|
}),
|
|
64
80
|
removeEntity: (type, id) => set((s) => {
|
|
65
81
|
delete s.entities[type]?.[id];
|
|
66
82
|
delete s.patches[type]?.[id];
|
|
67
83
|
delete s.entityStates[ek(type, id)];
|
|
84
|
+
delete s.syncMetadata[ek(type, id)];
|
|
68
85
|
}),
|
|
69
86
|
patchEntity: (type, id, patch) => set((s) => {
|
|
70
87
|
if (!s.patches[type]) s.patches[type] = {};
|
|
@@ -96,12 +113,20 @@ var useGraphStore = create()(
|
|
|
96
113
|
s.entityStates[k].isFetching = false;
|
|
97
114
|
s.entityStates[k].error = null;
|
|
98
115
|
s.entityStates[k].stale = false;
|
|
116
|
+
s.syncMetadata[k] = { ...s.syncMetadata[k] ?? defaultSyncMetadata(), synced: true, origin: "server", updatedAt: Date.now() };
|
|
99
117
|
}),
|
|
100
118
|
setEntityStale: (type, id, stale) => set((s) => {
|
|
101
119
|
const k = ek(type, id);
|
|
102
120
|
if (!s.entityStates[k]) s.entityStates[k] = defaultEntityState();
|
|
103
121
|
s.entityStates[k].stale = stale;
|
|
104
122
|
}),
|
|
123
|
+
setEntitySyncMetadata: (type, id, metadata) => set((s) => {
|
|
124
|
+
const k = ek(type, id);
|
|
125
|
+
s.syncMetadata[k] = { ...s.syncMetadata[k] ?? defaultSyncMetadata(), ...metadata };
|
|
126
|
+
}),
|
|
127
|
+
clearEntitySyncMetadata: (type, id) => set((s) => {
|
|
128
|
+
delete s.syncMetadata[ek(type, id)];
|
|
129
|
+
}),
|
|
105
130
|
setListResult: (key, ids, meta) => set((s) => {
|
|
106
131
|
const ex = s.lists[key] ?? defaultListState();
|
|
107
132
|
s.lists[key] = { ...ex, ...meta, ids, isFetching: false, isFetchingMore: false, error: null, stale: false, lastFetched: Date.now() };
|
|
@@ -173,11 +198,717 @@ var useGraphStore = create()(
|
|
|
173
198
|
if (!base) return null;
|
|
174
199
|
const patch = s.patches[type]?.[id];
|
|
175
200
|
return patch ? { ...base, ...patch } : base;
|
|
201
|
+
},
|
|
202
|
+
readEntitySnapshot: (type, id) => {
|
|
203
|
+
const s = get();
|
|
204
|
+
const base = s.entities[type]?.[id];
|
|
205
|
+
if (!base) return null;
|
|
206
|
+
const patch = s.patches[type]?.[id];
|
|
207
|
+
const metadata = s.syncMetadata[ek(type, id)] ?? EMPTY_SYNC_METADATA;
|
|
208
|
+
return {
|
|
209
|
+
...patch ? { ...base, ...patch } : base,
|
|
210
|
+
$synced: metadata.synced,
|
|
211
|
+
$origin: metadata.origin,
|
|
212
|
+
$updatedAt: metadata.updatedAt
|
|
213
|
+
};
|
|
176
214
|
}
|
|
177
215
|
}))
|
|
178
216
|
)
|
|
179
217
|
);
|
|
180
218
|
|
|
219
|
+
// src/graph-query.ts
|
|
220
|
+
function queryOnce(opts) {
|
|
221
|
+
const store = useGraphStore.getState();
|
|
222
|
+
const ids = resolveCandidateIds(store, opts);
|
|
223
|
+
let rows = ids.map((id) => store.readEntitySnapshot(opts.type, id)).filter((row) => row !== null);
|
|
224
|
+
if (opts.where) rows = rows.filter(opts.where);
|
|
225
|
+
if (opts.sort) rows = [...rows].sort(opts.sort);
|
|
226
|
+
const projected = rows.map((row) => applySelection(projectRow(row, opts.include, store), opts.select));
|
|
227
|
+
if (opts.id) return projected[0] ?? null;
|
|
228
|
+
return projected;
|
|
229
|
+
}
|
|
230
|
+
var selectGraph = queryOnce;
|
|
231
|
+
function resolveCandidateIds(store, opts) {
|
|
232
|
+
if (opts.id) return [opts.id];
|
|
233
|
+
if (opts.ids) return opts.ids;
|
|
234
|
+
if (opts.listKey) return store.lists[opts.listKey]?.ids ?? [];
|
|
235
|
+
return Object.keys(store.entities[opts.type] ?? {});
|
|
236
|
+
}
|
|
237
|
+
function projectRow(row, include, store) {
|
|
238
|
+
if (!include) return row;
|
|
239
|
+
const projected = { ...row };
|
|
240
|
+
for (const [key, relation] of Object.entries(include)) {
|
|
241
|
+
const related = resolveRelation(row, relation, store);
|
|
242
|
+
projected[key] = related;
|
|
243
|
+
}
|
|
244
|
+
return projected;
|
|
245
|
+
}
|
|
246
|
+
function resolveRelation(entity, relation, store) {
|
|
247
|
+
const include = relation.include;
|
|
248
|
+
switch (relation.via.kind) {
|
|
249
|
+
case "field": {
|
|
250
|
+
const relatedId = entity[relation.via.field];
|
|
251
|
+
if (typeof relatedId !== "string") return null;
|
|
252
|
+
const related = store.readEntitySnapshot(relation.type, relatedId);
|
|
253
|
+
return related ? projectRow(related, include, store) : null;
|
|
254
|
+
}
|
|
255
|
+
case "array": {
|
|
256
|
+
const ids = entity[relation.via.field];
|
|
257
|
+
if (!Array.isArray(ids)) return [];
|
|
258
|
+
return ids.map((id) => typeof id === "string" ? store.readEntitySnapshot(relation.type, id) : null).filter((row) => row !== null).map((row) => projectRow(row, include, store));
|
|
259
|
+
}
|
|
260
|
+
case "list": {
|
|
261
|
+
const key = typeof relation.via.key === "function" ? relation.via.key(entity) : relation.via.key;
|
|
262
|
+
if (!key) return [];
|
|
263
|
+
const ids = store.lists[key]?.ids ?? [];
|
|
264
|
+
return ids.map((id) => store.readEntitySnapshot(relation.type, id)).filter((row) => row !== null).map((row) => projectRow(row, include, store));
|
|
265
|
+
}
|
|
266
|
+
case "resolver": {
|
|
267
|
+
const resolved = relation.via.resolve(entity, store);
|
|
268
|
+
if (Array.isArray(resolved)) {
|
|
269
|
+
return resolved.map((id) => store.readEntitySnapshot(relation.type, id)).filter((row) => row !== null).map((row) => projectRow(row, include, store));
|
|
270
|
+
}
|
|
271
|
+
if (typeof resolved !== "string") return null;
|
|
272
|
+
const related = store.readEntitySnapshot(relation.type, resolved);
|
|
273
|
+
return related ? projectRow(related, include, store) : null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function applySelection(row, select) {
|
|
278
|
+
if (!select) return row;
|
|
279
|
+
if (typeof select === "function") {
|
|
280
|
+
const result = select(row);
|
|
281
|
+
return result && typeof result === "object" ? result : { value: result };
|
|
282
|
+
}
|
|
283
|
+
const picked = {};
|
|
284
|
+
for (const key of select) {
|
|
285
|
+
if (key in row) picked[key] = row[key];
|
|
286
|
+
}
|
|
287
|
+
return picked;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/graph-actions.ts
|
|
291
|
+
var graphActionListeners = /* @__PURE__ */ new Set();
|
|
292
|
+
var graphActionReplayers = /* @__PURE__ */ new Map();
|
|
293
|
+
function createGraphTransaction() {
|
|
294
|
+
const baseline = cloneGraphData();
|
|
295
|
+
let closed = false;
|
|
296
|
+
const tx = {
|
|
297
|
+
upsertEntity(type, id, data) {
|
|
298
|
+
useGraphStore.getState().upsertEntity(type, id, data);
|
|
299
|
+
return tx;
|
|
300
|
+
},
|
|
301
|
+
replaceEntity(type, id, data) {
|
|
302
|
+
useGraphStore.getState().replaceEntity(type, id, data);
|
|
303
|
+
return tx;
|
|
304
|
+
},
|
|
305
|
+
removeEntity(type, id) {
|
|
306
|
+
useGraphStore.getState().removeEntity(type, id);
|
|
307
|
+
return tx;
|
|
308
|
+
},
|
|
309
|
+
patchEntity(type, id, patch) {
|
|
310
|
+
useGraphStore.getState().patchEntity(type, id, patch);
|
|
311
|
+
return tx;
|
|
312
|
+
},
|
|
313
|
+
clearPatch(type, id) {
|
|
314
|
+
useGraphStore.getState().clearPatch(type, id);
|
|
315
|
+
return tx;
|
|
316
|
+
},
|
|
317
|
+
insertIdInList(key, id, position) {
|
|
318
|
+
useGraphStore.getState().insertIdInList(key, id, position);
|
|
319
|
+
return tx;
|
|
320
|
+
},
|
|
321
|
+
removeIdFromAllLists(type, id) {
|
|
322
|
+
useGraphStore.getState().removeIdFromAllLists(type, id);
|
|
323
|
+
return tx;
|
|
324
|
+
},
|
|
325
|
+
setEntitySyncMetadata(type, id, metadata) {
|
|
326
|
+
useGraphStore.getState().setEntitySyncMetadata(type, id, metadata);
|
|
327
|
+
return tx;
|
|
328
|
+
},
|
|
329
|
+
markEntityPending(type, id, origin = "optimistic") {
|
|
330
|
+
useGraphStore.getState().setEntitySyncMetadata(type, id, {
|
|
331
|
+
synced: false,
|
|
332
|
+
origin,
|
|
333
|
+
updatedAt: Date.now()
|
|
334
|
+
});
|
|
335
|
+
return tx;
|
|
336
|
+
},
|
|
337
|
+
markEntitySynced(type, id, origin = "server") {
|
|
338
|
+
useGraphStore.getState().setEntitySyncMetadata(type, id, {
|
|
339
|
+
synced: true,
|
|
340
|
+
origin,
|
|
341
|
+
updatedAt: Date.now()
|
|
342
|
+
});
|
|
343
|
+
return tx;
|
|
344
|
+
},
|
|
345
|
+
commit() {
|
|
346
|
+
closed = true;
|
|
347
|
+
},
|
|
348
|
+
rollback() {
|
|
349
|
+
if (closed) return;
|
|
350
|
+
useGraphStore.setState(cloneGraphData(baseline));
|
|
351
|
+
closed = true;
|
|
352
|
+
},
|
|
353
|
+
snapshot() {
|
|
354
|
+
return cloneGraphData();
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
return tx;
|
|
358
|
+
}
|
|
359
|
+
function createGraphAction(opts) {
|
|
360
|
+
if (opts.key) {
|
|
361
|
+
graphActionReplayers.set(opts.key, async (record) => {
|
|
362
|
+
const tx = createGraphTransaction();
|
|
363
|
+
try {
|
|
364
|
+
const result = await opts.run(tx, record.input);
|
|
365
|
+
tx.commit();
|
|
366
|
+
return result;
|
|
367
|
+
} catch (error) {
|
|
368
|
+
tx.rollback();
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
return async (input) => {
|
|
374
|
+
const tx = createGraphTransaction();
|
|
375
|
+
const record = opts.key ? {
|
|
376
|
+
id: `${opts.key}:${Date.now()}`,
|
|
377
|
+
key: opts.key,
|
|
378
|
+
input: structuredClone(input),
|
|
379
|
+
enqueuedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
380
|
+
} : null;
|
|
381
|
+
try {
|
|
382
|
+
if (record) emitGraphActionEvent({ type: "enqueued", record });
|
|
383
|
+
opts.optimistic?.(tx, input);
|
|
384
|
+
const result = await opts.run(tx, input);
|
|
385
|
+
opts.onSuccess?.(result, input, tx);
|
|
386
|
+
tx.commit();
|
|
387
|
+
if (record) emitGraphActionEvent({ type: "settled", record });
|
|
388
|
+
return result;
|
|
389
|
+
} catch (error) {
|
|
390
|
+
tx.rollback();
|
|
391
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
392
|
+
if (record) emitGraphActionEvent({ type: "settled", record });
|
|
393
|
+
opts.onError?.(normalized, input);
|
|
394
|
+
throw normalized;
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function subscribeGraphActionEvents(listener) {
|
|
399
|
+
graphActionListeners.add(listener);
|
|
400
|
+
return () => graphActionListeners.delete(listener);
|
|
401
|
+
}
|
|
402
|
+
async function replayRegisteredGraphAction(record) {
|
|
403
|
+
const replayer = graphActionReplayers.get(record.key);
|
|
404
|
+
if (!replayer) throw new Error(`No graph action registered for key "${record.key}"`);
|
|
405
|
+
return replayer(record);
|
|
406
|
+
}
|
|
407
|
+
function cloneGraphData(source = useGraphStore.getState()) {
|
|
408
|
+
return {
|
|
409
|
+
entities: structuredClone(source.entities),
|
|
410
|
+
patches: structuredClone(source.patches),
|
|
411
|
+
entityStates: structuredClone(source.entityStates),
|
|
412
|
+
syncMetadata: structuredClone(source.syncMetadata),
|
|
413
|
+
lists: structuredClone(source.lists)
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function emitGraphActionEvent(event) {
|
|
417
|
+
for (const listener of graphActionListeners) listener(event);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/graph-effects.ts
|
|
421
|
+
function createGraphEffect(opts) {
|
|
422
|
+
const getKey = opts.getKey ?? defaultGetKey;
|
|
423
|
+
const isEqual = opts.isEqual ?? defaultIsEqual;
|
|
424
|
+
let initialized = false;
|
|
425
|
+
let previous = /* @__PURE__ */ new Map();
|
|
426
|
+
const evaluate = () => {
|
|
427
|
+
const nextValues = normalizeQueryResult(opts.query());
|
|
428
|
+
const next = /* @__PURE__ */ new Map();
|
|
429
|
+
nextValues.forEach((value, index) => {
|
|
430
|
+
next.set(getKey(value, index), value);
|
|
431
|
+
});
|
|
432
|
+
if (!initialized) {
|
|
433
|
+
initialized = true;
|
|
434
|
+
previous = next;
|
|
435
|
+
if (opts.skipInitial) return;
|
|
436
|
+
}
|
|
437
|
+
for (const [key, value] of next.entries()) {
|
|
438
|
+
const previousValue = previous.get(key);
|
|
439
|
+
if (previousValue === void 0) {
|
|
440
|
+
opts.onEnter?.({ key, value });
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (!isEqual(previousValue, value)) {
|
|
444
|
+
opts.onUpdate?.({ key, value, previousValue });
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
for (const [key, previousValue] of previous.entries()) {
|
|
448
|
+
if (!next.has(key)) opts.onExit?.({ key, previousValue });
|
|
449
|
+
}
|
|
450
|
+
previous = next;
|
|
451
|
+
};
|
|
452
|
+
evaluate();
|
|
453
|
+
const unsubscribe = useGraphStore.subscribe(() => {
|
|
454
|
+
evaluate();
|
|
455
|
+
});
|
|
456
|
+
return {
|
|
457
|
+
dispose: () => {
|
|
458
|
+
unsubscribe();
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function normalizeQueryResult(value) {
|
|
463
|
+
if (value == null) return [];
|
|
464
|
+
return Array.isArray(value) ? value : [value];
|
|
465
|
+
}
|
|
466
|
+
function defaultGetKey(value, index) {
|
|
467
|
+
if (value && typeof value === "object") {
|
|
468
|
+
const record = value;
|
|
469
|
+
if (typeof record.id === "string") return record.id;
|
|
470
|
+
if (typeof record.$key === "string") return record.$key;
|
|
471
|
+
}
|
|
472
|
+
return String(index);
|
|
473
|
+
}
|
|
474
|
+
function defaultIsEqual(previousValue, nextValue) {
|
|
475
|
+
return JSON.stringify(previousValue) === JSON.stringify(nextValue);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/object-path.ts
|
|
479
|
+
function isObject(value) {
|
|
480
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
481
|
+
}
|
|
482
|
+
function getValueAtPath(source, path) {
|
|
483
|
+
if (!path) return source;
|
|
484
|
+
const segments = path.split(".").filter(Boolean);
|
|
485
|
+
let current = source;
|
|
486
|
+
for (const segment of segments) {
|
|
487
|
+
if (!isObject(current) && !Array.isArray(current)) return void 0;
|
|
488
|
+
current = current[segment];
|
|
489
|
+
}
|
|
490
|
+
return current;
|
|
491
|
+
}
|
|
492
|
+
function setValueAtPath(source, path, value) {
|
|
493
|
+
const segments = path.split(".").filter(Boolean);
|
|
494
|
+
if (segments.length === 0) return source;
|
|
495
|
+
const clone = structuredClone(source);
|
|
496
|
+
let current = clone;
|
|
497
|
+
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
498
|
+
const segment = segments[index];
|
|
499
|
+
const next = current[segment];
|
|
500
|
+
if (!isObject(next)) current[segment] = {};
|
|
501
|
+
current = current[segment];
|
|
502
|
+
}
|
|
503
|
+
current[segments[segments.length - 1]] = value;
|
|
504
|
+
return clone;
|
|
505
|
+
}
|
|
506
|
+
function collectDirtyPaths(current, original, prefix = "", acc = /* @__PURE__ */ new Set()) {
|
|
507
|
+
if (isObject(current) && isObject(original)) {
|
|
508
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(current), ...Object.keys(original)]);
|
|
509
|
+
for (const key of keys) {
|
|
510
|
+
const nextPrefix = prefix ? `${prefix}.${key}` : key;
|
|
511
|
+
collectDirtyPaths(current[key], original[key], nextPrefix, acc);
|
|
512
|
+
}
|
|
513
|
+
return acc;
|
|
514
|
+
}
|
|
515
|
+
if (JSON.stringify(current) !== JSON.stringify(original) && prefix) acc.add(prefix);
|
|
516
|
+
return acc;
|
|
517
|
+
}
|
|
518
|
+
var schemaRegistry = /* @__PURE__ */ new Map();
|
|
519
|
+
function registerEntityJsonSchema(config) {
|
|
520
|
+
const key = registryKey(config.entityType, config.field, config.schemaId);
|
|
521
|
+
schemaRegistry.set(key, config);
|
|
522
|
+
}
|
|
523
|
+
function registerRuntimeSchema(config) {
|
|
524
|
+
registerEntityJsonSchema(config);
|
|
525
|
+
}
|
|
526
|
+
function getEntityJsonSchema(opts) {
|
|
527
|
+
const exact = schemaRegistry.get(registryKey(opts.entityType, opts.field, opts.schemaId));
|
|
528
|
+
if (exact) return exact;
|
|
529
|
+
if (opts.field) {
|
|
530
|
+
const byField = schemaRegistry.get(registryKey(opts.entityType, opts.field));
|
|
531
|
+
if (byField) return byField;
|
|
532
|
+
}
|
|
533
|
+
if (opts.schemaId) {
|
|
534
|
+
const byId = schemaRegistry.get(registryKey(opts.entityType, void 0, opts.schemaId));
|
|
535
|
+
if (byId) return byId;
|
|
536
|
+
}
|
|
537
|
+
for (const schema of schemaRegistry.values()) {
|
|
538
|
+
if (schema.entityType !== opts.entityType) continue;
|
|
539
|
+
if (opts.field && schema.field !== opts.field) continue;
|
|
540
|
+
return schema;
|
|
541
|
+
}
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
function useSchemaEntityFields(opts) {
|
|
545
|
+
return useMemo(() => {
|
|
546
|
+
const schema = opts.schema ?? getEntityJsonSchema(opts)?.schema;
|
|
547
|
+
if (!schema) return [];
|
|
548
|
+
return buildEntityFieldsFromSchema({ schema, rootField: opts.rootField ?? opts.field });
|
|
549
|
+
}, [opts.entityType, opts.field, opts.rootField, opts.schemaId, opts.schema]);
|
|
550
|
+
}
|
|
551
|
+
function buildEntityFieldsFromSchema(opts) {
|
|
552
|
+
return buildSchemaFields(opts.schema, opts.rootField ?? "", "");
|
|
553
|
+
}
|
|
554
|
+
function exportGraphSnapshotWithSchemas(opts) {
|
|
555
|
+
return JSON.stringify(
|
|
556
|
+
{
|
|
557
|
+
scope: opts.scope,
|
|
558
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
559
|
+
data: opts.data,
|
|
560
|
+
schemas: opts.schemas.filter(Boolean)
|
|
561
|
+
},
|
|
562
|
+
null,
|
|
563
|
+
opts.pretty === false ? 0 : 2
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
function escapeHtml(input) {
|
|
567
|
+
return input.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
568
|
+
}
|
|
569
|
+
function renderMarkdownToHtml(value) {
|
|
570
|
+
const escaped = escapeHtml(value);
|
|
571
|
+
const blocks = escaped.split(/\n{2,}/).map((block) => block.trim()).filter(Boolean);
|
|
572
|
+
return blocks.map((block) => renderMarkdownBlock(block)).join("");
|
|
573
|
+
}
|
|
574
|
+
function MarkdownFieldRenderer({ value, className }) {
|
|
575
|
+
return /* @__PURE__ */ jsx(
|
|
576
|
+
"div",
|
|
577
|
+
{
|
|
578
|
+
className,
|
|
579
|
+
dangerouslySetInnerHTML: { __html: renderMarkdownToHtml(value ?? "") }
|
|
580
|
+
}
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
function MarkdownFieldEditor({
|
|
584
|
+
value,
|
|
585
|
+
onChange,
|
|
586
|
+
placeholder
|
|
587
|
+
}) {
|
|
588
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
589
|
+
/* @__PURE__ */ jsx(
|
|
590
|
+
"textarea",
|
|
591
|
+
{
|
|
592
|
+
value,
|
|
593
|
+
onChange: (event) => onChange(event.target.value),
|
|
594
|
+
placeholder,
|
|
595
|
+
className: "w-full min-h-[120px] rounded-md border bg-muted/50 px-3 py-2 text-sm resize-y focus:outline-none focus:ring-1 focus:ring-ring transition-colors"
|
|
596
|
+
}
|
|
597
|
+
),
|
|
598
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-md border bg-background px-3 py-2", children: /* @__PURE__ */ jsx(MarkdownFieldRenderer, { value, className: "prose prose-sm max-w-none" }) })
|
|
599
|
+
] });
|
|
600
|
+
}
|
|
601
|
+
function createMarkdownDetailRenderer(field) {
|
|
602
|
+
return (value, entity) => /* @__PURE__ */ jsx(MarkdownFieldRenderer, { value: String(value ?? getValueAtPath(entity, field) ?? ""), className: "prose prose-sm max-w-none" });
|
|
603
|
+
}
|
|
604
|
+
function buildSchemaFields(schema, pathPrefix, schemaPathPrefix) {
|
|
605
|
+
if (schema.type === "object" && schema.properties) {
|
|
606
|
+
const entries = Object.entries(schema.properties).sort(([, left], [, right]) => {
|
|
607
|
+
const l = left["x-display-order"] ?? Number.MAX_SAFE_INTEGER;
|
|
608
|
+
const r = right["x-display-order"] ?? Number.MAX_SAFE_INTEGER;
|
|
609
|
+
return l - r;
|
|
610
|
+
});
|
|
611
|
+
return entries.flatMap(([key, childSchema]) => {
|
|
612
|
+
if (childSchema["x-hidden"]) return [];
|
|
613
|
+
const field = pathPrefix ? `${pathPrefix}.${key}` : key;
|
|
614
|
+
const schemaPath = schemaPathPrefix ? `${schemaPathPrefix}.${key}` : key;
|
|
615
|
+
if (childSchema.type === "object" && childSchema.properties) {
|
|
616
|
+
return buildSchemaFields(childSchema, field, schemaPath);
|
|
617
|
+
}
|
|
618
|
+
return [schemaField(field, schemaPath, childSchema, schema.required?.includes(key) ?? false)];
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
return [];
|
|
622
|
+
}
|
|
623
|
+
function schemaField(field, schemaPath, schema, required) {
|
|
624
|
+
const type = inferFieldType(schema);
|
|
625
|
+
const descriptor = {
|
|
626
|
+
field,
|
|
627
|
+
label: schema.title ?? humanize(field.split(".").pop() ?? field),
|
|
628
|
+
type,
|
|
629
|
+
required,
|
|
630
|
+
hint: schema.description,
|
|
631
|
+
schemaPath,
|
|
632
|
+
schema,
|
|
633
|
+
componentHint: schema["x-a2ui-component"]
|
|
634
|
+
};
|
|
635
|
+
if (schema.enum) {
|
|
636
|
+
descriptor.options = schema.enum.map((value) => ({
|
|
637
|
+
value: String(value),
|
|
638
|
+
label: String(value)
|
|
639
|
+
}));
|
|
640
|
+
}
|
|
641
|
+
if (type === "markdown") {
|
|
642
|
+
descriptor.render = createMarkdownDetailRenderer(field);
|
|
643
|
+
}
|
|
644
|
+
return descriptor;
|
|
645
|
+
}
|
|
646
|
+
function inferFieldType(schema) {
|
|
647
|
+
const forced = schema["x-field-type"];
|
|
648
|
+
if (forced === "markdown") return "markdown";
|
|
649
|
+
if (schema.format === "markdown") return "markdown";
|
|
650
|
+
if (schema.enum) return "enum";
|
|
651
|
+
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
652
|
+
switch (type) {
|
|
653
|
+
case "boolean":
|
|
654
|
+
return "boolean";
|
|
655
|
+
case "integer":
|
|
656
|
+
case "number":
|
|
657
|
+
return "number";
|
|
658
|
+
case "string":
|
|
659
|
+
if (schema.format === "email") return "email";
|
|
660
|
+
if (schema.format === "uri" || schema.format === "url") return "url";
|
|
661
|
+
if (schema.format === "date" || schema.format === "date-time") return "date";
|
|
662
|
+
return "text";
|
|
663
|
+
case "array":
|
|
664
|
+
case "object":
|
|
665
|
+
return "json";
|
|
666
|
+
default:
|
|
667
|
+
return "text";
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function registryKey(entityType, field, schemaId) {
|
|
671
|
+
return `${entityType}::${field ?? "*"}::${schemaId ?? "*"}`;
|
|
672
|
+
}
|
|
673
|
+
function humanize(value) {
|
|
674
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
675
|
+
}
|
|
676
|
+
function renderMarkdownBlock(block) {
|
|
677
|
+
if (block.startsWith("# ")) return `<h1>${renderInlineMarkdown(block.slice(2))}</h1>`;
|
|
678
|
+
if (block.startsWith("## ")) return `<h2>${renderInlineMarkdown(block.slice(3))}</h2>`;
|
|
679
|
+
return `<p>${renderInlineMarkdown(block).replaceAll("\n", "<br/>")}</p>`;
|
|
680
|
+
}
|
|
681
|
+
function renderInlineMarkdown(block) {
|
|
682
|
+
return block.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// src/ai-interop.ts
|
|
686
|
+
function exportGraphSnapshot(opts) {
|
|
687
|
+
const payload = {
|
|
688
|
+
scope: opts.scope,
|
|
689
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
690
|
+
data: opts.data
|
|
691
|
+
};
|
|
692
|
+
return JSON.stringify(payload, null, opts.pretty === false ? 0 : 2);
|
|
693
|
+
}
|
|
694
|
+
function createGraphTool(handler) {
|
|
695
|
+
return (input) => handler(input, {
|
|
696
|
+
store: useGraphStore.getState(),
|
|
697
|
+
queryOnce,
|
|
698
|
+
exportGraphSnapshot
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
function createSchemaGraphTool(handler) {
|
|
702
|
+
return (input) => handler(input, {
|
|
703
|
+
store: useGraphStore.getState(),
|
|
704
|
+
queryOnce,
|
|
705
|
+
exportGraphSnapshot,
|
|
706
|
+
getEntityJsonSchema,
|
|
707
|
+
exportGraphSnapshotWithSchemas
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
var DEFAULT_STORAGE_KEY = "prometheus:graph";
|
|
711
|
+
var useGraphSyncStatusStore = create((set) => ({
|
|
712
|
+
status: {
|
|
713
|
+
phase: "idle",
|
|
714
|
+
isOnline: true,
|
|
715
|
+
isSynced: true,
|
|
716
|
+
pendingActions: 0,
|
|
717
|
+
lastHydratedAt: null,
|
|
718
|
+
lastPersistedAt: null,
|
|
719
|
+
storageKey: null,
|
|
720
|
+
error: null
|
|
721
|
+
},
|
|
722
|
+
setStatus: (status) => set((state) => ({
|
|
723
|
+
status: {
|
|
724
|
+
...state.status,
|
|
725
|
+
...status
|
|
726
|
+
}
|
|
727
|
+
}))
|
|
728
|
+
}));
|
|
729
|
+
var pendingActions = /* @__PURE__ */ new Map();
|
|
730
|
+
function useGraphSyncStatus() {
|
|
731
|
+
return useGraphSyncStatusStore((state) => state.status);
|
|
732
|
+
}
|
|
733
|
+
async function persistGraphToStorage(opts) {
|
|
734
|
+
const payload = {
|
|
735
|
+
version: 1,
|
|
736
|
+
snapshot: cloneGraphSnapshot(),
|
|
737
|
+
pendingActions: opts.pendingActions ?? Array.from(pendingActions.values())
|
|
738
|
+
};
|
|
739
|
+
const json = JSON.stringify(payload);
|
|
740
|
+
await opts.storage.set(opts.key, json);
|
|
741
|
+
const persistedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
742
|
+
useGraphSyncStatusStore.getState().setStatus({
|
|
743
|
+
lastPersistedAt: persistedAt,
|
|
744
|
+
storageKey: opts.key,
|
|
745
|
+
pendingActions: payload.pendingActions.length
|
|
746
|
+
});
|
|
747
|
+
return {
|
|
748
|
+
ok: true,
|
|
749
|
+
key: opts.key,
|
|
750
|
+
bytes: json.length,
|
|
751
|
+
persistedAt
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
async function hydrateGraphFromStorage(opts) {
|
|
755
|
+
const raw = await opts.storage.get(opts.key);
|
|
756
|
+
if (!raw) {
|
|
757
|
+
return {
|
|
758
|
+
ok: false,
|
|
759
|
+
key: opts.key,
|
|
760
|
+
hydratedAt: null,
|
|
761
|
+
entityCounts: {},
|
|
762
|
+
error: "No persisted graph snapshot found"
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
try {
|
|
766
|
+
const parsed = JSON.parse(raw);
|
|
767
|
+
useGraphStore.setState(parsed.snapshot);
|
|
768
|
+
pendingActions.clear();
|
|
769
|
+
for (const action of parsed.pendingActions ?? []) pendingActions.set(action.id, action);
|
|
770
|
+
const hydratedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
771
|
+
useGraphSyncStatusStore.getState().setStatus({
|
|
772
|
+
lastHydratedAt: hydratedAt,
|
|
773
|
+
storageKey: opts.key,
|
|
774
|
+
pendingActions: pendingActions.size,
|
|
775
|
+
error: null
|
|
776
|
+
});
|
|
777
|
+
return {
|
|
778
|
+
ok: true,
|
|
779
|
+
key: opts.key,
|
|
780
|
+
hydratedAt,
|
|
781
|
+
entityCounts: Object.fromEntries(
|
|
782
|
+
Object.entries(parsed.snapshot.entities).map(([type, entities]) => [type, Object.keys(entities).length])
|
|
783
|
+
),
|
|
784
|
+
pendingActions: Array.from(pendingActions.values())
|
|
785
|
+
};
|
|
786
|
+
} catch (error) {
|
|
787
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
788
|
+
useGraphSyncStatusStore.getState().setStatus({
|
|
789
|
+
phase: "error",
|
|
790
|
+
error: message,
|
|
791
|
+
storageKey: opts.key
|
|
792
|
+
});
|
|
793
|
+
return {
|
|
794
|
+
ok: false,
|
|
795
|
+
key: opts.key,
|
|
796
|
+
hydratedAt: null,
|
|
797
|
+
entityCounts: {},
|
|
798
|
+
error: message
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
function startLocalFirstGraph(opts) {
|
|
803
|
+
const key = opts.key ?? DEFAULT_STORAGE_KEY;
|
|
804
|
+
const persistDebounceMs = opts.persistDebounceMs ?? 50;
|
|
805
|
+
const statusStore = useGraphSyncStatusStore.getState();
|
|
806
|
+
statusStore.setStatus({
|
|
807
|
+
phase: "hydrating",
|
|
808
|
+
storageKey: key,
|
|
809
|
+
isOnline: opts.onlineSource?.getIsOnline() ?? getDefaultOnlineSource().getIsOnline(),
|
|
810
|
+
isSynced: pendingActions.size === 0,
|
|
811
|
+
error: null
|
|
812
|
+
});
|
|
813
|
+
let persistTimer = null;
|
|
814
|
+
const schedulePersist = () => {
|
|
815
|
+
if (persistTimer) clearTimeout(persistTimer);
|
|
816
|
+
persistTimer = setTimeout(() => {
|
|
817
|
+
void persistGraphToStorage({ storage: opts.storage, key });
|
|
818
|
+
}, persistDebounceMs);
|
|
819
|
+
};
|
|
820
|
+
const graphUnsub = useGraphStore.subscribe(() => {
|
|
821
|
+
schedulePersist();
|
|
822
|
+
});
|
|
823
|
+
const actionUnsub = subscribeGraphActionEvents((event) => {
|
|
824
|
+
if (event.type === "enqueued") pendingActions.set(event.record.id, event.record);
|
|
825
|
+
if (event.type === "settled") pendingActions.delete(event.record.id);
|
|
826
|
+
useGraphSyncStatusStore.getState().setStatus({
|
|
827
|
+
pendingActions: pendingActions.size,
|
|
828
|
+
isSynced: pendingActions.size === 0
|
|
829
|
+
});
|
|
830
|
+
schedulePersist();
|
|
831
|
+
});
|
|
832
|
+
const onlineSource = opts.onlineSource ?? getDefaultOnlineSource();
|
|
833
|
+
const onlineUnsub = onlineSource.subscribe((online) => {
|
|
834
|
+
useGraphSyncStatusStore.getState().setStatus({
|
|
835
|
+
isOnline: online,
|
|
836
|
+
phase: online ? "ready" : "offline"
|
|
837
|
+
});
|
|
838
|
+
});
|
|
839
|
+
const ready = (async () => {
|
|
840
|
+
const hydrated = await hydrateGraphFromStorage({ storage: opts.storage, key });
|
|
841
|
+
if (opts.replayPendingActions && hydrated.ok && pendingActions.size > 0) {
|
|
842
|
+
useGraphSyncStatusStore.getState().setStatus({
|
|
843
|
+
phase: "syncing",
|
|
844
|
+
isSynced: false
|
|
845
|
+
});
|
|
846
|
+
for (const action of Array.from(pendingActions.values())) {
|
|
847
|
+
await replayRegisteredGraphAction(action);
|
|
848
|
+
pendingActions.delete(action.id);
|
|
849
|
+
}
|
|
850
|
+
await persistGraphToStorage({ storage: opts.storage, key });
|
|
851
|
+
}
|
|
852
|
+
const online = onlineSource.getIsOnline();
|
|
853
|
+
useGraphSyncStatusStore.getState().setStatus({
|
|
854
|
+
phase: online ? "ready" : "offline",
|
|
855
|
+
isOnline: online,
|
|
856
|
+
isSynced: pendingActions.size === 0,
|
|
857
|
+
pendingActions: pendingActions.size
|
|
858
|
+
});
|
|
859
|
+
})();
|
|
860
|
+
return {
|
|
861
|
+
ready,
|
|
862
|
+
dispose() {
|
|
863
|
+
graphUnsub();
|
|
864
|
+
actionUnsub();
|
|
865
|
+
onlineUnsub();
|
|
866
|
+
if (persistTimer) clearTimeout(persistTimer);
|
|
867
|
+
},
|
|
868
|
+
async persistNow() {
|
|
869
|
+
await persistGraphToStorage({ storage: opts.storage, key });
|
|
870
|
+
},
|
|
871
|
+
hydrate() {
|
|
872
|
+
return hydrateGraphFromStorage({ storage: opts.storage, key });
|
|
873
|
+
},
|
|
874
|
+
getStatus() {
|
|
875
|
+
return useGraphSyncStatusStore.getState().status;
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
function cloneGraphSnapshot() {
|
|
880
|
+
const state = useGraphStore.getState();
|
|
881
|
+
return {
|
|
882
|
+
entities: structuredClone(state.entities),
|
|
883
|
+
patches: structuredClone(state.patches),
|
|
884
|
+
entityStates: structuredClone(state.entityStates),
|
|
885
|
+
syncMetadata: structuredClone(state.syncMetadata),
|
|
886
|
+
lists: structuredClone(state.lists)
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
function getDefaultOnlineSource() {
|
|
890
|
+
if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
|
|
891
|
+
return {
|
|
892
|
+
getIsOnline: () => window.navigator.onLine,
|
|
893
|
+
subscribe: (listener) => {
|
|
894
|
+
const onlineHandler = () => listener(true);
|
|
895
|
+
const offlineHandler = () => listener(false);
|
|
896
|
+
window.addEventListener("online", onlineHandler);
|
|
897
|
+
window.addEventListener("offline", offlineHandler);
|
|
898
|
+
return () => {
|
|
899
|
+
window.removeEventListener("online", onlineHandler);
|
|
900
|
+
window.removeEventListener("offline", offlineHandler);
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
return {
|
|
906
|
+
getIsOnline: () => true,
|
|
907
|
+
subscribe: () => () => {
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
|
|
181
912
|
// src/engine.ts
|
|
182
913
|
function serializeKey(key) {
|
|
183
914
|
return JSON.stringify(key, (_, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).sort()) : v);
|
|
@@ -445,10 +1176,7 @@ function useEntity(opts) {
|
|
|
445
1176
|
normalizeRef.current = opts.normalize;
|
|
446
1177
|
const data = useStore(useGraphStore, useShallow((state) => {
|
|
447
1178
|
if (!id) return null;
|
|
448
|
-
|
|
449
|
-
if (!base) return null;
|
|
450
|
-
const patch = state.patches[type]?.[id];
|
|
451
|
-
return patch ? { ...base, ...patch } : base;
|
|
1179
|
+
return state.readEntitySnapshot(type, id);
|
|
452
1180
|
}));
|
|
453
1181
|
const entityState = useStore(useGraphStore, useCallback(
|
|
454
1182
|
(state) => state.entityStates[`${type}:${id}`] ?? EMPTY_ENTITY_STATE,
|
|
@@ -486,12 +1214,7 @@ function useEntityList(opts) {
|
|
|
486
1214
|
useGraphStore,
|
|
487
1215
|
useShallow((state) => {
|
|
488
1216
|
const ids = state.lists[key]?.ids ?? EMPTY_IDS;
|
|
489
|
-
return ids.map((id) =>
|
|
490
|
-
const base = state.entities[type]?.[id];
|
|
491
|
-
if (!base) return null;
|
|
492
|
-
const patch = state.patches[type]?.[id];
|
|
493
|
-
return patch ? { ...base, ...patch } : base;
|
|
494
|
-
}).filter((x) => x !== null);
|
|
1217
|
+
return ids.map((id) => state.readEntitySnapshot(type, id)).filter((x) => x !== null);
|
|
495
1218
|
})
|
|
496
1219
|
);
|
|
497
1220
|
const doFetch = useCallback((params = {}) => {
|
|
@@ -528,18 +1251,28 @@ function useEntityMutation(opts) {
|
|
|
528
1251
|
const { id, patch } = opt;
|
|
529
1252
|
const store = useGraphStore.getState();
|
|
530
1253
|
const previous = { ...store.patches[type]?.[id] };
|
|
1254
|
+
const previousSync = store.syncMetadata[`${type}:${id}`];
|
|
531
1255
|
store.patchEntity(type, id, patch);
|
|
532
|
-
|
|
1256
|
+
store.setEntitySyncMetadata(type, id, { synced: false, origin: "optimistic", updatedAt: Date.now() });
|
|
1257
|
+
rollback = () => {
|
|
1258
|
+
const currentStore = useGraphStore.getState();
|
|
1259
|
+
if (Object.keys(previous).length > 0) currentStore.patchEntity(type, id, previous);
|
|
1260
|
+
else currentStore.clearPatch(type, id);
|
|
1261
|
+
if (previousSync) currentStore.setEntitySyncMetadata(type, id, previousSync);
|
|
1262
|
+
else currentStore.clearEntitySyncMetadata(type, id);
|
|
1263
|
+
};
|
|
533
1264
|
}
|
|
534
1265
|
}
|
|
535
1266
|
try {
|
|
536
1267
|
const result = await apiFn(input);
|
|
537
1268
|
if (normalize) {
|
|
538
1269
|
const { id, data } = normalize(result, input);
|
|
539
|
-
useGraphStore.getState()
|
|
1270
|
+
const store = useGraphStore.getState();
|
|
1271
|
+
store.upsertEntity(type, id, data);
|
|
1272
|
+
store.setEntitySyncMetadata(type, id, { synced: true, origin: "server", updatedAt: Date.now() });
|
|
540
1273
|
if (optimistic) {
|
|
541
1274
|
const opt = optimistic(input);
|
|
542
|
-
if (opt)
|
|
1275
|
+
if (opt) store.clearPatch(type, opt.id);
|
|
543
1276
|
}
|
|
544
1277
|
}
|
|
545
1278
|
if (invalidateLists) for (const k of invalidateLists) useGraphStore.getState().invalidateLists(k);
|
|
@@ -1041,12 +1774,7 @@ function useEntityView(opts) {
|
|
|
1041
1774
|
useShallow((state) => {
|
|
1042
1775
|
const list = state.lists[baseKey] ?? EMPTY_LIST_STATE;
|
|
1043
1776
|
const sourceIds = completenessMode !== "remote" && remoteResultKey ? state.lists[remoteResultKey]?.ids ?? EMPTY_IDS : list.ids;
|
|
1044
|
-
const getEntity = (id) =>
|
|
1045
|
-
const base = state.entities[type]?.[id];
|
|
1046
|
-
if (!base) return null;
|
|
1047
|
-
const patch = state.patches[type]?.[id];
|
|
1048
|
-
return patch ? { ...base, ...patch } : base;
|
|
1049
|
-
};
|
|
1777
|
+
const getEntity = (id) => state.readEntitySnapshot(type, id);
|
|
1050
1778
|
return applyView(
|
|
1051
1779
|
sourceIds,
|
|
1052
1780
|
getEntity,
|
|
@@ -1059,12 +1787,7 @@ function useEntityView(opts) {
|
|
|
1059
1787
|
const items = useStore(
|
|
1060
1788
|
useGraphStore,
|
|
1061
1789
|
useShallow(
|
|
1062
|
-
(state) => localViewIds.map((id) =>
|
|
1063
|
-
const base = state.entities[type]?.[id];
|
|
1064
|
-
if (!base) return null;
|
|
1065
|
-
const patch = state.patches[type]?.[id];
|
|
1066
|
-
return patch ? { ...base, ...patch } : base;
|
|
1067
|
-
}).filter((item) => item !== null)
|
|
1790
|
+
(state) => localViewIds.map((id) => state.readEntitySnapshot(type, id)).filter((item) => item !== null)
|
|
1068
1791
|
)
|
|
1069
1792
|
);
|
|
1070
1793
|
const fireRemoteFetch = useCallback(async (view, cursor) => {
|
|
@@ -1119,17 +1842,11 @@ function useEntityView(opts) {
|
|
|
1119
1842
|
const isPresent = id in newEntities;
|
|
1120
1843
|
if (!isPresent) continue;
|
|
1121
1844
|
const entity = newEntities[id];
|
|
1122
|
-
const
|
|
1123
|
-
const merged = patch ? { ...entity, ...patch } : entity;
|
|
1845
|
+
const merged = store.readEntitySnapshot(type, id) ?? entity;
|
|
1124
1846
|
const matches = (!view.filter || matchesFilter(merged, view.filter)) && (!view.search?.query || matchesSearch(merged, view.search.query, view.search.fields));
|
|
1125
1847
|
if (matches && !list.ids.includes(id)) {
|
|
1126
1848
|
if (view.sort && view.sort.length > 0) {
|
|
1127
|
-
const idx = findInsertionIndex(merged, list.ids, (eid) =>
|
|
1128
|
-
const b = store.entities[type]?.[eid];
|
|
1129
|
-
if (!b) return null;
|
|
1130
|
-
const p = store.patches[type]?.[eid];
|
|
1131
|
-
return p ? { ...b, ...p } : b;
|
|
1132
|
-
}, view.sort);
|
|
1849
|
+
const idx = findInsertionIndex(merged, list.ids, (eid) => store.readEntitySnapshot(type, eid), view.sort);
|
|
1133
1850
|
store.insertIdInList(baseKey, id, idx);
|
|
1134
1851
|
} else store.insertIdInList(baseKey, id, "start");
|
|
1135
1852
|
}
|
|
@@ -1172,15 +1889,15 @@ function useEntityView(opts) {
|
|
|
1172
1889
|
}
|
|
1173
1890
|
|
|
1174
1891
|
// src/crud/relations.ts
|
|
1175
|
-
var
|
|
1892
|
+
var schemaRegistry2 = /* @__PURE__ */ new Map();
|
|
1176
1893
|
function registerSchema(schema) {
|
|
1177
|
-
|
|
1894
|
+
schemaRegistry2.set(schema.type, schema);
|
|
1178
1895
|
}
|
|
1179
1896
|
function getSchema(type) {
|
|
1180
|
-
return
|
|
1897
|
+
return schemaRegistry2.get(type) ?? null;
|
|
1181
1898
|
}
|
|
1182
1899
|
function cascadeInvalidation(ctx) {
|
|
1183
|
-
const schema =
|
|
1900
|
+
const schema = schemaRegistry2.get(ctx.type);
|
|
1184
1901
|
if (!schema) return;
|
|
1185
1902
|
const store = useGraphStore.getState();
|
|
1186
1903
|
if (schema.globalListKeys) for (const key of schema.globalListKeys) store.invalidateLists(key);
|
|
@@ -1207,7 +1924,7 @@ function cascadeInvalidation(ctx) {
|
|
|
1207
1924
|
}
|
|
1208
1925
|
}
|
|
1209
1926
|
}
|
|
1210
|
-
for (const [, otherSchema] of
|
|
1927
|
+
for (const [, otherSchema] of schemaRegistry2) {
|
|
1211
1928
|
if (!otherSchema.relations) continue;
|
|
1212
1929
|
for (const [, rel] of Object.entries(otherSchema.relations)) {
|
|
1213
1930
|
if (rel.targetType !== ctx.type) continue;
|
|
@@ -1216,7 +1933,7 @@ function cascadeInvalidation(ctx) {
|
|
|
1216
1933
|
}
|
|
1217
1934
|
}
|
|
1218
1935
|
function readRelations(type, entity) {
|
|
1219
|
-
const schema =
|
|
1936
|
+
const schema = schemaRegistry2.get(type);
|
|
1220
1937
|
if (!schema?.relations) return {};
|
|
1221
1938
|
const store = useGraphStore.getState();
|
|
1222
1939
|
const result = {};
|
|
@@ -1277,7 +1994,7 @@ function useEntityCRUD(opts) {
|
|
|
1277
1994
|
useEffect(() => {
|
|
1278
1995
|
if (detail) setEditBuffer({ ...detail });
|
|
1279
1996
|
}, [selectedId]);
|
|
1280
|
-
const setField = useCallback((field, value) => setEditBuffer((prev) => (
|
|
1997
|
+
const setField = useCallback((field, value) => setEditBuffer((prev) => setValueAtPath(prev, String(field), value)), []);
|
|
1281
1998
|
const setFields = useCallback((fields) => setEditBuffer((prev) => ({ ...prev, ...fields })), []);
|
|
1282
1999
|
const resetBuffer = useCallback(() => {
|
|
1283
2000
|
const current = selectedId ? useGraphStore.getState().readEntity(type, selectedId) : null;
|
|
@@ -1285,10 +2002,7 @@ function useEntityCRUD(opts) {
|
|
|
1285
2002
|
}, [type, selectedId]);
|
|
1286
2003
|
const dirty = useMemo(() => {
|
|
1287
2004
|
if (!detail) return { changed: /* @__PURE__ */ new Set(), isDirty: false };
|
|
1288
|
-
const changed =
|
|
1289
|
-
for (const key of Object.keys(editBuffer)) {
|
|
1290
|
-
if (JSON.stringify(editBuffer[key]) !== JSON.stringify(detail[key])) changed.add(key);
|
|
1291
|
-
}
|
|
2005
|
+
const changed = collectDirtyPaths(editBuffer, detail);
|
|
1292
2006
|
return { changed, isDirty: changed.size > 0 };
|
|
1293
2007
|
}, [editBuffer, detail]);
|
|
1294
2008
|
const startEdit = useCallback((id) => {
|
|
@@ -1307,25 +2021,33 @@ function useEntityCRUD(opts) {
|
|
|
1307
2021
|
}, [resetBuffer, selectedId]);
|
|
1308
2022
|
const applyOptimistic = useCallback(() => {
|
|
1309
2023
|
if (!selectedId) return;
|
|
1310
|
-
useGraphStore.getState()
|
|
2024
|
+
const store = useGraphStore.getState();
|
|
2025
|
+
store.patchEntity(type, selectedId, editBuffer);
|
|
2026
|
+
store.setEntitySyncMetadata(type, selectedId, { synced: false, origin: "optimistic", updatedAt: Date.now() });
|
|
1311
2027
|
}, [type, selectedId, editBuffer]);
|
|
1312
2028
|
const save = useCallback(async () => {
|
|
1313
2029
|
if (!selectedId || !onUpdate) return null;
|
|
1314
2030
|
setIsSaving(true);
|
|
1315
2031
|
setSaveError(null);
|
|
1316
|
-
const
|
|
1317
|
-
|
|
2032
|
+
const store = useGraphStore.getState();
|
|
2033
|
+
const previous = store.readEntity(type, selectedId);
|
|
2034
|
+
const previousSync = store.syncMetadata[`${type}:${selectedId}`];
|
|
2035
|
+
store.upsertEntity(type, selectedId, editBuffer);
|
|
2036
|
+
store.setEntitySyncMetadata(type, selectedId, { synced: false, origin: "optimistic", updatedAt: Date.now() });
|
|
1318
2037
|
try {
|
|
1319
2038
|
const result = await onUpdate(selectedId, editBuffer);
|
|
1320
2039
|
const { id, data } = normalize(result);
|
|
1321
|
-
|
|
1322
|
-
|
|
2040
|
+
store.replaceEntity(type, id, data);
|
|
2041
|
+
store.clearPatch(type, id);
|
|
2042
|
+
store.setEntitySyncMetadata(type, id, { synced: true, origin: "server", updatedAt: Date.now() });
|
|
1323
2043
|
cascadeInvalidation({ type, id: selectedId, previous, next: data, op: "update" });
|
|
1324
2044
|
setMode("detail");
|
|
1325
2045
|
optsRef.current.onUpdateSuccess?.(result);
|
|
1326
2046
|
return result;
|
|
1327
2047
|
} catch (err) {
|
|
1328
|
-
if (previous)
|
|
2048
|
+
if (previous) store.replaceEntity(type, selectedId, previous);
|
|
2049
|
+
if (previousSync) store.setEntitySyncMetadata(type, selectedId, previousSync);
|
|
2050
|
+
else store.clearEntitySyncMetadata(type, selectedId);
|
|
1329
2051
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
1330
2052
|
setSaveError(error.message);
|
|
1331
2053
|
optsRef.current.onError?.("update", error);
|
|
@@ -1337,7 +2059,7 @@ function useEntityCRUD(opts) {
|
|
|
1337
2059
|
const [createBuffer, setCreateBuffer] = useState({ ...createDefaults });
|
|
1338
2060
|
const [isCreating, setIsCreating] = useState(false);
|
|
1339
2061
|
const [createError, setCreateError] = useState(null);
|
|
1340
|
-
const setCreateField = useCallback((field, value) => setCreateBuffer((prev) => (
|
|
2062
|
+
const setCreateField = useCallback((field, value) => setCreateBuffer((prev) => setValueAtPath(prev, String(field), value)), []);
|
|
1341
2063
|
const setCreateFields = useCallback((fields) => setCreateBuffer((prev) => ({ ...prev, ...fields })), []);
|
|
1342
2064
|
const resetCreateBuffer = useCallback(() => setCreateBuffer({ ...optsRef.current.createDefaults ?? {} }), []);
|
|
1343
2065
|
const startCreate = useCallback(() => {
|
|
@@ -1350,21 +2072,23 @@ function useEntityCRUD(opts) {
|
|
|
1350
2072
|
setMode("list");
|
|
1351
2073
|
setCreateError(null);
|
|
1352
2074
|
}, [resetCreateBuffer]);
|
|
1353
|
-
const
|
|
2075
|
+
const create3 = useCallback(async () => {
|
|
1354
2076
|
if (!onCreate) return null;
|
|
1355
2077
|
setIsCreating(true);
|
|
1356
2078
|
setCreateError(null);
|
|
1357
2079
|
const tempId = `__temp__${Date.now()}`;
|
|
1358
2080
|
const optimisticData = { ...createBuffer, id: tempId, _optimistic: true };
|
|
1359
|
-
useGraphStore.getState()
|
|
1360
|
-
|
|
2081
|
+
const store = useGraphStore.getState();
|
|
2082
|
+
store.upsertEntity(type, tempId, optimisticData);
|
|
2083
|
+
store.setEntitySyncMetadata(type, tempId, { synced: false, origin: "optimistic", updatedAt: Date.now() });
|
|
2084
|
+
store.insertIdInList(serializeKey(listQueryKey), tempId, "start");
|
|
1361
2085
|
try {
|
|
1362
2086
|
const result = await onCreate(createBuffer);
|
|
1363
2087
|
const { id: realId, data } = normalize(result);
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
2088
|
+
store.removeEntity(type, tempId);
|
|
2089
|
+
store.upsertEntity(type, realId, data);
|
|
2090
|
+
store.setEntityFetched(type, realId);
|
|
2091
|
+
store.setEntitySyncMetadata(type, realId, { synced: true, origin: "server", updatedAt: Date.now() });
|
|
1368
2092
|
for (const key of Object.keys(store.lists)) {
|
|
1369
2093
|
const list2 = store.lists[key];
|
|
1370
2094
|
const idx = list2.ids.indexOf(tempId);
|
|
@@ -1382,8 +2106,8 @@ function useEntityCRUD(opts) {
|
|
|
1382
2106
|
optsRef.current.onCreateSuccess?.(result);
|
|
1383
2107
|
return result;
|
|
1384
2108
|
} catch (err) {
|
|
1385
|
-
|
|
1386
|
-
|
|
2109
|
+
store.removeEntity(type, tempId);
|
|
2110
|
+
store.removeIdFromAllLists(type, tempId);
|
|
1387
2111
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
1388
2112
|
setCreateError(error.message);
|
|
1389
2113
|
optsRef.current.onError?.("create", error);
|
|
@@ -1422,7 +2146,7 @@ function useEntityCRUD(opts) {
|
|
|
1422
2146
|
setIsDeleting(false);
|
|
1423
2147
|
}
|
|
1424
2148
|
}, [type, selectedId, listQueryKey, clearSelectionAfterDelete]);
|
|
1425
|
-
return { mode, setMode, list, selectedId, select, openDetail, detail: detail ?? null, detailIsLoading, detailError: detailError ?? null, relations, editBuffer, setField, setFields, resetBuffer, dirty, startEdit, cancelEdit, save, isSaving, saveError, applyOptimistic, createBuffer, setCreateField, setCreateFields, resetCreateBuffer, startCreate, cancelCreate, create:
|
|
2149
|
+
return { mode, setMode, list, selectedId, select, openDetail, detail: detail ?? null, detailIsLoading, detailError: detailError ?? null, relations, editBuffer, setField, setFields, resetBuffer, dirty, startEdit, cancelEdit, save, isSaving, saveError, applyOptimistic, createBuffer, setCreateField, setCreateFields, resetCreateBuffer, startCreate, cancelCreate, create: create3, isCreating, createError, deleteEntity, isDeleting, deleteError, isEditing: mode === "edit" || mode === "create" };
|
|
1426
2150
|
}
|
|
1427
2151
|
|
|
1428
2152
|
// src/adapters/realtime-manager.ts
|
|
@@ -2187,10 +2911,7 @@ function useGQLEntity(opts) {
|
|
|
2187
2911
|
optsRef.current = opts;
|
|
2188
2912
|
const data = useStore(useGraphStore, useShallow((s) => {
|
|
2189
2913
|
if (!id) return null;
|
|
2190
|
-
|
|
2191
|
-
if (!base) return null;
|
|
2192
|
-
const patch = s.patches[type]?.[id];
|
|
2193
|
-
return patch ? { ...base, ...patch } : base;
|
|
2914
|
+
return s.readEntitySnapshot(type, id);
|
|
2194
2915
|
}));
|
|
2195
2916
|
const entityState = useStore(useGraphStore, useCallback(
|
|
2196
2917
|
(s) => s.entityStates[`${type}:${id}`] ?? EMPTY_ENTITY_STATE,
|
|
@@ -2236,12 +2957,7 @@ function useGQLList(opts) {
|
|
|
2236
2957
|
useGraphStore,
|
|
2237
2958
|
useShallow((s) => {
|
|
2238
2959
|
const ids = s.lists[key]?.ids ?? EMPTY_IDS;
|
|
2239
|
-
return ids.map((id) =>
|
|
2240
|
-
const base = s.entities[type]?.[id];
|
|
2241
|
-
if (!base) return null;
|
|
2242
|
-
const p = s.patches[type]?.[id];
|
|
2243
|
-
return p ? { ...base, ...p } : base;
|
|
2244
|
-
}).filter((x) => x !== null);
|
|
2960
|
+
return ids.map((id) => s.readEntitySnapshot(type, id)).filter((x) => x !== null);
|
|
2245
2961
|
})
|
|
2246
2962
|
);
|
|
2247
2963
|
const doFetch = useCallback((cursor, append = false) => {
|
|
@@ -2478,7 +3194,7 @@ function EntityTable({ viewResult, columns, getRowId = (r) => String(r.id), sele
|
|
|
2478
3194
|
] });
|
|
2479
3195
|
}
|
|
2480
3196
|
function Sheet({ open, onClose, title, subtitle, children, footer, width = "w-[480px]" }) {
|
|
2481
|
-
|
|
3197
|
+
React6.useEffect(() => {
|
|
2482
3198
|
const h = (e) => {
|
|
2483
3199
|
if (e.key === "Escape") onClose();
|
|
2484
3200
|
};
|
|
@@ -2514,6 +3230,8 @@ function FieldControl({ descriptor, value, onChange, entity, readonly }) {
|
|
|
2514
3230
|
return /* @__PURE__ */ jsx("input", { type: "number", value: String(value ?? ""), onChange: (e) => onChange(e.target.valueAsNumber), placeholder: descriptor.placeholder, className: base });
|
|
2515
3231
|
case "textarea":
|
|
2516
3232
|
return /* @__PURE__ */ jsx("textarea", { value: String(value ?? ""), onChange: (e) => onChange(e.target.value), placeholder: descriptor.placeholder, className: "w-full min-h-[80px] rounded-md border bg-muted/50 px-3 py-2 text-sm resize-none focus:outline-none focus:ring-1 focus:ring-ring transition-colors" });
|
|
3233
|
+
case "markdown":
|
|
3234
|
+
return /* @__PURE__ */ jsx(MarkdownFieldEditor, { value: String(value ?? ""), onChange: (nextValue) => onChange(nextValue), placeholder: descriptor.placeholder });
|
|
2517
3235
|
case "date":
|
|
2518
3236
|
return /* @__PURE__ */ jsx("input", { type: "date", value: value ? new Date(value).toISOString().split("T")[0] : "", onChange: (e) => onChange(e.target.value ? new Date(e.target.value).toISOString() : null), className: base });
|
|
2519
3237
|
case "boolean":
|
|
@@ -2533,12 +3251,35 @@ function FieldControl({ descriptor, value, onChange, entity, readonly }) {
|
|
|
2533
3251
|
!value && /* @__PURE__ */ jsx("option", { value: "", children: "Select\u2026" }),
|
|
2534
3252
|
(descriptor.options ?? []).map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
|
|
2535
3253
|
] });
|
|
3254
|
+
case "json":
|
|
3255
|
+
return /* @__PURE__ */ jsx(
|
|
3256
|
+
"textarea",
|
|
3257
|
+
{
|
|
3258
|
+
value: value != null ? JSON.stringify(value, null, 2) : "",
|
|
3259
|
+
onChange: (event) => {
|
|
3260
|
+
const nextValue = event.target.value;
|
|
3261
|
+
try {
|
|
3262
|
+
onChange(nextValue ? JSON.parse(nextValue) : null);
|
|
3263
|
+
} catch {
|
|
3264
|
+
onChange(nextValue);
|
|
3265
|
+
}
|
|
3266
|
+
},
|
|
3267
|
+
placeholder: descriptor.placeholder,
|
|
3268
|
+
className: "w-full min-h-[120px] rounded-md border bg-muted/50 px-3 py-2 text-sm font-mono resize-y focus:outline-none focus:ring-1 focus:ring-ring transition-colors"
|
|
3269
|
+
}
|
|
3270
|
+
);
|
|
2536
3271
|
default:
|
|
2537
3272
|
return /* @__PURE__ */ jsx("input", { value: String(value ?? ""), onChange: (e) => onChange(e.target.value), className: base });
|
|
2538
3273
|
}
|
|
2539
3274
|
}
|
|
3275
|
+
function FieldReadonlyValue({ descriptor, value, entity }) {
|
|
3276
|
+
if (descriptor.render) return /* @__PURE__ */ jsx(Fragment, { children: descriptor.render(value, entity) });
|
|
3277
|
+
if (descriptor.type === "markdown") return /* @__PURE__ */ jsx(MarkdownFieldRenderer, { value: String(value ?? ""), className: "prose prose-sm max-w-none py-1" });
|
|
3278
|
+
if (descriptor.type === "json") return /* @__PURE__ */ jsx("pre", { className: "text-xs py-1 whitespace-pre-wrap break-words", children: JSON.stringify(value ?? null, null, 2) });
|
|
3279
|
+
return /* @__PURE__ */ jsx("p", { className: "text-sm py-1", children: value != null && value !== "" ? String(value) : "\u2014" });
|
|
3280
|
+
}
|
|
2540
3281
|
function EntityDetailSheet({ crud, fields, title = "Details", description, children, showEditButton = true, showDeleteButton = true, deleteConfirmMessage = "This action cannot be undone." }) {
|
|
2541
|
-
const [confirmDelete, setConfirmDelete] =
|
|
3282
|
+
const [confirmDelete, setConfirmDelete] = React6.useState(false);
|
|
2542
3283
|
const open = crud.mode === "detail" && !!crud.selectedId;
|
|
2543
3284
|
const entity = crud.detail;
|
|
2544
3285
|
const resolvedTitle = entity && typeof title === "function" ? title(entity) : String(title);
|
|
@@ -2562,8 +3303,7 @@ function EntityDetailSheet({ crud, fields, title = "Details", description, child
|
|
|
2562
3303
|
entity && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
2563
3304
|
fields.map((f) => /* @__PURE__ */ jsxs("div", { children: [
|
|
2564
3305
|
/* @__PURE__ */ jsx("p", { className: "text-[10px] font-medium text-muted-foreground uppercase tracking-wide mb-1", children: f.label }),
|
|
2565
|
-
/* @__PURE__ */ jsx(
|
|
2566
|
-
}, entity, readonly: true })
|
|
3306
|
+
/* @__PURE__ */ jsx(FieldReadonlyValue, { descriptor: f, value: getValueAtPath(entity, f.field), entity })
|
|
2567
3307
|
] }, f.field)),
|
|
2568
3308
|
children && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2569
3309
|
/* @__PURE__ */ jsx("div", { className: "border-t my-1" }),
|
|
@@ -2633,13 +3373,14 @@ function EntityFormSheet({ crud, fields, createTitle = "Create", editTitle = "Ed
|
|
|
2633
3373
|
error && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 rounded-md bg-destructive/10 border border-destructive/20 text-xs text-destructive", children: error }),
|
|
2634
3374
|
visibleFields.map((f) => {
|
|
2635
3375
|
const isDirty = !isCreate && crud.dirty.changed.has(f.field);
|
|
3376
|
+
const currentValue = getValueAtPath(buf, f.field);
|
|
2636
3377
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
2637
3378
|
/* @__PURE__ */ jsxs("label", { className: cn("text-xs font-medium", isDirty ? "text-primary" : "text-muted-foreground"), children: [
|
|
2638
3379
|
f.label,
|
|
2639
3380
|
f.required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-0.5", children: "*" }),
|
|
2640
3381
|
isDirty && /* @__PURE__ */ jsx("span", { className: "ml-1.5 text-[10px] font-normal opacity-70", children: "modified" })
|
|
2641
3382
|
] }),
|
|
2642
|
-
/* @__PURE__ */ jsx(FieldControl, { descriptor: f, value:
|
|
3383
|
+
/* @__PURE__ */ jsx(FieldControl, { descriptor: f, value: currentValue, onChange: (v) => setField(f.field, v), entity: buf, readonly: f.readonlyOnEdit && isEdit }),
|
|
2643
3384
|
f.hint && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: f.hint })
|
|
2644
3385
|
] }, f.field);
|
|
2645
3386
|
})
|
|
@@ -3840,9 +4581,9 @@ function createSelectionStore() {
|
|
|
3840
4581
|
function useSelectionStore(store, selector) {
|
|
3841
4582
|
return useStore(store, selector);
|
|
3842
4583
|
}
|
|
3843
|
-
var SelectionContext =
|
|
4584
|
+
var SelectionContext = React6.createContext(null);
|
|
3844
4585
|
function useSelectionContext() {
|
|
3845
|
-
const store =
|
|
4586
|
+
const store = React6.useContext(SelectionContext);
|
|
3846
4587
|
if (!store) throw new Error("useSelectionContext must be used within a SelectionContext.Provider");
|
|
3847
4588
|
return store;
|
|
3848
4589
|
}
|
|
@@ -4757,7 +5498,7 @@ function useTableStorageAdapter() {
|
|
|
4757
5498
|
function useTableRealtimeMode() {
|
|
4758
5499
|
return useContext(TableStorageContext).realtimeMode;
|
|
4759
5500
|
}
|
|
4760
|
-
var Table =
|
|
5501
|
+
var Table = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { className: "relative w-full overflow-auto", children: /* @__PURE__ */ jsx(
|
|
4761
5502
|
"table",
|
|
4762
5503
|
{
|
|
4763
5504
|
ref,
|
|
@@ -4766,11 +5507,11 @@ var Table = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */
|
|
|
4766
5507
|
}
|
|
4767
5508
|
) }));
|
|
4768
5509
|
Table.displayName = "Table";
|
|
4769
|
-
var TableHeader =
|
|
5510
|
+
var TableHeader = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("thead", { ref, className: cn("bg-muted/60", className), ...props }));
|
|
4770
5511
|
TableHeader.displayName = "TableHeader";
|
|
4771
|
-
var TableBody =
|
|
5512
|
+
var TableBody = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("tbody", { ref, className: cn("bg-background", className), ...props }));
|
|
4772
5513
|
TableBody.displayName = "TableBody";
|
|
4773
|
-
var TableFooter =
|
|
5514
|
+
var TableFooter = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
4774
5515
|
"tfoot",
|
|
4775
5516
|
{
|
|
4776
5517
|
ref,
|
|
@@ -4779,7 +5520,7 @@ var TableFooter = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE
|
|
|
4779
5520
|
}
|
|
4780
5521
|
));
|
|
4781
5522
|
TableFooter.displayName = "TableFooter";
|
|
4782
|
-
var TableRow =
|
|
5523
|
+
var TableRow = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
4783
5524
|
"tr",
|
|
4784
5525
|
{
|
|
4785
5526
|
ref,
|
|
@@ -4791,7 +5532,7 @@ var TableRow = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__
|
|
|
4791
5532
|
}
|
|
4792
5533
|
));
|
|
4793
5534
|
TableRow.displayName = "TableRow";
|
|
4794
|
-
var TableHead =
|
|
5535
|
+
var TableHead = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
4795
5536
|
"th",
|
|
4796
5537
|
{
|
|
4797
5538
|
ref,
|
|
@@ -4803,7 +5544,7 @@ var TableHead = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__
|
|
|
4803
5544
|
}
|
|
4804
5545
|
));
|
|
4805
5546
|
TableHead.displayName = "TableHead";
|
|
4806
|
-
var TableCell =
|
|
5547
|
+
var TableCell = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
4807
5548
|
"td",
|
|
4808
5549
|
{
|
|
4809
5550
|
ref,
|
|
@@ -4815,7 +5556,7 @@ var TableCell = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__
|
|
|
4815
5556
|
}
|
|
4816
5557
|
));
|
|
4817
5558
|
TableCell.displayName = "TableCell";
|
|
4818
|
-
var TableCaption =
|
|
5559
|
+
var TableCaption = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
4819
5560
|
"caption",
|
|
4820
5561
|
{
|
|
4821
5562
|
ref,
|
|
@@ -6334,7 +7075,7 @@ function ChevronsRightIcon({ className }) {
|
|
|
6334
7075
|
] });
|
|
6335
7076
|
}
|
|
6336
7077
|
function EmptyState({ config, isFiltered = false, className }) {
|
|
6337
|
-
if (
|
|
7078
|
+
if (React6.isValidElement(config)) {
|
|
6338
7079
|
return /* @__PURE__ */ jsx(Fragment, { children: config });
|
|
6339
7080
|
}
|
|
6340
7081
|
const cfg = config ?? {};
|
|
@@ -7734,13 +8475,13 @@ function selectionColumn2() {
|
|
|
7734
8475
|
enableFiltering: false,
|
|
7735
8476
|
enableHiding: false,
|
|
7736
8477
|
enableResizing: false,
|
|
7737
|
-
header: ({ table }) =>
|
|
8478
|
+
header: ({ table }) => React6.createElement("input", {
|
|
7738
8479
|
type: "checkbox",
|
|
7739
8480
|
checked: table.getIsAllPageRowsSelected(),
|
|
7740
8481
|
onChange: table.getToggleAllPageRowsSelectedHandler(),
|
|
7741
8482
|
className: "h-4 w-4 rounded border-primary text-primary focus:ring-ring"
|
|
7742
8483
|
}),
|
|
7743
|
-
cell: ({ row }) =>
|
|
8484
|
+
cell: ({ row }) => React6.createElement("input", {
|
|
7744
8485
|
type: "checkbox",
|
|
7745
8486
|
checked: row.getIsSelected(),
|
|
7746
8487
|
onChange: row.getToggleSelectedHandler(),
|
|
@@ -7876,7 +8617,7 @@ function enumColumn2(options) {
|
|
|
7876
8617
|
const opt = options.options.find((o) => o.value === val);
|
|
7877
8618
|
if (!opt) return val;
|
|
7878
8619
|
if (opt.badgeClassName) {
|
|
7879
|
-
return
|
|
8620
|
+
return React6.createElement(
|
|
7880
8621
|
"span",
|
|
7881
8622
|
{
|
|
7882
8623
|
className: `inline-flex items-center rounded px-1.5 py-0.5 text-[11px] font-medium capitalize ${opt.badgeClassName}`
|
|
@@ -7884,7 +8625,7 @@ function enumColumn2(options) {
|
|
|
7884
8625
|
opt.label
|
|
7885
8626
|
);
|
|
7886
8627
|
}
|
|
7887
|
-
return
|
|
8628
|
+
return React6.createElement(
|
|
7888
8629
|
"span",
|
|
7889
8630
|
{
|
|
7890
8631
|
className: "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium",
|
|
@@ -7921,6 +8662,6 @@ function actionsColumn2() {
|
|
|
7921
8662
|
};
|
|
7922
8663
|
}
|
|
7923
8664
|
|
|
7924
|
-
export { ActionButtonRow, ActionDropdown, ColumnPresetDialog, DataTable, DataTableColumnHeader, DataTableFilter, DataTablePagination, DataTableToolbar, ElectricSQLAdapter as ElectricSQLPresetAdapter, EmptyState, EntityDetailSheet, EntityFormSheet, EntityListView, EntityTable, FilterPresetDialog, GQLClient, GalleryView, InlineCellEditor, InlineItemEditor, ListView, MemoryAdapter, MultiSelectBar, PresetPicker, InlineCellEditor2 as PureInlineCellEditor, RealtimeManager, RestApiAdapter, SelectionContext, Sheet, SortHeader, SupabaseRealtimeAdapter as SupabasePresetAdapter, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, TableStorageProvider, ViewModeSwitcher, ZustandPersistAdapter, actionsColumn, applyView, booleanColumn, cascadeInvalidation, checkCompleteness, compareEntities, configureEngine, createConvexAdapter, createElectricAdapter, createGQLClient, createGraphQLSubscriptionAdapter, createPresetStore, createPrismaEntityConfig, createRow, createSelectionStore, createSupabaseRealtimeAdapter, createWebSocketAdapter, dateColumn, dedupe, deleteAction, editAction, enumColumn, executeGQL, fetchEntity, fetchList, flattenClauses, getCoreRowModel2 as getCoreRowModel, getExpandedRowModel, getFacetedMinMaxValues, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getGroupedRowModel, getPaginatedRowModel, getRealtimeManager, getSchema, getSelectedRowModel, getSortedRowModel2 as getSortedRowModel, hasCustomPredicates, matchesFilter, matchesSearch, normalizeGQLResponse, numberColumn, prismaRelationsToSchema, actionsColumn2 as pureActionsColumn, booleanColumn2 as pureBooleanColumn, dateColumn2 as pureDateColumn, enumColumn2 as pureEnumColumn, numberColumn2 as pureNumberColumn, selectionColumn2 as pureSelectionColumn, textColumn2 as pureTextColumn, readRelations, registerSchema, resetRealtimeManager, selectionColumn, serializeKey, startGarbageCollector, stopGarbageCollector, textColumn, toGraphQLVariables, toPrismaInclude, toPrismaOrderBy, toPrismaWhere, toRestParams, toSQLClauses, useEntity, useEntityAugment, useEntityCRUD, useEntityList, useEntityMutation, useEntityView, useGQLEntity, useGQLList, useGQLMutation, useGQLSubscription, useGraphDevTools, useGraphStore, useLocalFirst, usePGliteQuery, useSelectionContext, useSelectionStore, useSuspenseEntity, useSuspenseEntityList, useTable, useTablePresets, useTableRealtimeMode, useTableStorageAdapter, viewAction };
|
|
8665
|
+
export { ActionButtonRow, ActionDropdown, ColumnPresetDialog, DataTable, DataTableColumnHeader, DataTableFilter, DataTablePagination, DataTableToolbar, ElectricSQLAdapter as ElectricSQLPresetAdapter, EmptyState, EntityDetailSheet, EntityFormSheet, EntityListView, EntityTable, FilterPresetDialog, GQLClient, GalleryView, InlineCellEditor, InlineItemEditor, ListView, MarkdownFieldEditor, MarkdownFieldRenderer, MemoryAdapter, MultiSelectBar, PresetPicker, InlineCellEditor2 as PureInlineCellEditor, RealtimeManager, RestApiAdapter, SelectionContext, Sheet, SortHeader, SupabaseRealtimeAdapter as SupabasePresetAdapter, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, TableStorageProvider, ViewModeSwitcher, ZustandPersistAdapter, actionsColumn, applyView, booleanColumn, buildEntityFieldsFromSchema, cascadeInvalidation, checkCompleteness, compareEntities, configureEngine, createConvexAdapter, createElectricAdapter, createGQLClient, createGraphAction, createGraphEffect, createGraphQLSubscriptionAdapter, createGraphTool, createGraphTransaction, createPresetStore, createPrismaEntityConfig, createRow, createSchemaGraphTool, createSelectionStore, createSupabaseRealtimeAdapter, createWebSocketAdapter, dateColumn, dedupe, deleteAction, editAction, enumColumn, executeGQL, exportGraphSnapshot, exportGraphSnapshotWithSchemas, fetchEntity, fetchList, flattenClauses, getCoreRowModel2 as getCoreRowModel, getEntityJsonSchema, getExpandedRowModel, getFacetedMinMaxValues, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getGroupedRowModel, getPaginatedRowModel, getRealtimeManager, getSchema, getSelectedRowModel, getSortedRowModel2 as getSortedRowModel, hasCustomPredicates, hydrateGraphFromStorage, matchesFilter, matchesSearch, normalizeGQLResponse, numberColumn, persistGraphToStorage, prismaRelationsToSchema, actionsColumn2 as pureActionsColumn, booleanColumn2 as pureBooleanColumn, dateColumn2 as pureDateColumn, enumColumn2 as pureEnumColumn, numberColumn2 as pureNumberColumn, selectionColumn2 as pureSelectionColumn, textColumn2 as pureTextColumn, queryOnce, readRelations, registerEntityJsonSchema, registerRuntimeSchema, registerSchema, renderMarkdownToHtml, resetRealtimeManager, selectGraph, selectionColumn, serializeKey, startGarbageCollector, startLocalFirstGraph, stopGarbageCollector, textColumn, toGraphQLVariables, toPrismaInclude, toPrismaOrderBy, toPrismaWhere, toRestParams, toSQLClauses, useEntity, useEntityAugment, useEntityCRUD, useEntityList, useEntityMutation, useEntityView, useGQLEntity, useGQLList, useGQLMutation, useGQLSubscription, useGraphDevTools, useGraphStore, useGraphSyncStatus, useLocalFirst, usePGliteQuery, useSchemaEntityFields, useSelectionContext, useSelectionStore, useSuspenseEntity, useSuspenseEntityList, useTable, useTablePresets, useTableRealtimeMode, useTableStorageAdapter, viewAction };
|
|
7925
8666
|
//# sourceMappingURL=index.mjs.map
|
|
7926
8667
|
//# sourceMappingURL=index.mjs.map
|