@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
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scripted adapter: asynchronous, and able to fail on purpose.
|
|
3
|
+
*
|
|
4
|
+
* It is a second adapter rather than the in-memory one with a flag, and the
|
|
5
|
+
* structural difference is deliberate: **it does not settle until it is told
|
|
6
|
+
* to.** That is the only way to observe the store mid-flight, which is the only
|
|
7
|
+
* way to assert that the order stays put while a write is pending — and a flag
|
|
8
|
+
* on a synchronous adapter could never provide it.
|
|
9
|
+
*
|
|
10
|
+
* Two audiences. Inside this package it is what induces `failed` and `conflict`
|
|
11
|
+
* rather than asserting they exist. Outside it, it is the harness a host writes
|
|
12
|
+
* its own adapter's tests against, which is why it ships rather than living in
|
|
13
|
+
* a test file.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* A data source that hands control of every settlement to its caller.
|
|
17
|
+
*
|
|
18
|
+
* `seed` is what it hydrates. `apply` computes the document an `'applied'`
|
|
19
|
+
* settlement produces — injected rather than reimplemented here, so a test can
|
|
20
|
+
* reuse an in-memory source's behaviour or supply its own without this adapter
|
|
21
|
+
* growing a second copy of the edit semantics.
|
|
22
|
+
*/
|
|
23
|
+
export function createScriptedSource(seed, apply) {
|
|
24
|
+
let document = { issues: [...seed.issues], edges: [...seed.edges] };
|
|
25
|
+
const waiting = [];
|
|
26
|
+
let watchers = [];
|
|
27
|
+
function take(mutationId) {
|
|
28
|
+
const index = mutationId === undefined
|
|
29
|
+
? 0
|
|
30
|
+
: waiting.findIndex((entry) => entry.mutationId === mutationId);
|
|
31
|
+
const entry = index < 0 ? undefined : waiting[index];
|
|
32
|
+
if (entry === undefined) {
|
|
33
|
+
throw new Error(mutationId === undefined
|
|
34
|
+
? 'scripted source: nothing is waiting to be settled'
|
|
35
|
+
: `scripted source: ${mutationId} is not waiting to be settled`);
|
|
36
|
+
}
|
|
37
|
+
waiting.splice(index, 1);
|
|
38
|
+
return entry;
|
|
39
|
+
}
|
|
40
|
+
function answer(entry, outcome) {
|
|
41
|
+
if (outcome === 'unchanged') {
|
|
42
|
+
entry.resolve({ outcome: 'unchanged', document });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (outcome !== 'applied') {
|
|
46
|
+
entry.resolve(outcome);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
document = apply(document, entry.mutation);
|
|
50
|
+
entry.resolve({ outcome: 'applied', document });
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
current: () => document,
|
|
54
|
+
pending: () => waiting.map(({ mutationId, mutation }) => ({ mutationId, mutation })),
|
|
55
|
+
hydrate: () => Promise.resolve(document),
|
|
56
|
+
dispatch(mutation) {
|
|
57
|
+
const settlement = new Promise((resolve, reject) => {
|
|
58
|
+
waiting.push({ mutationId: mutation.mutationId, mutation, resolve, reject });
|
|
59
|
+
});
|
|
60
|
+
const entry = { mutationId: mutation.mutationId, mutation };
|
|
61
|
+
const matched = watchers.filter((watcher) => watcher.mutationId === undefined || watcher.mutationId === mutation.mutationId);
|
|
62
|
+
watchers = watchers.filter((watcher) => !matched.includes(watcher));
|
|
63
|
+
for (const watcher of matched)
|
|
64
|
+
watcher.notify(entry);
|
|
65
|
+
return settlement;
|
|
66
|
+
},
|
|
67
|
+
whenPending(mutationId) {
|
|
68
|
+
const already = mutationId === undefined
|
|
69
|
+
? waiting[0]
|
|
70
|
+
: waiting.find((entry) => entry.mutationId === mutationId);
|
|
71
|
+
if (already !== undefined) {
|
|
72
|
+
return Promise.resolve({ mutationId: already.mutationId, mutation: already.mutation });
|
|
73
|
+
}
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
watchers.push({ mutationId, notify: resolve });
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
settleNext(outcome) {
|
|
79
|
+
answer(take(undefined), outcome);
|
|
80
|
+
},
|
|
81
|
+
settle(mutationId, outcome) {
|
|
82
|
+
answer(take(mutationId), outcome);
|
|
83
|
+
},
|
|
84
|
+
throwNext(error) {
|
|
85
|
+
take(undefined).reject(error);
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=scripted.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scripted.js","sourceRoot":"","sources":["../../src/adapters/scripted.ts"],"names":[],"mappings":"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"}
|
package/dist/change.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The re-evaluate loop: what an edit did to the order, as data.
|
|
3
|
+
*
|
|
4
|
+
* The design's answer to "how does an owner see cause and effect without
|
|
5
|
+
* re-scanning three hundred rows" is a one-line blast-radius summary plus a
|
|
6
|
+
* delta chip on ONLY the rows that changed. This module computes both — and
|
|
7
|
+
* stops there.
|
|
8
|
+
*
|
|
9
|
+
* Two things it deliberately does not do:
|
|
10
|
+
*
|
|
11
|
+
* - **It does not derive an order.** It diffs two orders it is handed. A sort
|
|
12
|
+
* in here would be the second derivation the reference packages exist to
|
|
13
|
+
* avoid having.
|
|
14
|
+
* - **It does not format a sentence.** The summary's counts ship as numbers, so
|
|
15
|
+
* a host writes "3 rows moved · 1 promoted" in its own words and its own
|
|
16
|
+
* language. A package that shipped the string would ship a language choice
|
|
17
|
+
* its consumers could not theme away.
|
|
18
|
+
*
|
|
19
|
+
* And one thing it must never grow: a timer. The design rejects the toast
|
|
20
|
+
* precisely because the only evidence the edit worked disappears before it is
|
|
21
|
+
* read. `lastChange` persists until the next edit or an explicit dismissal.
|
|
22
|
+
*/
|
|
23
|
+
import type { IssueRef } from './model.ts';
|
|
24
|
+
import type { Mutation } from './mutation.ts';
|
|
25
|
+
import type { OrderRow } from './source.ts';
|
|
26
|
+
/** How far and which way a row moved. */
|
|
27
|
+
export interface RankMovement {
|
|
28
|
+
readonly direction: 'up' | 'down';
|
|
29
|
+
/** Positions travelled. Always positive. */
|
|
30
|
+
readonly by: number;
|
|
31
|
+
readonly from: number;
|
|
32
|
+
readonly to: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* What happened to one row. Only rows that changed get one, and a row can have
|
|
36
|
+
* changed in more than one way at once — being promoted usually moves you.
|
|
37
|
+
*/
|
|
38
|
+
export interface RankDelta {
|
|
39
|
+
readonly ref: IssueRef;
|
|
40
|
+
readonly movement?: RankMovement;
|
|
41
|
+
/** `promoted` = held became ready. `newly-held` = the reverse. */
|
|
42
|
+
readonly readiness?: 'promoted' | 'newly-held';
|
|
43
|
+
/** Rows entering or leaving the order entirely (a `duplicate-of`, a closure). */
|
|
44
|
+
readonly presence?: 'entered' | 'left';
|
|
45
|
+
}
|
|
46
|
+
/** The blast radius, as counts a host can render into its own sentence. */
|
|
47
|
+
export interface ChangeCounts {
|
|
48
|
+
readonly moved: number;
|
|
49
|
+
readonly promoted: number;
|
|
50
|
+
readonly newlyHeld: number;
|
|
51
|
+
readonly entered: number;
|
|
52
|
+
readonly left: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* What the last landed edit did to the order.
|
|
56
|
+
*
|
|
57
|
+
* An edit that lands and moves nothing still produces one, with an empty
|
|
58
|
+
* `deltas` and all-zero counts. "This edit changed nothing" is a FINDING — it
|
|
59
|
+
* is the one an owner auditing an encoding most needs — and reporting it as the
|
|
60
|
+
* absence of a change would hide it.
|
|
61
|
+
*/
|
|
62
|
+
export interface OrderChange {
|
|
63
|
+
readonly mutation: Mutation;
|
|
64
|
+
readonly deltas: readonly RankDelta[];
|
|
65
|
+
readonly counts: ChangeCounts;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The deltas between two orders, for the rows that changed and no others.
|
|
69
|
+
*
|
|
70
|
+
* Rows that did not move, did not change readiness and did not enter or leave
|
|
71
|
+
* appear nowhere in the result — which is what lets a viewer leave them
|
|
72
|
+
* completely alone rather than re-rendering the whole rail.
|
|
73
|
+
*/
|
|
74
|
+
export declare function diffOrder(previous: readonly OrderRow[], next: readonly OrderRow[], mutation: Mutation): OrderChange;
|
|
75
|
+
//# sourceMappingURL=change.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"change.d.ts","sourceRoot":"","sources":["../src/change.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,yCAAyC;AACzC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;IAClC,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC;IACjC,kEAAkE;IAClE,QAAQ,CAAC,SAAS,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;IAC/C,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;CACxC;AAED,2EAA2E;AAC3E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;CAC/B;AAMD;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,SAAS,QAAQ,EAAE,EAC7B,IAAI,EAAE,SAAS,QAAQ,EAAE,EACzB,QAAQ,EAAE,QAAQ,GACjB,WAAW,CAgDb"}
|
package/dist/change.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The re-evaluate loop: what an edit did to the order, as data.
|
|
3
|
+
*
|
|
4
|
+
* The design's answer to "how does an owner see cause and effect without
|
|
5
|
+
* re-scanning three hundred rows" is a one-line blast-radius summary plus a
|
|
6
|
+
* delta chip on ONLY the rows that changed. This module computes both — and
|
|
7
|
+
* stops there.
|
|
8
|
+
*
|
|
9
|
+
* Two things it deliberately does not do:
|
|
10
|
+
*
|
|
11
|
+
* - **It does not derive an order.** It diffs two orders it is handed. A sort
|
|
12
|
+
* in here would be the second derivation the reference packages exist to
|
|
13
|
+
* avoid having.
|
|
14
|
+
* - **It does not format a sentence.** The summary's counts ship as numbers, so
|
|
15
|
+
* a host writes "3 rows moved · 1 promoted" in its own words and its own
|
|
16
|
+
* language. A package that shipped the string would ship a language choice
|
|
17
|
+
* its consumers could not theme away.
|
|
18
|
+
*
|
|
19
|
+
* And one thing it must never grow: a timer. The design rejects the toast
|
|
20
|
+
* precisely because the only evidence the edit worked disappears before it is
|
|
21
|
+
* read. `lastChange` persists until the next edit or an explicit dismissal.
|
|
22
|
+
*/
|
|
23
|
+
function byRef(rows) {
|
|
24
|
+
return new Map(rows.map((row) => [row.ref, row]));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The deltas between two orders, for the rows that changed and no others.
|
|
28
|
+
*
|
|
29
|
+
* Rows that did not move, did not change readiness and did not enter or leave
|
|
30
|
+
* appear nowhere in the result — which is what lets a viewer leave them
|
|
31
|
+
* completely alone rather than re-rendering the whole rail.
|
|
32
|
+
*/
|
|
33
|
+
export function diffOrder(previous, next, mutation) {
|
|
34
|
+
const before = byRef(previous);
|
|
35
|
+
const after = byRef(next);
|
|
36
|
+
const deltas = [];
|
|
37
|
+
for (const row of next) {
|
|
38
|
+
const was = before.get(row.ref);
|
|
39
|
+
if (was === undefined) {
|
|
40
|
+
deltas.push({ ref: row.ref, presence: 'entered' });
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const movement = was.rank === row.rank
|
|
44
|
+
? undefined
|
|
45
|
+
: {
|
|
46
|
+
direction: row.rank < was.rank ? 'up' : 'down',
|
|
47
|
+
by: Math.abs(row.rank - was.rank),
|
|
48
|
+
from: was.rank,
|
|
49
|
+
to: row.rank,
|
|
50
|
+
};
|
|
51
|
+
const readiness = was.ready === row.ready ? undefined : row.ready ? 'promoted' : 'newly-held';
|
|
52
|
+
if (movement === undefined && readiness === undefined)
|
|
53
|
+
continue;
|
|
54
|
+
// Keys are omitted rather than set to `undefined`: `exactOptionalPropertyTypes`
|
|
55
|
+
// is on, and a host reading `'movement' in delta` must not see a key that is
|
|
56
|
+
// there but empty.
|
|
57
|
+
deltas.push({
|
|
58
|
+
ref: row.ref,
|
|
59
|
+
...(movement === undefined ? {} : { movement }),
|
|
60
|
+
...(readiness === undefined ? {} : { readiness }),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
for (const row of previous) {
|
|
64
|
+
if (!after.has(row.ref))
|
|
65
|
+
deltas.push({ ref: row.ref, presence: 'left' });
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
mutation,
|
|
69
|
+
deltas,
|
|
70
|
+
counts: {
|
|
71
|
+
moved: deltas.filter((delta) => delta.movement !== undefined).length,
|
|
72
|
+
promoted: deltas.filter((delta) => delta.readiness === 'promoted').length,
|
|
73
|
+
newlyHeld: deltas.filter((delta) => delta.readiness === 'newly-held').length,
|
|
74
|
+
entered: deltas.filter((delta) => delta.presence === 'entered').length,
|
|
75
|
+
left: deltas.filter((delta) => delta.presence === 'left').length,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=change.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"change.js","sourceRoot":"","sources":["../src/change.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One structural comparison, used by every slice the store reuses.
|
|
3
|
+
*
|
|
4
|
+
* A comparator that lists the fields it cares about is a staleness bug waiting
|
|
5
|
+
* for the next field: leave one off and the slice compares equal, the old one
|
|
6
|
+
* is republished, and a host renders state the store has already replaced.
|
|
7
|
+
*
|
|
8
|
+
* A list of fields is the defect, not any particular list. Nothing here
|
|
9
|
+
* enumerates anything, so a field added tomorrow is compared tomorrow.
|
|
10
|
+
*
|
|
11
|
+
* The one comparator that stays bespoke is `sameEdgeSet`, and for a reason
|
|
12
|
+
* this cannot express: it is deliberately order-INSENSITIVE, because an adapter
|
|
13
|
+
* may return its edges in whatever order its storage yields and a reordering is
|
|
14
|
+
* not a change.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Whether two values say the same thing, all the way down.
|
|
18
|
+
*
|
|
19
|
+
* `Object.is` at the top, so a reference match short-circuits and `NaN`
|
|
20
|
+
* compares equal to itself — which matters because a rank is a number and a
|
|
21
|
+
* deriver that produces `NaN` should not make every snapshot look different
|
|
22
|
+
* from the last one for ever.
|
|
23
|
+
*/
|
|
24
|
+
export declare function sameValue(a: unknown, b: unknown, depth?: number): boolean;
|
|
25
|
+
/** Whether two lists say the same thing, element for element and in order. */
|
|
26
|
+
export declare function sameList(a: readonly unknown[], b: readonly unknown[]): boolean;
|
|
27
|
+
//# sourceMappingURL=equality.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"equality.d.ts","sourceRoot":"","sources":["../src/equality.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAiBH;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,OAAO,CAgBpE;AAED,8EAA8E;AAC9E,wBAAgB,QAAQ,CAAC,CAAC,EAAE,SAAS,OAAO,EAAE,EAAE,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAE9E"}
|
package/dist/equality.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One structural comparison, used by every slice the store reuses.
|
|
3
|
+
*
|
|
4
|
+
* A comparator that lists the fields it cares about is a staleness bug waiting
|
|
5
|
+
* for the next field: leave one off and the slice compares equal, the old one
|
|
6
|
+
* is republished, and a host renders state the store has already replaced.
|
|
7
|
+
*
|
|
8
|
+
* A list of fields is the defect, not any particular list. Nothing here
|
|
9
|
+
* enumerates anything, so a field added tomorrow is compared tomorrow.
|
|
10
|
+
*
|
|
11
|
+
* The one comparator that stays bespoke is `sameEdgeSet`, and for a reason
|
|
12
|
+
* this cannot express: it is deliberately order-INSENSITIVE, because an adapter
|
|
13
|
+
* may return its edges in whatever order its storage yields and a reordering is
|
|
14
|
+
* not a change.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* How deep a comparison will go before giving up and reporting "different".
|
|
18
|
+
*
|
|
19
|
+
* A bound rather than cycle tracking, because the failure it guards is a host
|
|
20
|
+
* value the store did not build — a row from an injected deriver — and the
|
|
21
|
+
* safe direction is obvious: over-reporting a change costs one redundant
|
|
22
|
+
* publish, while looping costs the process. Nothing the store itself builds is
|
|
23
|
+
* more than a handful of levels deep.
|
|
24
|
+
*/
|
|
25
|
+
const MAX_DEPTH = 100;
|
|
26
|
+
function isPlainObject(value) {
|
|
27
|
+
return typeof value === 'object' && value !== null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Whether two values say the same thing, all the way down.
|
|
31
|
+
*
|
|
32
|
+
* `Object.is` at the top, so a reference match short-circuits and `NaN`
|
|
33
|
+
* compares equal to itself — which matters because a rank is a number and a
|
|
34
|
+
* deriver that produces `NaN` should not make every snapshot look different
|
|
35
|
+
* from the last one for ever.
|
|
36
|
+
*/
|
|
37
|
+
export function sameValue(a, b, depth = 0) {
|
|
38
|
+
if (Object.is(a, b))
|
|
39
|
+
return true;
|
|
40
|
+
if (depth >= MAX_DEPTH)
|
|
41
|
+
return false;
|
|
42
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
43
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
|
|
44
|
+
return false;
|
|
45
|
+
return a.every((item, index) => sameValue(item, b[index], depth + 1));
|
|
46
|
+
}
|
|
47
|
+
if (!isPlainObject(a) || !isPlainObject(b))
|
|
48
|
+
return false;
|
|
49
|
+
const keys = Object.keys(a);
|
|
50
|
+
// Counted both ways, so a key present on one side and absent on the other is
|
|
51
|
+
// a difference even when every shared key matches.
|
|
52
|
+
if (keys.length !== Object.keys(b).length)
|
|
53
|
+
return false;
|
|
54
|
+
return keys.every((key) => Object.hasOwn(b, key) && sameValue(a[key], b[key], depth + 1));
|
|
55
|
+
}
|
|
56
|
+
/** Whether two lists say the same thing, element for element and in order. */
|
|
57
|
+
export function sameList(a, b) {
|
|
58
|
+
return sameValue(a, b);
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=equality.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"equality.js","sourceRoot":"","sources":["../src/equality.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;GAQG;AACH,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AACrD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,CAAU,EAAE,CAAU,EAAE,KAAK,GAAG,CAAC;IACzD,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACjC,IAAI,KAAK,IAAI,SAAS;QAAE,OAAO,KAAK,CAAC;IAErC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAClF,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IACxE,CAAC;IAED,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAEzD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,6EAA6E;IAC7E,mDAAmD;IACnD,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;AAC5F,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,QAAQ,CAAC,CAAqB,EAAE,CAAqB;IACnE,OAAO,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACzB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@issuegraph/store` — a client store for an Issuegraph document, and the port
|
|
3
|
+
* a host plugs its tracker in through.
|
|
4
|
+
*
|
|
5
|
+
* The package fetches nothing, authenticates nothing and persists nothing. It
|
|
6
|
+
* holds the document, renders edits optimistically, dispatches every one of
|
|
7
|
+
* them outward, and refuses to re-evaluate the selection order until a write
|
|
8
|
+
* has actually landed.
|
|
9
|
+
*
|
|
10
|
+
* @see https://github.com/autnmy/issuegraph/blob/main/SPEC.md
|
|
11
|
+
*/
|
|
12
|
+
export { type EdgeId, type EdgeKind, type GraphDocument, type IssueRef, type IssueState, type StoredEdge, type StoredIssue, edgeId, findEdge, hasIssue, makeEdge, sameEdgeSet, } from './model.ts';
|
|
13
|
+
export { type EdgeState, type InvalidCode, type InvalidReason, type Mutation, type MutationId, type Proposal, EDGE_STATES, INVALID_CODES, isEdgeState, } from './mutation.ts';
|
|
14
|
+
export type { DataSource, DispatchResult, EdgeGuard, EdgeGuardContext, OrderDeriver, OrderRow, } from './source.ts';
|
|
15
|
+
export { type EdgeChange, edgeChangeFor, nextDocument, resultingEdge, structuralRefusal, } from './validity.ts';
|
|
16
|
+
export { type ProjectedEdge, type WriteRecord, anyPending, edgeStateOf, project } from './write.ts';
|
|
17
|
+
export { sameList, sameValue } from './equality.ts';
|
|
18
|
+
export { type ChangeCounts, type OrderChange, type RankDelta, type RankMovement, diffOrder, } from './change.ts';
|
|
19
|
+
export { type HydrationStatus, type OrderStatus, type OrderView, type ProposalHandle, type Store, type StoreConfig, type StoreSnapshot, createStore, } from './store.ts';
|
|
20
|
+
export { type MemorySource, createMemorySource } from './adapters/memory.ts';
|
|
21
|
+
export { type PendingDispatch, type ScriptedOutcome, type ScriptedSource, createScriptedSource, } from './adapters/scripted.ts';
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,WAAW,EACX,aAAa,EACb,WAAW,GACZ,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,UAAU,EACV,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,QAAQ,GACT,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,KAAK,UAAU,EACf,aAAa,EACb,YAAY,EACZ,aAAa,EACb,iBAAiB,GAClB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEpG,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,SAAS,GACV,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,KAAK,EACV,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,KAAK,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@issuegraph/store` — a client store for an Issuegraph document, and the port
|
|
3
|
+
* a host plugs its tracker in through.
|
|
4
|
+
*
|
|
5
|
+
* The package fetches nothing, authenticates nothing and persists nothing. It
|
|
6
|
+
* holds the document, renders edits optimistically, dispatches every one of
|
|
7
|
+
* them outward, and refuses to re-evaluate the selection order until a write
|
|
8
|
+
* has actually landed.
|
|
9
|
+
*
|
|
10
|
+
* @see https://github.com/autnmy/issuegraph/blob/main/SPEC.md
|
|
11
|
+
*/
|
|
12
|
+
export { edgeId, findEdge, hasIssue, makeEdge, sameEdgeSet, } from "./model.js";
|
|
13
|
+
export { EDGE_STATES, INVALID_CODES, isEdgeState, } from "./mutation.js";
|
|
14
|
+
export { edgeChangeFor, nextDocument, resultingEdge, structuralRefusal, } from "./validity.js";
|
|
15
|
+
export { anyPending, edgeStateOf, project } from "./write.js";
|
|
16
|
+
export { sameList, sameValue } from "./equality.js";
|
|
17
|
+
export { diffOrder, } from "./change.js";
|
|
18
|
+
export { createStore, } from "./store.js";
|
|
19
|
+
export { createMemorySource } from "./adapters/memory.js";
|
|
20
|
+
export { createScriptedSource, } from "./adapters/scripted.js";
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAQL,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAOL,WAAW,EACX,aAAa,EACb,WAAW,GACZ,MAAM,eAAe,CAAC;AAWvB,OAAO,EAEL,aAAa,EACb,YAAY,EACZ,aAAa,EACb,iBAAiB,GAClB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAwC,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEpG,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,EAKL,SAAS,GACV,MAAM,aAAa,CAAC;AAErB,OAAO,EAQL,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAqB,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAIL,oBAAoB,GACrB,MAAM,wBAAwB,CAAC"}
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The document this store holds, and the identity rule for an edge in it.
|
|
3
|
+
*
|
|
4
|
+
* The shapes here are the *store's* schema, not a tracker's: an adapter maps its
|
|
5
|
+
* own issues onto these and the store never learns what it mapped from. That is
|
|
6
|
+
* the whole of BYO-DataSource on this side of the port.
|
|
7
|
+
*
|
|
8
|
+
* @see https://github.com/autnmy/issuegraph/blob/main/SPEC.md
|
|
9
|
+
*/
|
|
10
|
+
import { type EdgeField, type Priority } from '@issuegraph/core';
|
|
11
|
+
/**
|
|
12
|
+
* An issue reference, exactly as the adapter emits it.
|
|
13
|
+
*
|
|
14
|
+
* The specification admits two forms — a bare integer and the qualified
|
|
15
|
+
* `owner/repo#123` (§4.2) — and normalising between them is the reader's job.
|
|
16
|
+
* This store therefore treats a reference as an OPAQUE identity: it compares
|
|
17
|
+
* references, and it never parses, reformats or resolves one. An adapter's
|
|
18
|
+
* contract is that equal references mean the same issue.
|
|
19
|
+
*/
|
|
20
|
+
export type IssueRef = string;
|
|
21
|
+
/** A relationship kind. The format's vocabulary, taken from the shared package. */
|
|
22
|
+
export type EdgeKind = EdgeField;
|
|
23
|
+
/**
|
|
24
|
+
* An edge's identity, derived from its content by {@link edgeId} rather than
|
|
25
|
+
* assigned by a host.
|
|
26
|
+
*
|
|
27
|
+
* Derived because the store has to recognise the edge an adapter returns as the
|
|
28
|
+
* same edge it drew optimistically. A host-assigned identifier would push that
|
|
29
|
+
* correlation onto every adapter, and an adapter that got it wrong would leave
|
|
30
|
+
* an optimistic edge stranded beside its own landed twin.
|
|
31
|
+
*/
|
|
32
|
+
export type EdgeId = string;
|
|
33
|
+
/** Whether an issue is still open. Readiness (§6.2) is the deriver's to compute. */
|
|
34
|
+
export type IssueState = 'open' | 'closed';
|
|
35
|
+
/**
|
|
36
|
+
* One issue, reduced to what a relationship surface renders.
|
|
37
|
+
*
|
|
38
|
+
* `priority` is the *declared* priority (§4.3.5) as the adapter resolved it —
|
|
39
|
+
* the tracker's own convention where one exists, the frontmatter field where it
|
|
40
|
+
* does not. Resolving that precedence is the adapter's job, not the store's.
|
|
41
|
+
*/
|
|
42
|
+
export interface StoredIssue {
|
|
43
|
+
readonly ref: IssueRef;
|
|
44
|
+
readonly title: string;
|
|
45
|
+
readonly state: IssueState;
|
|
46
|
+
readonly priority?: Priority;
|
|
47
|
+
readonly url?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* One relationship, held as a directed pair even for the symmetric kinds.
|
|
51
|
+
*
|
|
52
|
+
* The pair is retained rather than normalised away because a symmetric edge is
|
|
53
|
+
* still *written* on one issue pointing at another (§4.3.4), and an editor has
|
|
54
|
+
* to know which issue carries the field in order to delete it. Symmetry is
|
|
55
|
+
* expressed in {@link edgeId} instead, so `A serialize-with B` and
|
|
56
|
+
* `B serialize-with A` are one edge without either of them losing its carrier.
|
|
57
|
+
*/
|
|
58
|
+
export interface StoredEdge {
|
|
59
|
+
readonly id: EdgeId;
|
|
60
|
+
readonly kind: EdgeKind;
|
|
61
|
+
readonly from: IssueRef;
|
|
62
|
+
readonly to: IssueRef;
|
|
63
|
+
}
|
|
64
|
+
/** Everything the store holds about the backlog at one instant. */
|
|
65
|
+
export interface GraphDocument {
|
|
66
|
+
readonly issues: readonly StoredIssue[];
|
|
67
|
+
readonly edges: readonly StoredEdge[];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The identity of an edge, as a pure function of its content.
|
|
71
|
+
*
|
|
72
|
+
* References are percent-encoded before joining, so the separator cannot appear
|
|
73
|
+
* inside a reference: `encodeURIComponent` escapes `|`, which means no reference
|
|
74
|
+
* — however a tracker spells it — can forge the identity of a different edge.
|
|
75
|
+
*
|
|
76
|
+
* The two symmetric kinds (§4.3.4, §4.3.7) sort their endpoints, so the two
|
|
77
|
+
* spellings of one relationship collapse to one identity. The directed kinds do
|
|
78
|
+
* not, because for them the direction IS the fact: `A blocked-by B` and
|
|
79
|
+
* `B blocked-by A` are opposite claims about which issue may start.
|
|
80
|
+
*/
|
|
81
|
+
export declare function edgeId(kind: EdgeKind, from: IssueRef, to: IssueRef): EdgeId;
|
|
82
|
+
/** An edge with its identity filled in from its content. */
|
|
83
|
+
export declare function makeEdge(kind: EdgeKind, from: IssueRef, to: IssueRef): StoredEdge;
|
|
84
|
+
/** The edge carrying an identity, or `undefined` when the document has none. */
|
|
85
|
+
export declare function findEdge(document: GraphDocument, id: EdgeId): StoredEdge | undefined;
|
|
86
|
+
/** Whether the document holds an issue under this reference. */
|
|
87
|
+
export declare function hasIssue(document: GraphDocument, ref: IssueRef): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Whether two edge lists state the same set of relationships, carriers included.
|
|
90
|
+
*
|
|
91
|
+
* Compared as a SET, not a sequence: an adapter is free to return its edges in
|
|
92
|
+
* whatever order its own storage yields, and treating a reordering as a change
|
|
93
|
+
* would re-derive the order and emit a change summary for a write that moved
|
|
94
|
+
* nothing.
|
|
95
|
+
*
|
|
96
|
+
* IDENTITY IS NOT ENOUGH, and the exception is the symmetric kinds. Their two
|
|
97
|
+
* spellings share one identity by design, but `StoredEdge` deliberately keeps
|
|
98
|
+
* the directed pair, because that pair is the field an editor deletes and the
|
|
99
|
+
* endpoints a retype to a directed kind inherits. Compare on identity alone and
|
|
100
|
+
* a rehydrate that reverses the carrier reads as "nothing changed" — after
|
|
101
|
+
* which a retype writes the OPPOSITE direction, which §17b names as the most
|
|
102
|
+
* common encoding mistake there is.
|
|
103
|
+
*/
|
|
104
|
+
export declare function sameEdgeSet(a: readonly StoredEdge[], b: readonly StoredEdge[]): boolean;
|
|
105
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAwB,MAAM,kBAAkB,CAAC;AAEvF;;;;;;;;GAQG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE9B,mFAAmF;AACnF,MAAM,MAAM,QAAQ,GAAG,SAAS,CAAC;AAEjC;;;;;;;;GAQG;AACH,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAE5B,oFAAoF;AACpF,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;CACvB;AAED,mEAAmE;AACnE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;CACvC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,GAAG,MAAM,CAG3E;AAED,4DAA4D;AAC5D,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,GAAG,UAAU,CAEjF;AAED,gFAAgF;AAChF,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAEpF;AAED,gEAAgE;AAChE,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAExE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,SAAS,UAAU,EAAE,EAAE,CAAC,EAAE,SAAS,UAAU,EAAE,GAAG,OAAO,CAOvF"}
|
package/dist/model.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The document this store holds, and the identity rule for an edge in it.
|
|
3
|
+
*
|
|
4
|
+
* The shapes here are the *store's* schema, not a tracker's: an adapter maps its
|
|
5
|
+
* own issues onto these and the store never learns what it mapped from. That is
|
|
6
|
+
* the whole of BYO-DataSource on this side of the port.
|
|
7
|
+
*
|
|
8
|
+
* @see https://github.com/autnmy/issuegraph/blob/main/SPEC.md
|
|
9
|
+
*/
|
|
10
|
+
import { isSymmetricEdgeField } from '@issuegraph/core';
|
|
11
|
+
/**
|
|
12
|
+
* The identity of an edge, as a pure function of its content.
|
|
13
|
+
*
|
|
14
|
+
* References are percent-encoded before joining, so the separator cannot appear
|
|
15
|
+
* inside a reference: `encodeURIComponent` escapes `|`, which means no reference
|
|
16
|
+
* — however a tracker spells it — can forge the identity of a different edge.
|
|
17
|
+
*
|
|
18
|
+
* The two symmetric kinds (§4.3.4, §4.3.7) sort their endpoints, so the two
|
|
19
|
+
* spellings of one relationship collapse to one identity. The directed kinds do
|
|
20
|
+
* not, because for them the direction IS the fact: `A blocked-by B` and
|
|
21
|
+
* `B blocked-by A` are opposite claims about which issue may start.
|
|
22
|
+
*/
|
|
23
|
+
export function edgeId(kind, from, to) {
|
|
24
|
+
const [first, second] = isSymmetricEdgeField(kind) && to < from ? [to, from] : [from, to];
|
|
25
|
+
return `${kind}|${encodeURIComponent(first)}|${encodeURIComponent(second)}`;
|
|
26
|
+
}
|
|
27
|
+
/** An edge with its identity filled in from its content. */
|
|
28
|
+
export function makeEdge(kind, from, to) {
|
|
29
|
+
return { id: edgeId(kind, from, to), kind, from, to };
|
|
30
|
+
}
|
|
31
|
+
/** The edge carrying an identity, or `undefined` when the document has none. */
|
|
32
|
+
export function findEdge(document, id) {
|
|
33
|
+
return document.edges.find((edge) => edge.id === id);
|
|
34
|
+
}
|
|
35
|
+
/** Whether the document holds an issue under this reference. */
|
|
36
|
+
export function hasIssue(document, ref) {
|
|
37
|
+
return document.issues.some((issue) => issue.ref === ref);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Whether two edge lists state the same set of relationships, carriers included.
|
|
41
|
+
*
|
|
42
|
+
* Compared as a SET, not a sequence: an adapter is free to return its edges in
|
|
43
|
+
* whatever order its own storage yields, and treating a reordering as a change
|
|
44
|
+
* would re-derive the order and emit a change summary for a write that moved
|
|
45
|
+
* nothing.
|
|
46
|
+
*
|
|
47
|
+
* IDENTITY IS NOT ENOUGH, and the exception is the symmetric kinds. Their two
|
|
48
|
+
* spellings share one identity by design, but `StoredEdge` deliberately keeps
|
|
49
|
+
* the directed pair, because that pair is the field an editor deletes and the
|
|
50
|
+
* endpoints a retype to a directed kind inherits. Compare on identity alone and
|
|
51
|
+
* a rehydrate that reverses the carrier reads as "nothing changed" — after
|
|
52
|
+
* which a retype writes the OPPOSITE direction, which §17b names as the most
|
|
53
|
+
* common encoding mistake there is.
|
|
54
|
+
*/
|
|
55
|
+
export function sameEdgeSet(a, b) {
|
|
56
|
+
if (a.length !== b.length)
|
|
57
|
+
return false;
|
|
58
|
+
const byId = new Map(a.map((edge) => [edge.id, edge]));
|
|
59
|
+
return b.every((edge) => {
|
|
60
|
+
const other = byId.get(edge.id);
|
|
61
|
+
return other !== undefined && other.from === edge.from && other.to === edge.to;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAiC,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAmEvF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,MAAM,CAAC,IAAc,EAAE,IAAc,EAAE,EAAY;IACjE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,oBAAoB,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC1F,OAAO,GAAG,IAAI,IAAI,kBAAkB,CAAC,KAAK,CAAC,IAAI,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;AAC9E,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,QAAQ,CAAC,IAAc,EAAE,IAAc,EAAE,EAAY;IACnE,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AACxD,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,QAAQ,CAAC,QAAuB,EAAE,EAAU;IAC1D,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,QAAQ,CAAC,QAAuB,EAAE,GAAa;IAC7D,OAAO,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,WAAW,CAAC,CAAwB,EAAE,CAAwB;IAC5E,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACvD,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChC,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;IACjF,CAAC,CAAC,CAAC;AACL,CAAC"}
|