@prometheus-ags/entity-graph-core 3.0.4 → 3.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 +30 -0
- package/README.md +248 -0
- package/dist/chunk-BE3BMRKF.mjs +938 -0
- package/dist/chunk-BE3BMRKF.mjs.map +1 -0
- package/dist/devtools.cjs +2561 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.cts +531 -0
- package/dist/devtools.d.ts +531 -0
- package/dist/devtools.mjs +2019 -0
- package/dist/devtools.mjs.map +1 -0
- package/dist/graph-ClW3HD8B.d.cts +990 -0
- package/dist/graph-ClW3HD8B.d.ts +990 -0
- package/dist/index.cjs +572 -448
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +131 -1059
- package/dist/index.d.ts +131 -1059
- package/dist/index.mjs +127 -910
- package/dist/index.mjs.map +1 -1
- package/fixtures/devtools/README.md +37 -0
- package/fixtures/devtools/entity-inspection-v1.json +304 -0
- package/fixtures/devtools/time-travel-v1.json +110 -0
- package/package.json +25 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @prometheus-ags/entity-graph-core
|
|
2
2
|
|
|
3
|
+
## 3.1.0
|
|
4
|
+
|
|
5
|
+
## 3.0.5
|
|
6
|
+
|
|
7
|
+
### Patch Changes
|
|
8
|
+
|
|
9
|
+
- Resolve imperative graph access against the active graph instead of the package
|
|
10
|
+
singleton, so `GraphStoreProvider` scopes `useGraphStore.getState()`,
|
|
11
|
+
`.setState()`, `.subscribe()` and `.getInitialState()` (issue #42).
|
|
12
|
+
|
|
13
|
+
3.0.4 replaced the copied StoreApi with delegates that warned but still targeted
|
|
14
|
+
the singleton, which left consumers using the documented imperative API with no
|
|
15
|
+
isolation. The React binding now proxies those methods, resolving per call.
|
|
16
|
+
|
|
17
|
+
New in `entity-graph-core`:
|
|
18
|
+
- `runWithGraphStore(store, fn)` — an `AsyncLocalStorage` request scope, so
|
|
19
|
+
concurrent server renders never share entity state.
|
|
20
|
+
- `prepareGraphStoreScope()` — awaited once during startup on **pure-ESM**
|
|
21
|
+
servers, where there is no synchronous `require` to load `node:async_hooks`.
|
|
22
|
+
Without it request scoping degrades to a module-level store and warns.
|
|
23
|
+
- `setActiveGraphStore(store)` — the module-level active store, set by
|
|
24
|
+
`GraphStoreProvider` on mount and restored on unmount.
|
|
25
|
+
- `resolveActiveGraphStore(fallback)` — request scope → module-level store →
|
|
26
|
+
fallback.
|
|
27
|
+
|
|
28
|
+
`node:async_hooks` is loaded lazily and never statically imported, so browser
|
|
29
|
+
bundles are unaffected. With no provider and no request scope, behaviour is
|
|
30
|
+
unchanged from 3.0.4, and the deprecation warning is gone because the methods
|
|
31
|
+
now do what callers expected.
|
|
32
|
+
|
|
3
33
|
## 3.0.4
|
|
4
34
|
|
|
5
35
|
## 3.0.3
|
package/README.md
CHANGED
|
@@ -98,6 +98,252 @@ registerMergeStrategy("Document", strategy);
|
|
|
98
98
|
The public `LoroModuleLoader` type describes this callback. Supplying it does
|
|
99
99
|
not make Loro part of the mandatory core bundle.
|
|
100
100
|
|
|
101
|
+
## Optional DevTools controller
|
|
102
|
+
|
|
103
|
+
The versioned DevTools runtime is isolated behind the
|
|
104
|
+
`@prometheus-ags/entity-graph-core/devtools` export. Normal application imports
|
|
105
|
+
do not expose the controller API.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { createGraphStore } from "@prometheus-ags/entity-graph-core";
|
|
109
|
+
import {
|
|
110
|
+
attachGraphDevtools,
|
|
111
|
+
createGraphDevtoolsClient,
|
|
112
|
+
} from "@prometheus-ags/entity-graph-core/devtools";
|
|
113
|
+
|
|
114
|
+
const store = createGraphStore();
|
|
115
|
+
const attachment = attachGraphDevtools(store, {
|
|
116
|
+
storeId: "admin-graph",
|
|
117
|
+
historyLimit: 500,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (attachment.controller) {
|
|
121
|
+
const client = createGraphDevtoolsClient(
|
|
122
|
+
attachment.controller.storeId,
|
|
123
|
+
attachment.controller.connect("local-inspector"),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const snapshot = await client.request("get-snapshot");
|
|
127
|
+
client.disconnect();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
attachment.detach();
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Inspect entities, rendered views, and relationships
|
|
134
|
+
|
|
135
|
+
The controller projects inspection state on demand from the owning graph. It
|
|
136
|
+
does not copy entity values into a second store:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const attachment = attachGraphDevtools(store, {
|
|
140
|
+
storeId: "admin-graph",
|
|
141
|
+
values: { mode: "include" },
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const controller = attachment.controller;
|
|
145
|
+
if (controller) {
|
|
146
|
+
const view = controller.registerView({
|
|
147
|
+
viewId: "users:active-table",
|
|
148
|
+
label: "Active users",
|
|
149
|
+
kind: "list",
|
|
150
|
+
entityType: "users",
|
|
151
|
+
queryKey: "users:active",
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Call this whenever the rendered membership changes.
|
|
155
|
+
view.updateMembership(["user-1", "user-2"]);
|
|
156
|
+
|
|
157
|
+
const entities = controller.getEntityRecords();
|
|
158
|
+
const views = controller.getViews();
|
|
159
|
+
const relationships = controller.getRelationships();
|
|
160
|
+
|
|
161
|
+
view.unregister();
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Entity records keep the canonical server-confirmed original, local graph patch,
|
|
166
|
+
and merged live value distinct. `dirtyReasons` names patched fields and
|
|
167
|
+
unsynchronized state; fetch/sync timestamps, typed fetch errors, store-local
|
|
168
|
+
revisions, and the stable IDs of views displaying the entity are projected
|
|
169
|
+
beside them. Missing canonical rows retained only by patch or metadata state are
|
|
170
|
+
reported as `presence: "missing-canonical"`.
|
|
171
|
+
|
|
172
|
+
View registrations are token-scoped. Duplicate registrations for one stable
|
|
173
|
+
`viewId` share the projected view but retain independent lifetimes, so one
|
|
174
|
+
consumer cannot unregister another. List views include current graph/list
|
|
175
|
+
statistics, and membership is available in both directions.
|
|
176
|
+
|
|
177
|
+
Relationships are derived from the existing CRUD schema registry and current
|
|
178
|
+
merged graph values. `belongsTo`, reverse `hasMany`, and `manyToMany` edges are
|
|
179
|
+
reported as `resolved` or `missing-target`; DevTools does not create a parallel
|
|
180
|
+
relationship registry.
|
|
181
|
+
|
|
182
|
+
### Preview and restore a local patch
|
|
183
|
+
|
|
184
|
+
Preview commands write through the graph's existing patch layer, so every live
|
|
185
|
+
consumer observes the proposed value:
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
const preview = await client.request("preview-entity-patch", {
|
|
189
|
+
type: "users",
|
|
190
|
+
id: "user-1",
|
|
191
|
+
patch: { displayName: "Proposed name" },
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
if (preview.ok && "previewId" in preview.result) {
|
|
195
|
+
const restore = await client.request("restore-entity-preview", {
|
|
196
|
+
previewId: preview.result.previewId,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The receipt captures the exact prior patch. Restore replaces that patch
|
|
202
|
+
atomically only when no intervening canonical or patch publication touched the
|
|
203
|
+
entity. Otherwise it returns a typed `conflict` receipt and leaves current graph
|
|
204
|
+
state unchanged. Fetch/sync metadata-only publications do not cause false
|
|
205
|
+
conflicts. Only one active preview is retained per entity, and receipts are
|
|
206
|
+
discarded with their controller.
|
|
207
|
+
|
|
208
|
+
The same value policy governs events, entity records, and preview receipts.
|
|
209
|
+
Under the default metadata-only policy, value positions contain an explicit
|
|
210
|
+
`{ $type: "hidden-by-policy" }` marker. Hosts must opt into `mode: "include"`
|
|
211
|
+
before canonical values, patches, merged values, or receipt values can cross the
|
|
212
|
+
inspection boundary.
|
|
213
|
+
|
|
214
|
+
The versioned conformance fixture is published at
|
|
215
|
+
`@prometheus-ags/entity-graph-core/devtools/fixtures/entity-inspection-v1.json`.
|
|
216
|
+
Its byte-identical source copy under `packages/entity_graph_flutter` freezes the
|
|
217
|
+
TypeScript/Flutter wire semantics; changing those semantics requires a new
|
|
218
|
+
fixture version.
|
|
219
|
+
|
|
220
|
+
Node ESM consumers import the JSON fixture with an import attribute:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import fixture from "@prometheus-ags/entity-graph-core/devtools/fixtures/entity-inspection-v1.json"
|
|
224
|
+
with { type: "json" };
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Rewind controller-owned snapshot history
|
|
228
|
+
|
|
229
|
+
Each active controller captures one initial attach baseline and the complete
|
|
230
|
+
mutable graph data slice after every semantic graph publication. Snapshot
|
|
231
|
+
payloads, stable cursors, imported candidates, rewind state, and the protected
|
|
232
|
+
live head stay inside that controller; UI code renders status and submits
|
|
233
|
+
intent rather than owning a second history ring.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
const attachment = attachGraphDevtools(store, {
|
|
237
|
+
storeId: "admin-graph",
|
|
238
|
+
snapshotLimit: 50,
|
|
239
|
+
snapshotBytesLimit: 10 * 1024 * 1024,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const controller = attachment.controller;
|
|
243
|
+
if (controller) {
|
|
244
|
+
const status = controller.getSnapshotHistoryStatus();
|
|
245
|
+
const cursor = status.newestCursor;
|
|
246
|
+
|
|
247
|
+
if (cursor !== null) {
|
|
248
|
+
const rewind = controller.rewind(cursor);
|
|
249
|
+
if (rewind?.status === "rewound") {
|
|
250
|
+
// Every graph subscriber now reads the selected historical state.
|
|
251
|
+
controller.returnToLive();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Count and byte ceilings both apply; eviction always removes whole oldest
|
|
258
|
+
snapshots. A cursor is never reused. Rewinding an evicted, cleared, or
|
|
259
|
+
unavailable capture returns a typed `expired-history` receipt with the current
|
|
260
|
+
retained range and never mutates the graph. Oversize captures remain visible as
|
|
261
|
+
`unavailable` metadata rather than retaining a partial graph.
|
|
262
|
+
|
|
263
|
+
The first rewind protects an exact deep clone of the live head. Explicit
|
|
264
|
+
return-to-live restores that clone through the Zustand publication boundary.
|
|
265
|
+
If an application mutation occurs while rewound, DevTools first emits a live
|
|
266
|
+
transition with `reason: "mutation"`, then emits and snapshots the completed
|
|
267
|
+
mutation as the new branch; the former protected future is released.
|
|
268
|
+
|
|
269
|
+
History imports are inert until confirmation. `inspectHistoryImport()` accepts
|
|
270
|
+
only the current protocol version, the same controller store ID, ordered stable
|
|
271
|
+
cursors, JSON-safe complete graph data, and the controller's count/byte budget.
|
|
272
|
+
`confirmHistoryImport(candidateId, cursor)` is one-shot and restores only the
|
|
273
|
+
exact inspected candidate. Inspection receipts and time-travel events expose
|
|
274
|
+
metadata, not snapshot values.
|
|
275
|
+
|
|
276
|
+
The deprecated root functions (`recordGraphSnapshot`,
|
|
277
|
+
`restoreGraphSnapshot*`, `stepTimeTravel`, and `getTimeTravelState`) are a thin
|
|
278
|
+
compatibility facade over the selected store's controller. They own no graph
|
|
279
|
+
payloads or cursors and return unavailable results until the explicit
|
|
280
|
+
`@prometheus-ags/entity-graph-core/devtools` module has been loaded. New tools
|
|
281
|
+
should use the controller's stable cursors and explicit return-to-live API.
|
|
282
|
+
|
|
283
|
+
The versioned time-travel fixture is published at
|
|
284
|
+
`@prometheus-ags/entity-graph-core/devtools/fixtures/time-travel-v1.json`. Its
|
|
285
|
+
byte-identical Flutter source copy freezes retention, rewind/live, branching,
|
|
286
|
+
expired-cursor, and confirmed-import semantics for the later Dart controller.
|
|
287
|
+
|
|
288
|
+
Each store owns one reference-counted controller. Attachments to the same store
|
|
289
|
+
share its event sequence and bounded history; separate stores never share
|
|
290
|
+
events, commands, clients, or teardown. The first active attachment determines
|
|
291
|
+
that controller's identifier, limits, and value policy until its final
|
|
292
|
+
attachment detaches.
|
|
293
|
+
|
|
294
|
+
Set `enabled: false` when a host should skip creating a particular attachment.
|
|
295
|
+
This is an attachment-level no-op, not a global kill switch: it never tears
|
|
296
|
+
down a controller still referenced by another attachment. Detach every active
|
|
297
|
+
attachment to stop observation for that store.
|
|
298
|
+
|
|
299
|
+
The controller observes the Zustand publication boundary, so transactions,
|
|
300
|
+
adapter writes, hydration, rollback, and direct `store.setState` changes use the
|
|
301
|
+
same semantic event stream. Events carry protocol version, store and event IDs,
|
|
302
|
+
sequence/correlation data, before/after graph counts, and categorized changes.
|
|
303
|
+
|
|
304
|
+
The deprecated root `subscribeDevtoolsEvent` API remains the original
|
|
305
|
+
history-free graph-transaction op-site stream. It does not attach this
|
|
306
|
+
controller and does not observe arbitrary direct `store.setState` writes. New
|
|
307
|
+
inspection tools should use this versioned subpath; the separate root shim
|
|
308
|
+
exists only for compatibility and keeps its incremental patch payloads.
|
|
309
|
+
|
|
310
|
+
### DevTools data boundary
|
|
311
|
+
|
|
312
|
+
Entity and patch values are omitted by default. Events contain metadata and
|
|
313
|
+
counts unless the host explicitly opts into values and supplies any required
|
|
314
|
+
redaction policy:
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
const attachment = attachGraphDevtools(store, {
|
|
318
|
+
values: {
|
|
319
|
+
mode: "include",
|
|
320
|
+
redact(value, context) {
|
|
321
|
+
if (context.category !== "entity" || !value || typeof value !== "object") {
|
|
322
|
+
return value;
|
|
323
|
+
}
|
|
324
|
+
const { accessToken: _secret, ...safe } = value as Record<string, unknown>;
|
|
325
|
+
return safe;
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
The current v1 redactor receives each whole changed value, represented by an
|
|
332
|
+
empty `context.fieldPath`. `context.destination` distinguishes retained event
|
|
333
|
+
history from on-demand inspection. Nested field paths are reserved for later
|
|
334
|
+
field-level inspection. A throwing redactor marks that change with
|
|
335
|
+
`valueState: "redaction-error"` and exposes neither the exception nor the
|
|
336
|
+
original value.
|
|
337
|
+
|
|
338
|
+
History is bounded by both event count and encoded bytes. Oversized included
|
|
339
|
+
values are removed before an event is retained or delivered; if metadata alone
|
|
340
|
+
still exceeds the per-event limit, `changesOmitted` records the explicit loss.
|
|
341
|
+
Redactor and
|
|
342
|
+
observer failures are isolated from graph writes, disconnected clients cannot
|
|
343
|
+
continue issuing commands, and the controller stops observing the store after
|
|
344
|
+
the final detach. A transport adapter is responsible for preserving the same
|
|
345
|
+
store/client boundary when messages leave the process.
|
|
346
|
+
|
|
101
347
|
## Major capabilities
|
|
102
348
|
|
|
103
349
|
- request deduplication, stale-while-revalidate, subscriber tracking, and garbage collection
|
|
@@ -116,6 +362,8 @@ The package publishes loader-specific artifacts and declarations:
|
|
|
116
362
|
|
|
117
363
|
- Node ESM and modern bundlers use `dist/index.mjs` with `dist/index.d.ts`.
|
|
118
364
|
- Node CommonJS uses `dist/index.cjs` with `dist/index.d.cts`.
|
|
365
|
+
- DevTools ESM uses `dist/devtools.mjs` with `dist/devtools.d.ts`.
|
|
366
|
+
- DevTools CommonJS uses `dist/devtools.cjs` with `dist/devtools.d.cts`.
|
|
119
367
|
|
|
120
368
|
Both paths are selected through conditional package exports.
|
|
121
369
|
|