@issuegraph/store 0.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/LICENSE +201 -0
- package/README.md +239 -0
- package/dist/adapters/memory.d.ts +30 -0
- package/dist/adapters/memory.d.ts.map +1 -0
- package/dist/adapters/memory.js +81 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/scripted.d.ts +67 -0
- package/dist/adapters/scripted.d.ts.map +1 -0
- package/dist/adapters/scripted.js +89 -0
- package/dist/adapters/scripted.js.map +1 -0
- package/dist/change.d.ts +75 -0
- package/dist/change.d.ts.map +1 -0
- package/dist/change.js +79 -0
- package/dist/change.js.map +1 -0
- package/dist/equality.d.ts +27 -0
- package/dist/equality.d.ts.map +1 -0
- package/dist/equality.js +60 -0
- package/dist/equality.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/model.d.ts +105 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +64 -0
- package/dist/model.js.map +1 -0
- package/dist/mutation.d.ts +83 -0
- package/dist/mutation.d.ts.map +1 -0
- package/dist/mutation.js +58 -0
- package/dist/mutation.js.map +1 -0
- package/dist/source.d.ts +139 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +17 -0
- package/dist/source.js.map +1 -0
- package/dist/store.d.ts +120 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +529 -0
- package/dist/store.js.map +1 -0
- package/dist/validity.d.ts +44 -0
- package/dist/validity.d.ts.map +1 -0
- package/dist/validity.js +125 -0
- package/dist/validity.js.map +1 -0
- package/dist/write.d.ts +79 -0
- package/dist/write.d.ts.map +1 -0
- package/dist/write.js +84 -0
- package/dist/write.js.map +1 -0
- package/package.json +56 -0
package/dist/store.js
ADDED
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The store: the one stateful module, and the only place IO happens.
|
|
3
|
+
*
|
|
4
|
+
* Everything it decides is decided by the pure modules beside it — validity,
|
|
5
|
+
* the write ledger and projection, the order diff. What lives here is the state
|
|
6
|
+
* itself, the calls into the injected data source, and the notification.
|
|
7
|
+
*
|
|
8
|
+
* **The rule the structure exists to enforce:** the selection order is derived
|
|
9
|
+
* from `landed` and from nothing else. `projected` — the landed document plus
|
|
10
|
+
* every unsettled edit — is built in one place and is not in scope where the
|
|
11
|
+
* deriver is called, so an optimistic edge cannot reach the order by anyone
|
|
12
|
+
* forgetting a filter.
|
|
13
|
+
*/
|
|
14
|
+
import { sameEdgeSet, } from "./model.js";
|
|
15
|
+
import { diffOrder } from "./change.js";
|
|
16
|
+
import { sameValue } from "./equality.js";
|
|
17
|
+
import { anyPending, project } from "./write.js";
|
|
18
|
+
import { nextDocument, structuralRefusal } from "./validity.js";
|
|
19
|
+
// Frozen because every store starts on this one object, and a shared mutable
|
|
20
|
+
// default is a cross-instance leak waiting for the first caller who sorts in
|
|
21
|
+
// place.
|
|
22
|
+
const EMPTY_DOCUMENT = Object.freeze({
|
|
23
|
+
issues: Object.freeze([]),
|
|
24
|
+
edges: Object.freeze([]),
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Take ownership of a list the store is about to publish: copy it, then freeze.
|
|
28
|
+
*
|
|
29
|
+
* The arrays a snapshot hands out ARE the store's state — `landed` and `issues`
|
|
30
|
+
* arrive from the adapter, `order.rows` from the deriver — and `Object.freeze`
|
|
31
|
+
* on the snapshot object protects none of them. A consumer calling `.sort()` on
|
|
32
|
+
* one (the common slip; `[...list].sort()` is the careful form) would reorder
|
|
33
|
+
* store state with no dispatch, no re-derivation and no notification, and the
|
|
34
|
+
* next order would derive from a document nobody wrote.
|
|
35
|
+
*
|
|
36
|
+
* COPIED BEFORE FREEZING, not frozen in place, because these arrive from the
|
|
37
|
+
* host: freezing an adapter's own array would freeze it out of its own storage
|
|
38
|
+
* for handing one over.
|
|
39
|
+
*
|
|
40
|
+
* THE CONTAINERS ONLY. The elements stay as the host made them — the store owns
|
|
41
|
+
* which edges and rows it holds, the host owns what each one is — because
|
|
42
|
+
* freezing those would reach into objects an adapter or a deriver may still be
|
|
43
|
+
* using. `readonly` in the types is what discourages touching them; nothing at
|
|
44
|
+
* runtime can promise it without taking objects that are not ours.
|
|
45
|
+
*/
|
|
46
|
+
function own(list) {
|
|
47
|
+
return Object.freeze([...list]);
|
|
48
|
+
}
|
|
49
|
+
/** A host-supplied document the store is taking on: its containers become ours. */
|
|
50
|
+
function ownDocument(document) {
|
|
51
|
+
return Object.freeze({ issues: own(document.issues), edges: own(document.edges) });
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Rows from the injected deriver, with their nested container taken on too.
|
|
55
|
+
*
|
|
56
|
+
* `holdReasons` is the deriver's array, so it is copied for the same reason the
|
|
57
|
+
* top-level list is: the store must be able to freeze what it publishes without
|
|
58
|
+
* freezing a host out of its own storage.
|
|
59
|
+
*/
|
|
60
|
+
function ownRows(rows) {
|
|
61
|
+
return own(rows.map((row) => ({ ...row, holdReasons: own(row.holdReasons) })));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Freeze every array reachable from a published value.
|
|
65
|
+
*
|
|
66
|
+
* `own` at each entry point covers the containers the store adopts directly,
|
|
67
|
+
* and that is exactly as far as it went — one level, when the predicate was
|
|
68
|
+
* never one level. Arrays nested inside published values (a conflict document's
|
|
69
|
+
* edges, an edge's state list, a row's hold reasons) are reachable from
|
|
70
|
+
* `getSnapshot()` just the same, and a consumer splicing one of those changes
|
|
71
|
+
* store state as surely as splicing `landed` would.
|
|
72
|
+
*
|
|
73
|
+
* ARRAYS ONLY: objects are walked but not frozen. That is the boundary the
|
|
74
|
+
* store can honestly keep — it owns which edges and rows it holds, the host
|
|
75
|
+
* owns what each one IS — and freezing the contents would seize objects an
|
|
76
|
+
* adapter or a deriver may still be using.
|
|
77
|
+
*
|
|
78
|
+
* Not short-circuited on an already-frozen array: `own` freezes a container
|
|
79
|
+
* without touching what is inside it, so stopping at the first frozen array
|
|
80
|
+
* would skip exactly the nested ones this exists to reach.
|
|
81
|
+
*/
|
|
82
|
+
function freezeArrays(value) {
|
|
83
|
+
if (Array.isArray(value)) {
|
|
84
|
+
Object.freeze(value);
|
|
85
|
+
for (const item of value)
|
|
86
|
+
freezeArrays(item);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (typeof value === 'object' && value !== null) {
|
|
90
|
+
for (const nested of Object.values(value))
|
|
91
|
+
freezeArrays(nested);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const NO_PROJECTION = Object.freeze([]);
|
|
95
|
+
function messageOf(thrown) {
|
|
96
|
+
return thrown instanceof Error ? thrown.message : String(thrown);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Create a store over one data source.
|
|
100
|
+
*
|
|
101
|
+
* Nothing is loaded until `hydrate()` is called, so a host can construct the
|
|
102
|
+
* store during render and start the load where it wants to.
|
|
103
|
+
*/
|
|
104
|
+
export function createStore(config) {
|
|
105
|
+
const { source, derive, guard } = config;
|
|
106
|
+
let landed = EMPTY_DOCUMENT;
|
|
107
|
+
let status = 'idle';
|
|
108
|
+
let hydrationError;
|
|
109
|
+
let records = [];
|
|
110
|
+
let selection = [];
|
|
111
|
+
let rows = [];
|
|
112
|
+
let lastChange;
|
|
113
|
+
let orderError;
|
|
114
|
+
let sequence = 0;
|
|
115
|
+
/**
|
|
116
|
+
* The edit the summary would belong to.
|
|
117
|
+
*
|
|
118
|
+
* A summary is written when a write LANDS, which can be after a newer edit
|
|
119
|
+
* has already started — and the contract is that a summary lasts until the
|
|
120
|
+
* next edit. Without this, a host shows the previous edit's blast radius
|
|
121
|
+
* beside the one the user is making now.
|
|
122
|
+
*/
|
|
123
|
+
let currentProposal;
|
|
124
|
+
const queue = [];
|
|
125
|
+
let draining = false;
|
|
126
|
+
const listeners = new Set();
|
|
127
|
+
const settlers = new Map();
|
|
128
|
+
const initial = {
|
|
129
|
+
status,
|
|
130
|
+
issues: landed.issues,
|
|
131
|
+
landed: landed.edges,
|
|
132
|
+
projected: NO_PROJECTION,
|
|
133
|
+
order: Object.freeze({ rows: ownRows(rows), status: 'settled' }),
|
|
134
|
+
writes: own(records),
|
|
135
|
+
selection: own(selection),
|
|
136
|
+
};
|
|
137
|
+
let published = Object.freeze(initial);
|
|
138
|
+
/**
|
|
139
|
+
* Adopt a document, reusing the arrays already held where the new ones say
|
|
140
|
+
* the same thing.
|
|
141
|
+
*
|
|
142
|
+
* Not an optimisation: `landed` is what everything downstream compares
|
|
143
|
+
* against, so an adapter returning an equal-but-fresh array on every call
|
|
144
|
+
* would make every slice of the snapshot look changed and defeat the "update
|
|
145
|
+
* only if something changed" rule at its source rather than at its edge.
|
|
146
|
+
*/
|
|
147
|
+
function adopt(next) {
|
|
148
|
+
const adopted = {
|
|
149
|
+
issues: sameValue(landed.issues, next.issues) ? landed.issues : own(next.issues),
|
|
150
|
+
edges: sameEdgeSet(landed.edges, next.edges) ? landed.edges : own(next.edges),
|
|
151
|
+
};
|
|
152
|
+
landed = adopted;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Rebuild the snapshot, reusing every slice that has not changed, and notify
|
|
156
|
+
* only if the result differs from what is already published.
|
|
157
|
+
*
|
|
158
|
+
* Per-slice reuse rather than one all-or-nothing comparison, so a host
|
|
159
|
+
* memoising on `snapshot.order.rows` is not re-rendered by an unrelated
|
|
160
|
+
* selection change — and `getSnapshot()` returns the identical reference when
|
|
161
|
+
* nothing at all moved, which is what `useSyncExternalStore` requires.
|
|
162
|
+
*/
|
|
163
|
+
function publish() {
|
|
164
|
+
// CALL THIS ONLY ONCE THE OPERATION IS FULLY COMMITTED. Subscribers run
|
|
165
|
+
// synchronously and may call straight back into the store, so anything left
|
|
166
|
+
// undone at this point can be overtaken by the re-entrant call — which is
|
|
167
|
+
// how a follow-up edit once jumped the queue ahead of the edit that
|
|
168
|
+
// triggered it. Every other call site already commits first; keep it that
|
|
169
|
+
// way rather than making each one rediscover the rule.
|
|
170
|
+
const nextProjected = project(landed, records, new Set(selection));
|
|
171
|
+
const nextOrderStatus = anyPending(records) ? 'held' : 'settled';
|
|
172
|
+
// One comparison for every slice. Nothing here names a field, so a field
|
|
173
|
+
// added to any of these shapes is compared without this block changing.
|
|
174
|
+
const projected = sameValue(published.projected, nextProjected)
|
|
175
|
+
? published.projected
|
|
176
|
+
: own(nextProjected);
|
|
177
|
+
// The rows keep their identity across a status-only change. Rebuilding the
|
|
178
|
+
// view is cheap; handing a host a new array when the ranking did not move
|
|
179
|
+
// defeats whatever it memoised on, and the status flips on every write.
|
|
180
|
+
const orderRows = sameValue(published.order.rows, rows) ? published.order.rows : rows;
|
|
181
|
+
const order = published.order.status === nextOrderStatus && published.order.rows === orderRows
|
|
182
|
+
? published.order
|
|
183
|
+
: Object.freeze({ rows: orderRows, status: nextOrderStatus });
|
|
184
|
+
const writes = sameValue(published.writes, records) ? published.writes : own(records);
|
|
185
|
+
const selected = sameValue(published.selection, selection) ? published.selection : own(selection);
|
|
186
|
+
const unchanged = published.status === status &&
|
|
187
|
+
published.hydrationError === hydrationError &&
|
|
188
|
+
published.issues === landed.issues &&
|
|
189
|
+
published.landed === landed.edges &&
|
|
190
|
+
published.projected === projected &&
|
|
191
|
+
published.order === order &&
|
|
192
|
+
published.writes === writes &&
|
|
193
|
+
published.lastChange === lastChange &&
|
|
194
|
+
published.orderError === orderError &&
|
|
195
|
+
published.selection === selected;
|
|
196
|
+
if (unchanged)
|
|
197
|
+
return;
|
|
198
|
+
// Optional keys are omitted rather than set to `undefined`, because
|
|
199
|
+
// `exactOptionalPropertyTypes` is on and a host testing `'lastChange' in
|
|
200
|
+
// snapshot` must not find an empty one.
|
|
201
|
+
published = Object.freeze({
|
|
202
|
+
status,
|
|
203
|
+
...(hydrationError === undefined ? {} : { hydrationError }),
|
|
204
|
+
issues: landed.issues,
|
|
205
|
+
landed: landed.edges,
|
|
206
|
+
projected,
|
|
207
|
+
order,
|
|
208
|
+
writes,
|
|
209
|
+
...(lastChange === undefined ? {} : { lastChange }),
|
|
210
|
+
...(orderError === undefined ? {} : { orderError }),
|
|
211
|
+
selection: selected,
|
|
212
|
+
});
|
|
213
|
+
freezeArrays(published);
|
|
214
|
+
// Isolated, and for the same reason as the guard and the deriver: a host
|
|
215
|
+
// callback that throws must not break the store. Rethrown asynchronously so
|
|
216
|
+
// it still surfaces as an unhandled error rather than being swallowed.
|
|
217
|
+
for (const listener of listeners) {
|
|
218
|
+
try {
|
|
219
|
+
listener();
|
|
220
|
+
}
|
|
221
|
+
catch (thrown) {
|
|
222
|
+
queueMicrotask(() => {
|
|
223
|
+
throw thrown;
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Recompute the order from `landed`, and record what moved.
|
|
230
|
+
*
|
|
231
|
+
* A deriver that throws is a HOST callback failing, and the write it followed
|
|
232
|
+
* has already landed — so this cannot become a failed write and must not
|
|
233
|
+
* escape into the dispatch queue, where it would strand every edit behind it.
|
|
234
|
+
* The previously derived rows stand, `orderError` says why they are stale, and
|
|
235
|
+
* no change summary is emitted for an order nobody computed.
|
|
236
|
+
*/
|
|
237
|
+
function reDerive(cause) {
|
|
238
|
+
const previous = rows;
|
|
239
|
+
let next;
|
|
240
|
+
try {
|
|
241
|
+
next = derive(landed);
|
|
242
|
+
}
|
|
243
|
+
catch (thrown) {
|
|
244
|
+
orderError = messageOf(thrown);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// RECOVERING FROM A STALE BASELINE EMITS NO SUMMARY. While `orderError`
|
|
248
|
+
// stood, the published rows were the ones from before the edit that broke
|
|
249
|
+
// the deriver — so diffing the recovered order against them would credit
|
|
250
|
+
// that edit's movement to this one. There is no baseline that would make
|
|
251
|
+
// the attribution true, so none is claimed.
|
|
252
|
+
const recovering = orderError !== undefined;
|
|
253
|
+
orderError = undefined;
|
|
254
|
+
rows = ownRows(next);
|
|
255
|
+
// Only for the edit that is still the current one. A landing that a newer
|
|
256
|
+
// proposal has already overtaken has no summary to publish — the newer edit
|
|
257
|
+
// owns that slot, and will fill it when it lands.
|
|
258
|
+
const overtaken = cause !== undefined && cause.mutationId !== currentProposal;
|
|
259
|
+
if (cause !== undefined && !recovering && !overtaken)
|
|
260
|
+
lastChange = diffOrder(previous, rows, cause);
|
|
261
|
+
}
|
|
262
|
+
function recordFor(mutationId) {
|
|
263
|
+
return records.find((record) => record.mutationId === mutationId);
|
|
264
|
+
}
|
|
265
|
+
function putRecord(next) {
|
|
266
|
+
records = recordFor(next.mutationId) === undefined
|
|
267
|
+
? [...records, next]
|
|
268
|
+
: records.map((record) => (record.mutationId === next.mutationId ? next : record));
|
|
269
|
+
}
|
|
270
|
+
function dropRecord(mutationId) {
|
|
271
|
+
records = records.filter((record) => record.mutationId !== mutationId);
|
|
272
|
+
}
|
|
273
|
+
function settle(mutationId) {
|
|
274
|
+
const resolve = settlers.get(mutationId);
|
|
275
|
+
settlers.delete(mutationId);
|
|
276
|
+
resolve?.();
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Why this edit cannot happen — structurally, then by the injected guard.
|
|
280
|
+
*
|
|
281
|
+
* A guard that throws reached no verdict, and an unknown verdict is not
|
|
282
|
+
* permission to write: it refuses, fail-closed, with the thrown message as
|
|
283
|
+
* the reason. Letting it escape would strand the whole dispatch queue behind
|
|
284
|
+
* an edit that stayed pending for ever.
|
|
285
|
+
*/
|
|
286
|
+
function refusalFor(mutation) {
|
|
287
|
+
const structural = structuralRefusal(landed, mutation);
|
|
288
|
+
if (structural !== undefined)
|
|
289
|
+
return structural;
|
|
290
|
+
if (guard === undefined)
|
|
291
|
+
return undefined;
|
|
292
|
+
try {
|
|
293
|
+
return guard({ mutation, current: landed, next: nextDocument(landed, mutation) });
|
|
294
|
+
}
|
|
295
|
+
catch (thrown) {
|
|
296
|
+
return { code: 'guard-failed', message: messageOf(thrown) };
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async function dispatch(mutation) {
|
|
300
|
+
let result;
|
|
301
|
+
try {
|
|
302
|
+
result = await source.dispatch(mutation);
|
|
303
|
+
}
|
|
304
|
+
catch (thrown) {
|
|
305
|
+
// A crashing adapter must not leave the edit pending for ever, and must
|
|
306
|
+
// not look like a success. It is the same fact as a rejection — the write
|
|
307
|
+
// did not land — so it gets the same state and the same retry.
|
|
308
|
+
putRecord({
|
|
309
|
+
mutationId: mutation.mutationId,
|
|
310
|
+
mutation,
|
|
311
|
+
state: 'failed',
|
|
312
|
+
reason: messageOf(thrown),
|
|
313
|
+
});
|
|
314
|
+
settle(mutation.mutationId);
|
|
315
|
+
publish();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
switch (result.outcome) {
|
|
319
|
+
case 'applied': {
|
|
320
|
+
adopt(result.document);
|
|
321
|
+
dropRecord(mutation.mutationId);
|
|
322
|
+
reDerive(mutation);
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
case 'unchanged': {
|
|
326
|
+
// Adopted like `applied`, because it is the same kind of claim about the
|
|
327
|
+
// document — and often said BECAUSE the store is out of date, which is
|
|
328
|
+
// exactly when not adopting it loses an edge that genuinely exists.
|
|
329
|
+
//
|
|
330
|
+
// Re-derived with NO cause: whatever moved was somebody else's doing, so
|
|
331
|
+
// attributing it to this edit would be wrong. `adopt` no-ops when the
|
|
332
|
+
// document already matches, so the ordinary case changes nothing.
|
|
333
|
+
adopt(result.document);
|
|
334
|
+
dropRecord(mutation.mutationId);
|
|
335
|
+
reDerive(undefined);
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
case 'rejected': {
|
|
339
|
+
putRecord({
|
|
340
|
+
mutationId: mutation.mutationId,
|
|
341
|
+
mutation,
|
|
342
|
+
state: 'failed',
|
|
343
|
+
reason: result.reason,
|
|
344
|
+
});
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
case 'conflict': {
|
|
348
|
+
putRecord({
|
|
349
|
+
mutationId: mutation.mutationId,
|
|
350
|
+
mutation,
|
|
351
|
+
state: 'conflict',
|
|
352
|
+
// Taken on rather than retained by reference: it is exposed through
|
|
353
|
+
// `snapshot.writes`, so a consumer must not be able to edit the
|
|
354
|
+
// adapter's own arrays through it.
|
|
355
|
+
upstream: ownDocument(result.upstream),
|
|
356
|
+
});
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
settle(mutation.mutationId);
|
|
361
|
+
publish();
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Refuse or dispatch, and put the record in the ledger either way.
|
|
365
|
+
*
|
|
366
|
+
* A refusal is recorded and NEVER dispatched — "refused before any write" is
|
|
367
|
+
* a fact about the adapter not being called, not just about the colour of the
|
|
368
|
+
* edge, and that is what the tests assert.
|
|
369
|
+
*/
|
|
370
|
+
function start(mutation) {
|
|
371
|
+
// The summary is evidence for ONE edit, and the contract is that it lasts
|
|
372
|
+
// until the next edit or an explicit dismissal. Clearing it here is what
|
|
373
|
+
// makes that true: left standing, a host would show the previous edit's
|
|
374
|
+
// blast radius beside the edit the user is making now.
|
|
375
|
+
lastChange = undefined;
|
|
376
|
+
currentProposal = mutation.mutationId;
|
|
377
|
+
const settled = new Promise((resolve) => settlers.set(mutation.mutationId, resolve));
|
|
378
|
+
const refusal = refusalFor(mutation);
|
|
379
|
+
if (refusal !== undefined) {
|
|
380
|
+
putRecord({ mutationId: mutation.mutationId, mutation, state: 'invalid', reason: refusal });
|
|
381
|
+
settle(mutation.mutationId);
|
|
382
|
+
publish();
|
|
383
|
+
return { mutationId: mutation.mutationId, settled };
|
|
384
|
+
}
|
|
385
|
+
putRecord({ mutationId: mutation.mutationId, mutation, state: 'pending' });
|
|
386
|
+
// ENQUEUE BEFORE NOTIFYING. `publish` runs subscribers synchronously, so a
|
|
387
|
+
// subscriber proposing a follow-up re-enters here — and if the queue does
|
|
388
|
+
// not yet hold this edit, the nested one takes its place in line and goes
|
|
389
|
+
// out first. Two edits on the same edge then apply in the reverse of the
|
|
390
|
+
// order the user made them.
|
|
391
|
+
queue.push({ kind: 'dispatch', mutation });
|
|
392
|
+
publish();
|
|
393
|
+
void drain();
|
|
394
|
+
return { mutationId: mutation.mutationId, settled };
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Run the queue, one authoritative operation at a time — dispatches and
|
|
398
|
+
* document reads alike, since both answer with a document.
|
|
399
|
+
*
|
|
400
|
+
* An edit that WAITED is re-checked before it goes out, because `landed` may
|
|
401
|
+
* have moved under it — a sibling edit can delete the relationship this one
|
|
402
|
+
* retypes, and dispatching against a document that no longer admits it would
|
|
403
|
+
* defeat "refused before any write". An edit that did not wait keeps the
|
|
404
|
+
* verdict `start` already reached, so the guard is called once per edit in
|
|
405
|
+
* the ordinary case and twice only when the document genuinely moved.
|
|
406
|
+
*/
|
|
407
|
+
async function drain() {
|
|
408
|
+
if (draining)
|
|
409
|
+
return;
|
|
410
|
+
draining = true;
|
|
411
|
+
try {
|
|
412
|
+
for (let next = queue.shift(); next !== undefined; next = queue.shift()) {
|
|
413
|
+
if (next.kind === 'load') {
|
|
414
|
+
next.done(await runLoad(next.first));
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
// ALWAYS re-checked, not only when something is known to have moved.
|
|
418
|
+
// Knowing that needs bookkeeping about document freshness, and every
|
|
419
|
+
// version of that bookkeeping this store has had was wrong in a way
|
|
420
|
+
// nobody could see. `refusalFor` is pure and cheap; asking it twice
|
|
421
|
+
// costs a great deal less than being wrong about when to ask.
|
|
422
|
+
const refusal = refusalFor(next.mutation);
|
|
423
|
+
if (refusal !== undefined) {
|
|
424
|
+
putRecord({
|
|
425
|
+
mutationId: next.mutation.mutationId,
|
|
426
|
+
mutation: next.mutation,
|
|
427
|
+
state: 'invalid',
|
|
428
|
+
reason: refusal,
|
|
429
|
+
});
|
|
430
|
+
settle(next.mutation.mutationId);
|
|
431
|
+
publish();
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
await dispatch(next.mutation);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
finally {
|
|
438
|
+
draining = false;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/** Queue a read of the document, behind whatever is already in flight. */
|
|
442
|
+
function load(first) {
|
|
443
|
+
return new Promise((resolve) => {
|
|
444
|
+
queue.push({ kind: 'load', first, done: resolve });
|
|
445
|
+
void drain();
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
/** A handle for a call that had nothing to act on. Already settled. */
|
|
449
|
+
function noop(mutationId) {
|
|
450
|
+
return { mutationId, settled: Promise.resolve() };
|
|
451
|
+
}
|
|
452
|
+
/** Read the document. Only ever called from the queue. */
|
|
453
|
+
/** Read the document. Resolves `true` when the read succeeded. */
|
|
454
|
+
async function runLoad(first) {
|
|
455
|
+
if (first) {
|
|
456
|
+
status = 'hydrating';
|
|
457
|
+
hydrationError = undefined;
|
|
458
|
+
publish();
|
|
459
|
+
}
|
|
460
|
+
try {
|
|
461
|
+
const document = await source.hydrate();
|
|
462
|
+
adopt(document);
|
|
463
|
+
status = 'ready';
|
|
464
|
+
hydrationError = undefined;
|
|
465
|
+
reDerive(undefined);
|
|
466
|
+
}
|
|
467
|
+
catch (thrown) {
|
|
468
|
+
hydrationError = messageOf(thrown);
|
|
469
|
+
// A failed refresh keeps the last good document and stays `ready`; only a
|
|
470
|
+
// first load that never produced one reports `failed`.
|
|
471
|
+
if (first)
|
|
472
|
+
status = 'failed';
|
|
473
|
+
publish();
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
publish();
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
return {
|
|
480
|
+
getSnapshot: () => published,
|
|
481
|
+
subscribe(listener) {
|
|
482
|
+
listeners.add(listener);
|
|
483
|
+
return () => {
|
|
484
|
+
listeners.delete(listener);
|
|
485
|
+
};
|
|
486
|
+
},
|
|
487
|
+
hydrate: async () => {
|
|
488
|
+
await load(true);
|
|
489
|
+
},
|
|
490
|
+
rehydrate: async () => {
|
|
491
|
+
await load(false);
|
|
492
|
+
},
|
|
493
|
+
propose(proposal) {
|
|
494
|
+
sequence += 1;
|
|
495
|
+
// A per-store counter, not a random identifier: identities only have to
|
|
496
|
+
// be unique within one store, and a deterministic one keeps every test
|
|
497
|
+
// that reads a mutation id reproducible.
|
|
498
|
+
return start({ ...proposal, mutationId: `m${sequence}` });
|
|
499
|
+
},
|
|
500
|
+
retry(mutationId) {
|
|
501
|
+
const record = recordFor(mutationId);
|
|
502
|
+
if (record === undefined || record.state === 'pending')
|
|
503
|
+
return noop(mutationId);
|
|
504
|
+
return start(record.mutation);
|
|
505
|
+
},
|
|
506
|
+
/**
|
|
507
|
+
* Single-step on purpose: it reads the record and acts on it with no
|
|
508
|
+
* `await` in between, so nothing can change underneath it. A resolution
|
|
509
|
+
* that had to refresh first could not have that property, which is why the
|
|
510
|
+
* store does not offer one.
|
|
511
|
+
*/
|
|
512
|
+
discardMine(mutationId) {
|
|
513
|
+
const record = recordFor(mutationId);
|
|
514
|
+
if (record === undefined || record.state === 'pending')
|
|
515
|
+
return;
|
|
516
|
+
dropRecord(mutationId);
|
|
517
|
+
publish();
|
|
518
|
+
},
|
|
519
|
+
select(edgeIds) {
|
|
520
|
+
selection = [...edgeIds];
|
|
521
|
+
publish();
|
|
522
|
+
},
|
|
523
|
+
dismissChange() {
|
|
524
|
+
lastChange = undefined;
|
|
525
|
+
publish();
|
|
526
|
+
},
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The document algebra, and the half of `invalid` that needs no graph walk.
|
|
3
|
+
*
|
|
4
|
+
* Pure and synchronous throughout: everything here answers "what would this
|
|
5
|
+
* edit produce" or "why can it not happen", and neither question has any
|
|
6
|
+
* business touching a network, a clock or the store's state.
|
|
7
|
+
*
|
|
8
|
+
* The split with {@link ../source.ts EdgeGuard} is by what the answer NEEDS TO
|
|
9
|
+
* SEE. A self-edge, an unknown reference and a duplicate are visible in the
|
|
10
|
+
* edit itself; a cycle is only visible by walking the graph, and the walk
|
|
11
|
+
* belongs with the derivation so the package family has one of it. Both halves
|
|
12
|
+
* produce the same refusal, and neither ever reaches `dispatch`.
|
|
13
|
+
*/
|
|
14
|
+
import { type EdgeId, type GraphDocument, type StoredEdge } from './model.ts';
|
|
15
|
+
import type { InvalidReason, Mutation } from './mutation.ts';
|
|
16
|
+
/**
|
|
17
|
+
* What an edit does to the projection: which landed edges it replaces, which
|
|
18
|
+
* edges it introduces, and which of them carry its state.
|
|
19
|
+
*
|
|
20
|
+
* A `delete` draws nothing and hides nothing — it MARKS the edge it is removing
|
|
21
|
+
* and leaves it visible. That is deliberate rather than a missing case: the
|
|
22
|
+
* design draws `failed` as a ghost with a ✕ terminal, and there is no ghost to
|
|
23
|
+
* draw if an optimistic delete has already taken the edge off the canvas. The
|
|
24
|
+
* edge leaves the document when the write lands, not when it is proposed.
|
|
25
|
+
*/
|
|
26
|
+
export interface EdgeChange {
|
|
27
|
+
readonly hidden: readonly EdgeId[];
|
|
28
|
+
readonly drawn: readonly StoredEdge[];
|
|
29
|
+
readonly marked: readonly EdgeId[];
|
|
30
|
+
}
|
|
31
|
+
/** The edge an edit produces, or `undefined` for one that produces none. */
|
|
32
|
+
export declare function resultingEdge(document: GraphDocument, mutation: Mutation): StoredEdge | undefined;
|
|
33
|
+
/** How an edit shows up in the projection while it is unlanded. */
|
|
34
|
+
export declare function edgeChangeFor(document: GraphDocument, mutation: Mutation): EdgeChange;
|
|
35
|
+
/** The document an edit would produce, if it landed exactly as proposed. */
|
|
36
|
+
export declare function nextDocument(document: GraphDocument, mutation: Mutation): GraphDocument;
|
|
37
|
+
/**
|
|
38
|
+
* Why this edit cannot happen, from the edit and the document alone.
|
|
39
|
+
*
|
|
40
|
+
* Returns `undefined` when nothing structural stands in the way — which is not
|
|
41
|
+
* the same as "valid": an injected guard runs afterwards and may still refuse.
|
|
42
|
+
*/
|
|
43
|
+
export declare function structuralRefusal(document: GraphDocument, mutation: Mutation): InvalidReason | undefined;
|
|
44
|
+
//# sourceMappingURL=validity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validity.d.ts","sourceRoot":"","sources":["../src/validity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,EACL,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,UAAU,EAIhB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE7D;;;;;;;;;GASG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,CAejG;AAED,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,GAAG,UAAU,CA2BrF;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,GAAG,aAAa,CASvF;AAMD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,QAAQ,GACjB,aAAa,GAAG,SAAS,CA2C3B"}
|