@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,83 @@
1
+ /**
2
+ * What an edit IS, and the states an edge can be drawn in while one is in
3
+ * flight.
4
+ *
5
+ * The two live together because they are the same design decision seen from two
6
+ * sides: the mutation set is closed so that one user act is one round trip, and
7
+ * the state set is *orthogonal to edge type* so that a pending `blocked-by` is
8
+ * still recognisably a `blocked-by`.
9
+ */
10
+ import type { EdgeId, EdgeKind, IssueRef } from './model.ts';
11
+ /**
12
+ * The states an edge can carry, from the workspace design's §17b.
13
+ *
14
+ * They are orthogonal to each other and to the edge's kind: an edge keeps its
15
+ * type identity and gains a state as an OVERLAY, so combinations need no new
16
+ * symbols and a viewer can draw `selected` on top of `pending-write` without a
17
+ * fifteenth case. A projected edge therefore carries a *list* of states, never
18
+ * one.
19
+ *
20
+ * - `selected` — the only state that is not about a write.
21
+ * - `pending-write` — drawn optimistically; the order has not re-evaluated yet.
22
+ * - `invalid` — refused before any write. Never dispatched.
23
+ * - `failed` — the write was rejected; nothing changed upstream.
24
+ * - `conflict` — the issue changed upstream mid-edit; both versions are held.
25
+ */
26
+ export declare const EDGE_STATES: readonly ["selected", "pending-write", "invalid", "failed", "conflict"];
27
+ /** One of the five edge states. */
28
+ export type EdgeState = (typeof EDGE_STATES)[number];
29
+ /** Narrow an arbitrary string to an edge state. */
30
+ export declare function isEdgeState(value: string): value is EdgeState;
31
+ /** Correlates a dispatch with its settlement. Unique within one store. */
32
+ export type MutationId = string;
33
+ /**
34
+ * An edit, before the store has minted an identity for it.
35
+ *
36
+ * Four operations and no more. `retype` and `flip` are their own operations
37
+ * rather than a delete followed by a create, because the design requires a
38
+ * retype to be one operation, one round trip and one undo entry — and a flip is
39
+ * the same argument applied to direction. Composing them would make one user
40
+ * act produce two dispatches and two order re-evaluations.
41
+ */
42
+ export type Proposal = {
43
+ readonly op: 'create';
44
+ readonly kind: EdgeKind;
45
+ readonly from: IssueRef;
46
+ readonly to: IssueRef;
47
+ } | {
48
+ readonly op: 'delete';
49
+ readonly edgeId: EdgeId;
50
+ } | {
51
+ readonly op: 'retype';
52
+ readonly edgeId: EdgeId;
53
+ readonly nextKind: EdgeKind;
54
+ } | {
55
+ readonly op: 'flip';
56
+ readonly edgeId: EdgeId;
57
+ };
58
+ /** A proposal the store has accepted responsibility for, with its identity. */
59
+ export type Mutation = Proposal & {
60
+ readonly mutationId: MutationId;
61
+ };
62
+ /** Why an edit was refused before any write was attempted. */
63
+ export interface InvalidReason {
64
+ /** A stable machine-readable code. A host may key a message off it. */
65
+ readonly code: InvalidCode;
66
+ /** A plain sentence, safe to show as the inline reason beside the ghost edge. */
67
+ readonly message: string;
68
+ }
69
+ /**
70
+ * The refusals this package can reach.
71
+ *
72
+ * `would-cycle` is listed here but is never produced by this package: it is what
73
+ * an injected guard returns, and it is named so hosts and guards agree on one
74
+ * code rather than inventing two.
75
+ *
76
+ * `guard-failed` is the opposite case — the guard threw, so no verdict was
77
+ * reached at all. It refuses rather than proceeding, because an unknown verdict
78
+ * is not permission to write.
79
+ */
80
+ export declare const INVALID_CODES: readonly ["self-edge", "unknown-issue", "unknown-edge", "duplicate-edge", "unchanged-kind", "symmetric-edge", "would-cycle", "guard-failed"];
81
+ /** A refusal code. */
82
+ export type InvalidCode = (typeof INVALID_CODES)[number];
83
+ //# sourceMappingURL=mutation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation.d.ts","sourceRoot":"","sources":["../src/mutation.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE7D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,WAAW,yEAMb,CAAC;AAEZ,mCAAmC;AACnC,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAIrD,mDAAmD;AACnD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,SAAS,CAE7D;AAED,0EAA0E;AAC1E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC;AAEhC;;;;;;;;GAQG;AACH,MAAM,MAAM,QAAQ,GAChB;IAAE,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAA;CAAE,GAClG;IAAE,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAC/E;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD,+EAA+E;AAC/E,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG;IAAE,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAA;CAAE,CAAC;AAEtE,8DAA8D;AAC9D,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,8IASf,CAAC;AAEZ,sBAAsB;AACtB,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * What an edit IS, and the states an edge can be drawn in while one is in
3
+ * flight.
4
+ *
5
+ * The two live together because they are the same design decision seen from two
6
+ * sides: the mutation set is closed so that one user act is one round trip, and
7
+ * the state set is *orthogonal to edge type* so that a pending `blocked-by` is
8
+ * still recognisably a `blocked-by`.
9
+ */
10
+ /**
11
+ * The states an edge can carry, from the workspace design's §17b.
12
+ *
13
+ * They are orthogonal to each other and to the edge's kind: an edge keeps its
14
+ * type identity and gains a state as an OVERLAY, so combinations need no new
15
+ * symbols and a viewer can draw `selected` on top of `pending-write` without a
16
+ * fifteenth case. A projected edge therefore carries a *list* of states, never
17
+ * one.
18
+ *
19
+ * - `selected` — the only state that is not about a write.
20
+ * - `pending-write` — drawn optimistically; the order has not re-evaluated yet.
21
+ * - `invalid` — refused before any write. Never dispatched.
22
+ * - `failed` — the write was rejected; nothing changed upstream.
23
+ * - `conflict` — the issue changed upstream mid-edit; both versions are held.
24
+ */
25
+ export const EDGE_STATES = Object.freeze([
26
+ 'selected',
27
+ 'pending-write',
28
+ 'invalid',
29
+ 'failed',
30
+ 'conflict',
31
+ ]);
32
+ const EDGE_STATE_SET = new Set(EDGE_STATES);
33
+ /** Narrow an arbitrary string to an edge state. */
34
+ export function isEdgeState(value) {
35
+ return EDGE_STATE_SET.has(value);
36
+ }
37
+ /**
38
+ * The refusals this package can reach.
39
+ *
40
+ * `would-cycle` is listed here but is never produced by this package: it is what
41
+ * an injected guard returns, and it is named so hosts and guards agree on one
42
+ * code rather than inventing two.
43
+ *
44
+ * `guard-failed` is the opposite case — the guard threw, so no verdict was
45
+ * reached at all. It refuses rather than proceeding, because an unknown verdict
46
+ * is not permission to write.
47
+ */
48
+ export const INVALID_CODES = Object.freeze([
49
+ 'self-edge',
50
+ 'unknown-issue',
51
+ 'unknown-edge',
52
+ 'duplicate-edge',
53
+ 'unchanged-kind',
54
+ 'symmetric-edge',
55
+ 'would-cycle',
56
+ 'guard-failed',
57
+ ]);
58
+ //# sourceMappingURL=mutation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation.js","sourceRoot":"","sources":["../src/mutation.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;IACvC,UAAU;IACV,eAAe;IACf,SAAS;IACT,QAAQ;IACR,UAAU;CACF,CAAC,CAAC;AAKZ,MAAM,cAAc,GAAwB,IAAI,GAAG,CAAS,WAAW,CAAC,CAAC;AAEzE,mDAAmD;AACnD,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,OAAO,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC;AA+BD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC;IACzC,WAAW;IACX,eAAe;IACf,cAAc;IACd,gBAAgB;IAChB,gBAAgB;IAChB,gBAAgB;IAChB,aAAa;IACb,cAAc;CACN,CAAC,CAAC"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * The ports. Everything the host supplies, and nothing this package implements.
3
+ *
4
+ * Three of them, and each exists because the alternative would put something in
5
+ * the package that cannot be there:
6
+ *
7
+ * - {@link DataSource} — auth, network and persistence. A package that fetched
8
+ * would be a client for one tracker with a pluggable skin.
9
+ * - {@link OrderDeriver} — the selection order. Writing one here would make a
10
+ * second implementation of the thing the reference packages exist to have one
11
+ * of. The store owns *when* the order is recomputed; the deriver owns *what*
12
+ * it is.
13
+ * - {@link EdgeGuard} — refusals that need to see the graph, cycles above all.
14
+ * Same reason: the walk belongs with the derivation, not beside it.
15
+ */
16
+ import type { GraphDocument, IssueRef } from './model.ts';
17
+ import type { InvalidReason, Mutation } from './mutation.ts';
18
+ /**
19
+ * What the host plugs its tracker in through.
20
+ *
21
+ * Two methods, deliberately. Anything an adapter needs beyond them — a token, a
22
+ * retry policy, a rate limiter, a cache — stays on its own side of this
23
+ * interface, where the store cannot see it and does not have to model it.
24
+ *
25
+ * **The store dispatches once per mutation and never retries on its own.** A
26
+ * retry is a user act (the `retry` on a failed edge), so an adapter must not
27
+ * assume an idempotency it has not been given.
28
+ *
29
+ * **It also runs ONE AUTHORITATIVE OPERATION AT A TIME**, queueing the rest.
30
+ * `hydrate` and an `applied` `dispatch` both answer with a full authoritative
31
+ * document and this interface gives neither a version, so two in flight cannot
32
+ * be ordered by anything the store can observe: the later answer arriving
33
+ * first, then the earlier one, silently rolls an edge back out — after which
34
+ * the order derives from a backlog missing a real relationship. Adding a
35
+ * version here would push bookkeeping onto every adapter, so the store declines
36
+ * to create the situation instead. An adapter may therefore assume its
37
+ * `dispatch` is never re-entered, and never overlapped by a `hydrate`.
38
+ */
39
+ export interface DataSource {
40
+ /** The whole document. Called on `hydrate()` and again on `rehydrate()`. */
41
+ hydrate(): Promise<GraphDocument>;
42
+ /** Perform one edit. Resolving with an outcome; rejecting is read as `rejected`. */
43
+ dispatch(mutation: Mutation): Promise<DispatchResult>;
44
+ }
45
+ /**
46
+ * How a dispatch settled.
47
+ *
48
+ * Four arms. `applied` and `unchanged` are both authoritative statements about
49
+ * the document and both carry one; they differ only in whether this edit caused
50
+ * the difference, which is what decides whether a change summary is emitted.
51
+ * Collapsing them would attribute somebody else's movement to this edit;
52
+ * letting `unchanged` carry nothing left the store unable to act on a perfectly
53
+ * good answer.
54
+ */
55
+ export type DispatchResult = {
56
+ /**
57
+ * The edit landed. `document` is the AUTHORITATIVE state afterwards, not
58
+ * a patch and not a slice: a partial answer would need merge rules, and
59
+ * merge rules are exactly where an optimistic store goes wrong. The
60
+ * document is small enough to be client-side by construction, so sending
61
+ * all of it removes the whole class.
62
+ *
63
+ * A WHOLE DOCUMENT, ISSUES INCLUDED — not an edge set. When this answered
64
+ * with edges alone, "what the store holds is newer" was true of edges and
65
+ * false of issues, so the store's freshness question had two dimensions
66
+ * and only one of them was tracked: a conflict snapshot skipped as stale
67
+ * threw away the only copy of the issue updates it carried. One kind of
68
+ * authoritative answer collapses that back to one question. An adapter
69
+ * that changed no issues simply returns the ones it holds.
70
+ */
71
+ readonly outcome: 'applied';
72
+ readonly document: GraphDocument;
73
+ } | {
74
+ /**
75
+ * The edit changed nothing, and `document` is the AUTHORITATIVE state —
76
+ * the same contract as `applied`, because this is the same kind of claim.
77
+ *
78
+ * It carries one for a reason that is easy to miss: "nothing to do" is
79
+ * often said precisely BECAUSE the store is out of date. Another client
80
+ * creates the same edge, this edit arrives, the adapter has nothing to
81
+ * apply — and a bare `unchanged` would leave the store without an edge
82
+ * that genuinely exists, ranking a backlog missing a real relationship.
83
+ *
84
+ * The store adopts it and re-derives, but emits NO change summary: the
85
+ * movement, if any, was somebody else's and this edit did not cause it.
86
+ */
87
+ readonly outcome: 'unchanged';
88
+ readonly document: GraphDocument;
89
+ } | {
90
+ /** The write was refused upstream. Nothing changed there. */
91
+ readonly outcome: 'rejected';
92
+ readonly reason: string;
93
+ } | {
94
+ /**
95
+ * The issue changed upstream mid-edit, and `upstream` is the authoritative
96
+ * document as it stood at that moment.
97
+ *
98
+ * The store retains it and never merges it into anything. What a host may
99
+ * then do with it is the README's "Resolving a conflict"; this type says
100
+ * only what the value is.
101
+ */
102
+ readonly outcome: 'conflict';
103
+ readonly upstream: GraphDocument;
104
+ };
105
+ /** One row of the selection order, as the deriver computed it. */
106
+ export interface OrderRow {
107
+ readonly ref: IssueRef;
108
+ /** 0-based position. The rail renders these in ascending order. */
109
+ readonly rank: number;
110
+ /** Whether the issue may be started now (§6.2). */
111
+ readonly ready: boolean;
112
+ /** Why it may not, in the deriver's own words. Empty when ready. */
113
+ readonly holdReasons: readonly string[];
114
+ }
115
+ /**
116
+ * The selection order for a document.
117
+ *
118
+ * Required, not defaulted. A store cannot invent an order, and a default would
119
+ * be the second derivation this port exists to avoid.
120
+ */
121
+ export type OrderDeriver = (document: GraphDocument) => readonly OrderRow[];
122
+ /** What a guard is shown. Both documents, so it need not recompute either. */
123
+ export interface EdgeGuardContext {
124
+ readonly mutation: Mutation;
125
+ /** The landed document, before the edit. */
126
+ readonly current: GraphDocument;
127
+ /** The document the edit would produce, if it landed. */
128
+ readonly next: GraphDocument;
129
+ }
130
+ /**
131
+ * A refusal that needs the graph — a `blocked-by` that would cycle, above all.
132
+ *
133
+ * Optional: the store's own structural refusals run either way, and a host
134
+ * without a graph-aware guard still cannot create a self-edge or a duplicate.
135
+ * A guard's refusal is identical in effect to a structural one — the edge is
136
+ * drawn `invalid` and **is never dispatched**.
137
+ */
138
+ export type EdgeGuard = (context: EdgeGuardContext) => InvalidReason | undefined;
139
+ //# sourceMappingURL=source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,UAAU;IACzB,4EAA4E;IAC5E,OAAO,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IAClC,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACvD;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,GACtB;IACE;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;CAClC,GACD;IACE;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;CAClC,GACD;IACE,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GACD;IACE;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;CAClC,CAAC;AAEN,kEAAkE;AAClE,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC;IACvB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,aAAa,KAAK,SAAS,QAAQ,EAAE,CAAC;AAE5E,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,EAAE,gBAAgB,KAAK,aAAa,GAAG,SAAS,CAAC"}
package/dist/source.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The ports. Everything the host supplies, and nothing this package implements.
3
+ *
4
+ * Three of them, and each exists because the alternative would put something in
5
+ * the package that cannot be there:
6
+ *
7
+ * - {@link DataSource} — auth, network and persistence. A package that fetched
8
+ * would be a client for one tracker with a pluggable skin.
9
+ * - {@link OrderDeriver} — the selection order. Writing one here would make a
10
+ * second implementation of the thing the reference packages exist to have one
11
+ * of. The store owns *when* the order is recomputed; the deriver owns *what*
12
+ * it is.
13
+ * - {@link EdgeGuard} — refusals that need to see the graph, cycles above all.
14
+ * Same reason: the walk belongs with the derivation, not beside it.
15
+ */
16
+ export {};
17
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * The store: the one stateful module, and the only place IO happens.
3
+ *
4
+ * Everything it decides is decided by the pure modules beside it — validity,
5
+ * the write ledger and projection, the order diff. What lives here is the state
6
+ * itself, the calls into the injected data source, and the notification.
7
+ *
8
+ * **The rule the structure exists to enforce:** the selection order is derived
9
+ * from `landed` and from nothing else. `projected` — the landed document plus
10
+ * every unsettled edit — is built in one place and is not in scope where the
11
+ * deriver is called, so an optimistic edge cannot reach the order by anyone
12
+ * forgetting a filter.
13
+ */
14
+ import { type EdgeId, type StoredEdge, type StoredIssue } from './model.ts';
15
+ import type { MutationId, Proposal } from './mutation.ts';
16
+ import type { DataSource, EdgeGuard, OrderDeriver, OrderRow } from './source.ts';
17
+ import { type OrderChange } from './change.ts';
18
+ import { type ProjectedEdge, type WriteRecord } from './write.ts';
19
+ /** Where hydration has got to. A failure is a state, not an exception. */
20
+ export type HydrationStatus = 'idle' | 'hydrating' | 'ready' | 'failed';
21
+ /**
22
+ * Whether the order describes everything the user has done.
23
+ *
24
+ * `held` means at least one edit is in flight, so the rows shown are the ones
25
+ * from before it — held still and labelled, which the design prefers to a
26
+ * half-computed order the rail cannot vouch for.
27
+ */
28
+ export type OrderStatus = 'settled' | 'held';
29
+ /** The order, and whether it is currently telling the whole story. */
30
+ export interface OrderView {
31
+ readonly rows: readonly OrderRow[];
32
+ readonly status: OrderStatus;
33
+ }
34
+ /** Everything a host renders from. Frozen, and stable while nothing changes. */
35
+ export interface StoreSnapshot {
36
+ readonly status: HydrationStatus;
37
+ /**
38
+ * Why the last load failed, when it did. A failed *rehydrate* sets this while
39
+ * leaving `status` at `ready`: the previous document is still good, and
40
+ * reporting the store as unloaded because a refresh blipped would throw away
41
+ * a working surface.
42
+ */
43
+ readonly hydrationError?: string;
44
+ readonly issues: readonly StoredIssue[];
45
+ /** What the data source has confirmed. The only input to the order. */
46
+ readonly landed: readonly StoredEdge[];
47
+ /** What to draw: `landed` plus every unsettled edit, each carrying its states. */
48
+ readonly projected: readonly ProjectedEdge[];
49
+ readonly order: OrderView;
50
+ /** Edits still carrying a state: pending, invalid, failed or conflicted. */
51
+ readonly writes: readonly WriteRecord[];
52
+ /** What the last landed edit did to the order. Cleared only by a call. */
53
+ readonly lastChange?: OrderChange;
54
+ /**
55
+ * Why the order could not be recomputed, when the injected deriver threw.
56
+ *
57
+ * `order.rows` is then the last order that WAS derived — stale, and labelled
58
+ * as such, on the same principle as the held order: a rail that cannot vouch
59
+ * for a ranking says so rather than showing a half-computed one.
60
+ */
61
+ readonly orderError?: string;
62
+ readonly selection: readonly EdgeId[];
63
+ }
64
+ /** A proposal the store has taken on, and a promise for its settlement. */
65
+ export interface ProposalHandle {
66
+ readonly mutationId: MutationId;
67
+ /**
68
+ * Resolves when this edit stops being `pending` — landed, refused, failed or
69
+ * conflicted. It never rejects: a failure is a state on the snapshot, and a
70
+ * host that has to `catch` to find out is a host that can forget to.
71
+ */
72
+ readonly settled: Promise<void>;
73
+ }
74
+ /** What the host supplies. Only `source` and `derive` are required. */
75
+ export interface StoreConfig {
76
+ readonly source: DataSource;
77
+ /**
78
+ * The selection order. Required: a store cannot invent an order, and a
79
+ * default would be a second implementation of one.
80
+ */
81
+ readonly derive: OrderDeriver;
82
+ /** Refusals that need the graph — a `blocked-by` that would cycle. */
83
+ readonly guard?: EdgeGuard;
84
+ }
85
+ /** The store's public surface. */
86
+ export interface Store {
87
+ getSnapshot(): StoreSnapshot;
88
+ subscribe(listener: () => void): () => void;
89
+ /** Load the document. Safe to call once; use `rehydrate` afterwards. */
90
+ hydrate(): Promise<void>;
91
+ /**
92
+ * Re-read the document from the source, keeping unsettled edits.
93
+ *
94
+ * Queued behind anything already in flight, and resolves once it has had its
95
+ * turn — a refresh that overtook a dispatch could be overwritten by that
96
+ * dispatch's answer, which was computed before it.
97
+ */
98
+ rehydrate(): Promise<void>;
99
+ /** Propose an edit. Renders immediately; the order does not move yet. */
100
+ propose(proposal: Proposal): ProposalHandle;
101
+ /** Re-dispatch an unsettled edit, unchanged, against the document as it stands. */
102
+ retry(mutationId: MutationId): ProposalHandle;
103
+ /**
104
+ * Drop an unsettled edit's overlay. The only call that removes the user's
105
+ * work, and it adopts nothing. See the README's "Resolving a conflict".
106
+ */
107
+ discardMine(mutationId: MutationId): void;
108
+ /** Set the selected edges. Selection is client state; it writes nothing. */
109
+ select(edgeIds: readonly EdgeId[]): void;
110
+ /** Clear the change summary. The only thing that does, besides the next edit. */
111
+ dismissChange(): void;
112
+ }
113
+ /**
114
+ * Create a store over one data source.
115
+ *
116
+ * Nothing is loaded until `hydrate()` is called, so a host can construct the
117
+ * store during render and start the load where it wants to.
118
+ */
119
+ export declare function createStore(config: StoreConfig): Store;
120
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}