@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,125 @@
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 { isSymmetricEdgeField } from '@issuegraph/core';
15
+ import { findEdge, hasIssue, makeEdge, } from "./model.js";
16
+ /** The edge an edit produces, or `undefined` for one that produces none. */
17
+ export function resultingEdge(document, mutation) {
18
+ switch (mutation.op) {
19
+ case 'create':
20
+ return makeEdge(mutation.kind, mutation.from, mutation.to);
21
+ case 'delete':
22
+ return undefined;
23
+ case 'retype': {
24
+ const edge = findEdge(document, mutation.edgeId);
25
+ return edge === undefined ? undefined : makeEdge(mutation.nextKind, edge.from, edge.to);
26
+ }
27
+ case 'flip': {
28
+ const edge = findEdge(document, mutation.edgeId);
29
+ return edge === undefined ? undefined : makeEdge(edge.kind, edge.to, edge.from);
30
+ }
31
+ }
32
+ }
33
+ /** How an edit shows up in the projection while it is unlanded. */
34
+ export function edgeChangeFor(document, mutation) {
35
+ if (mutation.op === 'create') {
36
+ const created = makeEdge(mutation.kind, mutation.from, mutation.to);
37
+ return { hidden: [], drawn: [created], marked: [created.id] };
38
+ }
39
+ if (mutation.op === 'delete') {
40
+ return { hidden: [], drawn: [], marked: [mutation.edgeId] };
41
+ }
42
+ // retype and flip replace one edge with another, which is why they are single
43
+ // operations: one round trip, one undo entry, one order re-evaluation.
44
+ //
45
+ // The empty change IS reachable: a sibling write can land a delete of this
46
+ // edge while this one is still in flight, and an overlay on an edge that is
47
+ // no longer there draws nothing rather than resurrecting it.
48
+ const produced = resultingEdge(document, mutation);
49
+ if (produced === undefined)
50
+ return { hidden: [], drawn: [], marked: [] };
51
+ // A REPLACEMENT THAT PRODUCES THE SAME IDENTITY IS A MARK, NOT A SWAP. A
52
+ // retype to the kind the edge already has, and a flip of a symmetric edge,
53
+ // both name the edge they started from — so hiding the original while drawing
54
+ // the replacement would hide and redraw one identity, and the projection
55
+ // (which filters hidden ids out of BOTH sources) would erase a real, landed
56
+ // relationship because the user made a no-op edit. Both are refused anyway;
57
+ // this is what lets the refusal be SHOWN on the edge it is about.
58
+ if (produced.id === mutation.edgeId) {
59
+ return { hidden: [], drawn: [], marked: [mutation.edgeId] };
60
+ }
61
+ return { hidden: [mutation.edgeId], drawn: [produced], marked: [produced.id] };
62
+ }
63
+ /** The document an edit would produce, if it landed exactly as proposed. */
64
+ export function nextDocument(document, mutation) {
65
+ const change = edgeChangeFor(document, mutation);
66
+ const removed = new Set([
67
+ ...change.hidden,
68
+ ...(mutation.op === 'delete' ? [mutation.edgeId] : []),
69
+ ]);
70
+ const kept = document.edges.filter((edge) => !removed.has(edge.id));
71
+ const added = change.drawn.filter((edge) => !kept.some((existing) => existing.id === edge.id));
72
+ return { issues: document.issues, edges: [...kept, ...added] };
73
+ }
74
+ function refuse(code, message) {
75
+ return { code, message };
76
+ }
77
+ /**
78
+ * Why this edit cannot happen, from the edit and the document alone.
79
+ *
80
+ * Returns `undefined` when nothing structural stands in the way — which is not
81
+ * the same as "valid": an injected guard runs afterwards and may still refuse.
82
+ */
83
+ export function structuralRefusal(document, mutation) {
84
+ if (mutation.op === 'create') {
85
+ if (mutation.from === mutation.to) {
86
+ return refuse('self-edge', 'An issue cannot carry a relationship to itself.');
87
+ }
88
+ for (const ref of [mutation.from, mutation.to]) {
89
+ if (!hasIssue(document, ref)) {
90
+ return refuse('unknown-issue', `This document holds no issue ${ref}.`);
91
+ }
92
+ }
93
+ const produced = makeEdge(mutation.kind, mutation.from, mutation.to);
94
+ if (findEdge(document, produced.id) !== undefined) {
95
+ return refuse('duplicate-edge', `${mutation.from} is already ${mutation.kind} ${mutation.to}.`);
96
+ }
97
+ return undefined;
98
+ }
99
+ const edge = findEdge(document, mutation.edgeId);
100
+ if (edge === undefined) {
101
+ return refuse('unknown-edge', 'That relationship is no longer in this document.');
102
+ }
103
+ if (mutation.op === 'delete')
104
+ return undefined;
105
+ if (mutation.op === 'retype' && mutation.nextKind === edge.kind) {
106
+ return refuse('unchanged-kind', `That relationship is already ${edge.kind}.`);
107
+ }
108
+ if (mutation.op === 'flip') {
109
+ if (isSymmetricEdgeField(edge.kind)) {
110
+ return refuse('symmetric-edge', `${edge.kind} carries no direction, so there is nothing to flip.`);
111
+ }
112
+ if (edge.from === edge.to) {
113
+ return refuse('self-edge', 'A relationship from an issue to itself has no direction to flip.');
114
+ }
115
+ }
116
+ const produced = resultingEdge(document, mutation);
117
+ // `produced` cannot be undefined here — the edge was found above and neither
118
+ // remaining operation deletes. Narrowed rather than asserted so a future
119
+ // operation added to the union has to say what it produces.
120
+ if (produced !== undefined && produced.id !== edge.id && findEdge(document, produced.id) !== undefined) {
121
+ return refuse('duplicate-edge', `${produced.from} is already ${produced.kind} ${produced.to}.`);
122
+ }
123
+ return undefined;
124
+ }
125
+ //# sourceMappingURL=validity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validity.js","sourceRoot":"","sources":["../src/validity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * The write ledger and the projection built from it — the functional core of
3
+ * the write loop. The store (the imperative shell) holds these values, performs
4
+ * the IO, and notifies; every decision about what a record becomes is made
5
+ * here, where it can be tested without a clock or an adapter.
6
+ *
7
+ * The rule that shapes the whole file: **a failed write is marked, never
8
+ * reverted.** The user's work stays on the canvas carrying a state, and the
9
+ * only call that removes it is one the user makes.
10
+ */
11
+ import type { EdgeId, GraphDocument, StoredEdge } from './model.ts';
12
+ import type { EdgeState, InvalidReason, Mutation, MutationId } from './mutation.ts';
13
+ /**
14
+ * An edit the store is still carrying.
15
+ *
16
+ * A record that lands is REMOVED, not marked `applied`: the landed document is
17
+ * then the evidence, and a settled-successful record would be a second copy of
18
+ * a fact that can go stale against it.
19
+ */
20
+ export type WriteRecord = {
21
+ readonly mutationId: MutationId;
22
+ readonly mutation: Mutation;
23
+ readonly state: 'pending';
24
+ } | {
25
+ readonly mutationId: MutationId;
26
+ readonly mutation: Mutation;
27
+ readonly state: 'invalid';
28
+ readonly reason: InvalidReason;
29
+ } | {
30
+ readonly mutationId: MutationId;
31
+ readonly mutation: Mutation;
32
+ readonly state: 'failed';
33
+ readonly reason: string;
34
+ } | {
35
+ readonly mutationId: MutationId;
36
+ readonly mutation: Mutation;
37
+ readonly state: 'conflict';
38
+ /** The authoritative document as it stood when the conflict was reported. Held, never merged. */
39
+ /**
40
+ * The document as the adapter reported it at the moment of the conflict.
41
+ * Retained for the host to show; never adopted. See the README's
42
+ * "Resolving a conflict".
43
+ */
44
+ readonly upstream: GraphDocument;
45
+ };
46
+ /** The state a record contributes to the edges it marks. */
47
+ export declare function edgeStateOf(record: WriteRecord): EdgeState;
48
+ /** A landed or optimistic edge, with whatever states currently apply to it. */
49
+ export interface ProjectedEdge extends StoredEdge {
50
+ /**
51
+ * Every state this edge carries, in the canonical order of `EDGE_STATES`.
52
+ *
53
+ * A list rather than one value because the states are orthogonal: an edge can
54
+ * be `selected` while it is `pending-write`, and a viewer draws the selection
55
+ * halo on top of the marching dash rather than choosing between them.
56
+ */
57
+ readonly states: readonly EdgeState[];
58
+ /**
59
+ * The unsettled edits marking this edge. A host reads the reason, and calls
60
+ * `retry` / `discardMine`, through these identifiers.
61
+ */
62
+ readonly writes: readonly MutationId[];
63
+ }
64
+ /** Whether any edit is still in flight, which is what holds the order still. */
65
+ export declare function anyPending(records: readonly WriteRecord[]): boolean;
66
+ /**
67
+ * The edges a viewer should draw: the landed document plus every unsettled
68
+ * edit's overlay, each carrying its state.
69
+ *
70
+ * Overlays apply in proposal order, so two edits touching one edge compose in
71
+ * the order the user made them.
72
+ *
73
+ * This is the ONLY place optimistic edges exist. The selection order is derived
74
+ * from the landed edges alone, which is what makes "optimistic rendering yes,
75
+ * optimistic re-ordering no" a property of the structure rather than a rule
76
+ * someone has to remember.
77
+ */
78
+ export declare function project(landed: GraphDocument, records: readonly WriteRecord[], selection: ReadonlySet<EdgeId>): readonly ProjectedEdge[];
79
+ //# sourceMappingURL=write.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../src/write.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAIpF;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,GAC3F;IACE,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;CAChC,GACD;IACE,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GACD;IACE,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,iGAAiG;IACjG;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;CAClC,CAAC;AAEN,4DAA4D;AAC5D,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAW1D;AAED,+EAA+E;AAC/E,MAAM,WAAW,aAAc,SAAQ,UAAU;IAC/C;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;CACxC;AAED,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,OAAO,CAEnE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CACrB,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,GAC7B,SAAS,aAAa,EAAE,CAwC1B"}
package/dist/write.js ADDED
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The write ledger and the projection built from it — the functional core of
3
+ * the write loop. The store (the imperative shell) holds these values, performs
4
+ * the IO, and notifies; every decision about what a record becomes is made
5
+ * here, where it can be tested without a clock or an adapter.
6
+ *
7
+ * The rule that shapes the whole file: **a failed write is marked, never
8
+ * reverted.** The user's work stays on the canvas carrying a state, and the
9
+ * only call that removes it is one the user makes.
10
+ */
11
+ import { EDGE_STATES } from "./mutation.js";
12
+ import { edgeChangeFor } from "./validity.js";
13
+ /** The state a record contributes to the edges it marks. */
14
+ export function edgeStateOf(record) {
15
+ switch (record.state) {
16
+ case 'pending':
17
+ return 'pending-write';
18
+ case 'invalid':
19
+ return 'invalid';
20
+ case 'failed':
21
+ return 'failed';
22
+ case 'conflict':
23
+ return 'conflict';
24
+ }
25
+ }
26
+ /** Whether any edit is still in flight, which is what holds the order still. */
27
+ export function anyPending(records) {
28
+ return records.some((record) => record.state === 'pending');
29
+ }
30
+ /**
31
+ * The edges a viewer should draw: the landed document plus every unsettled
32
+ * edit's overlay, each carrying its state.
33
+ *
34
+ * Overlays apply in proposal order, so two edits touching one edge compose in
35
+ * the order the user made them.
36
+ *
37
+ * This is the ONLY place optimistic edges exist. The selection order is derived
38
+ * from the landed edges alone, which is what makes "optimistic rendering yes,
39
+ * optimistic re-ordering no" a property of the structure rather than a rule
40
+ * someone has to remember.
41
+ */
42
+ export function project(landed, records, selection) {
43
+ const hidden = new Set();
44
+ const drawn = [];
45
+ const states = new Map();
46
+ const writes = new Map();
47
+ for (const record of records) {
48
+ const change = edgeChangeFor(landed, record.mutation);
49
+ for (const id of change.hidden)
50
+ hidden.add(id);
51
+ for (const edge of change.drawn)
52
+ drawn.push(edge);
53
+ for (const id of change.marked) {
54
+ const forEdge = states.get(id) ?? new Set();
55
+ forEdge.add(edgeStateOf(record));
56
+ states.set(id, forEdge);
57
+ writes.set(id, [...(writes.get(id) ?? []), record.mutationId]);
58
+ }
59
+ }
60
+ const seen = new Set();
61
+ const visible = [];
62
+ for (const edge of [...landed.edges, ...drawn]) {
63
+ if (hidden.has(edge.id) || seen.has(edge.id))
64
+ continue;
65
+ seen.add(edge.id);
66
+ visible.push(edge);
67
+ }
68
+ return visible.map((edge) => {
69
+ // Copied rather than mutated in place: `states` is the map built above, and
70
+ // adding to its entry here would make the projection depend on how many
71
+ // times it had been walked.
72
+ const applied = new Set(states.get(edge.id));
73
+ if (selection.has(edge.id))
74
+ applied.add('selected');
75
+ return {
76
+ ...edge,
77
+ // Canonical order, so two projections of the same state compare equal and
78
+ // a viewer's memoisation is not defeated by set iteration order.
79
+ states: EDGE_STATES.filter((state) => applied.has(state)),
80
+ writes: writes.get(edge.id) ?? [],
81
+ };
82
+ });
83
+ }
84
+ //# sourceMappingURL=write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write.js","sourceRoot":"","sources":["../src/write.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@issuegraph/store",
3
+ "version": "0.1.0",
4
+ "description": "A framework-free client store for an Issuegraph document, plus the data-source port a host plugs its tracker into. Renders edits optimistically, dispatches every mutation outward, and never re-evaluates the selection order until the write lands.",
5
+ "keywords": [
6
+ "issuegraph",
7
+ "dependency-graph",
8
+ "backlog",
9
+ "scheduling",
10
+ "optimistic-updates",
11
+ "store"
12
+ ],
13
+ "license": "Apache-2.0",
14
+ "author": "Autonomy LLC",
15
+ "type": "module",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "LICENSE",
25
+ "README.md"
26
+ ],
27
+ "sideEffects": false,
28
+ "publishConfig": {
29
+ "access": "public",
30
+ "provenance": true
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/autnmy/issuegraph.git",
35
+ "directory": "packages/store"
36
+ },
37
+ "homepage": "https://github.com/autnmy/issuegraph#readme",
38
+ "bugs": {
39
+ "url": "https://github.com/autnmy/issuegraph/issues"
40
+ },
41
+ "engines": {
42
+ "node": ">=18"
43
+ },
44
+ "dependencies": {
45
+ "@issuegraph/core": "^0.1.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^22.18.0",
49
+ "typescript": "^6.0.3"
50
+ },
51
+ "scripts": {
52
+ "build": "tsc -p tsconfig.json",
53
+ "typecheck": "tsc -p tsconfig.test.json",
54
+ "test": "node --test --test-timeout=20000 \"src/**/*.test.ts\""
55
+ }
56
+ }