@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.
Files changed (47) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +239 -0
  3. package/dist/adapters/memory.d.ts +30 -0
  4. package/dist/adapters/memory.d.ts.map +1 -0
  5. package/dist/adapters/memory.js +81 -0
  6. package/dist/adapters/memory.js.map +1 -0
  7. package/dist/adapters/scripted.d.ts +67 -0
  8. package/dist/adapters/scripted.d.ts.map +1 -0
  9. package/dist/adapters/scripted.js +89 -0
  10. package/dist/adapters/scripted.js.map +1 -0
  11. package/dist/change.d.ts +75 -0
  12. package/dist/change.d.ts.map +1 -0
  13. package/dist/change.js +79 -0
  14. package/dist/change.js.map +1 -0
  15. package/dist/equality.d.ts +27 -0
  16. package/dist/equality.d.ts.map +1 -0
  17. package/dist/equality.js +60 -0
  18. package/dist/equality.js.map +1 -0
  19. package/dist/index.d.ts +22 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +21 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/model.d.ts +105 -0
  24. package/dist/model.d.ts.map +1 -0
  25. package/dist/model.js +64 -0
  26. package/dist/model.js.map +1 -0
  27. package/dist/mutation.d.ts +83 -0
  28. package/dist/mutation.d.ts.map +1 -0
  29. package/dist/mutation.js +58 -0
  30. package/dist/mutation.js.map +1 -0
  31. package/dist/source.d.ts +139 -0
  32. package/dist/source.d.ts.map +1 -0
  33. package/dist/source.js +17 -0
  34. package/dist/source.js.map +1 -0
  35. package/dist/store.d.ts +120 -0
  36. package/dist/store.d.ts.map +1 -0
  37. package/dist/store.js +529 -0
  38. package/dist/store.js.map +1 -0
  39. package/dist/validity.d.ts +44 -0
  40. package/dist/validity.d.ts.map +1 -0
  41. package/dist/validity.js +125 -0
  42. package/dist/validity.js.map +1 -0
  43. package/dist/write.d.ts +79 -0
  44. package/dist/write.d.ts.map +1 -0
  45. package/dist/write.js +84 -0
  46. package/dist/write.js.map +1 -0
  47. 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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"}
@@ -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"}
@@ -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"}
@@ -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"}
@@ -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"}