opencode-wait-a-minute 0.0.0-stage → 1.1.0-rc.8

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 (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +318 -2
  3. package/index.js +1836 -0
  4. package/package.json +70 -4
  5. package/skills/codebase-design/SKILL.md +114 -0
  6. package/skills/design-contract/SKILL.md +27 -0
  7. package/skills/design-critique/SKILL.md +19 -0
  8. package/skills/design-system/SKILL.md +17 -0
  9. package/skills/design-system/references/image-analysis.md +14 -0
  10. package/skills/design-system/references/tokens.md +32 -0
  11. package/skills/design-system/references/ui-rules.md +14 -0
  12. package/skills/diagnosing-bugs/SKILL.md +138 -0
  13. package/skills/frontend-design/SKILL.md +36 -0
  14. package/skills/handoff/SKILL.md +16 -0
  15. package/skills/improve-codebase-architecture/SKILL.md +71 -0
  16. package/skills/linter-negative/SKILL.md +74 -0
  17. package/skills/no-slop-ui/SKILL.md +26 -0
  18. package/skills/prototype/SKILL.md +26 -0
  19. package/skills/registry.json +44087 -0
  20. package/skills/tastemaker/SKILL.md +8 -0
  21. package/skills/tastemaker/references/tastemaker.md +14 -0
  22. package/skills/tdd/SKILL.md +38 -0
  23. package/skills/visual-review/SKILL.md +103 -0
  24. package/skills/visual-review/references/review-gates.md +42 -0
  25. package/skills/visual-review/references/surface-archetypes.md +37 -0
  26. package/skills/wizard/SKILL.md +44 -0
  27. package/skills/writing-for-agents/SKILL.md +81 -0
  28. package/src/cognition/cognition-store.js +251 -0
  29. package/src/cognition/cognitive-state.js +126 -0
  30. package/src/cognition/hypothesis-manager.js +113 -0
  31. package/src/cognition/observation-engine.js +112 -0
  32. package/src/cognition/observation-provenance.js +268 -0
  33. package/src/context/active-context-boundary.js +68 -0
  34. package/src/context/assembly.js +494 -0
  35. package/src/context/assessment-rules.js +55 -0
  36. package/src/context/capsule-staleness.js +267 -0
  37. package/src/context/capsules.js +2 -0
  38. package/src/context/compare-array.js +98 -0
  39. package/src/context/compare-object.js +89 -0
  40. package/src/context/compare-values.js +42 -0
  41. package/src/context/context-assembly-contract.js +71 -0
  42. package/src/context/context-budget-manager.js +232 -0
  43. package/src/context/context-builder.js +49 -0
  44. package/src/context/context-cache.js +55 -0
  45. package/src/context/context-capture.js +243 -0
  46. package/src/context/context-classification.js +199 -0
  47. package/src/context/context-compaction.js +179 -0
  48. package/src/context/context-decision-audit.js +267 -0
  49. package/src/context/context-demotion.js +173 -0
  50. package/src/context/context-dictionary.js +106 -0
  51. package/src/context/context-drift-detection.js +225 -0
  52. package/src/context/context-evaluation.js +374 -0
  53. package/src/context/context-event-ingress.js +100 -0
  54. package/src/context/context-event-normalization.js +224 -0
  55. package/src/context/context-feedback-loop.js +216 -0
  56. package/src/context/context-freshness.js +21 -0
  57. package/src/context/context-graph-builder.js +71 -0
  58. package/src/context/context-graph.js +451 -0
  59. package/src/context/context-history-relevance.js +321 -0
  60. package/src/context/context-interception.js +145 -0
  61. package/src/context/context-invalidation-events.js +154 -0
  62. package/src/context/context-lifecycle.js +170 -0
  63. package/src/context/context-loop-prevention.js +347 -0
  64. package/src/context/context-manager.js +340 -0
  65. package/src/context/context-memory-layers.js +265 -0
  66. package/src/context/context-minimality.js +166 -0
  67. package/src/context/context-normalization.js +130 -0
  68. package/src/context/context-optimization-metrics.js +434 -0
  69. package/src/context/context-output-policy.js +135 -0
  70. package/src/context/context-pack-authority.js +157 -0
  71. package/src/context/context-promotion.js +172 -0
  72. package/src/context/context-query-contract.js +101 -0
  73. package/src/context/context-ranking.js +164 -0
  74. package/src/context/context-rate-distortion.js +261 -0
  75. package/src/context/context-recovery.js +209 -0
  76. package/src/context/context-retrieval-integration.js +60 -0
  77. package/src/context/context-retrieval.js +93 -0
  78. package/src/context/context-router.js +457 -0
  79. package/src/context/context-routing-evaluation.js +331 -0
  80. package/src/context/context-routing-observability.js +296 -0
  81. package/src/context/context-routing-shadow.js +237 -0
  82. package/src/context/context-scope.js +181 -0
  83. package/src/context/context-snapshot.js +226 -0
  84. package/src/context/context-source-registry.js +95 -0
  85. package/src/context/context-sufficiency-gate.js +120 -0
  86. package/src/context/context-sufficiency-oracle.js +167 -0
  87. package/src/context/context-telemetry.js +176 -0
  88. package/src/context/context.js +633 -0
  89. package/src/context/dependency-closure.js +246 -0
  90. package/src/context/page-fault-tracker.js +275 -0
  91. package/src/context/prompt-engine.js +74 -0
  92. package/src/context/runtime-context-graph.js +260 -0
  93. package/src/context/selection.js +2 -0
  94. package/src/context/session.js +2 -0
  95. package/src/context/sufficiency-contract.js +300 -0
  96. package/src/context/tokenizer.js +17 -0
  97. package/src/context-benchmark-router.mjs +125 -0
  98. package/src/evidence/evidence-engine.js +161 -0
  99. package/src/evidence/evidence-factory.js +3 -0
  100. package/src/evidence/evidence-freshness.js +463 -0
  101. package/src/evidence/evidence-gap.js +76 -0
  102. package/src/evidence/evidence-lineage.js +525 -0
  103. package/src/evidence/evidence.js +136 -0
  104. package/src/evidence/freshness-validation.js +187 -0
  105. package/src/evidence/lineage.js +4 -0
  106. package/src/execution/action-evaluation.js +179 -0
  107. package/src/execution/circuit-breaker.js +70 -0
  108. package/src/execution/claim-interception.js +63 -0
  109. package/src/execution/execution-engine.js +258 -0
  110. package/src/execution/execution-intent.js +69 -0
  111. package/src/execution/execution-state.js +89 -0
  112. package/src/execution/formatting.js +60 -0
  113. package/src/execution/l1-sanitize.js +16 -0
  114. package/src/execution/l2-resource-guards.js +19 -0
  115. package/src/execution/l3-invariant-checker.js +5 -0
  116. package/src/execution/l4-containment.js +6 -0
  117. package/src/execution/runtime-guards.js +106 -0
  118. package/src/execution/strategy-identity.js +162 -0
  119. package/src/execution/tool-interceptor.js +7 -0
  120. package/src/execution/tool-result-classifier.js +63 -0
  121. package/src/integration/canary-deploy.js +143 -0
  122. package/src/integration/collector.js +59 -0
  123. package/src/integration/error-recovery.js +6 -0
  124. package/src/integration/event-bus.js +66 -0
  125. package/src/integration/health-checker.js +57 -0
  126. package/src/integration/logger.js +39 -0
  127. package/src/integration/message-handler.js +611 -0
  128. package/src/integration/opencode-db.js +153 -0
  129. package/src/integration/orchestration.js +419 -0
  130. package/src/integration/part-provenance.js +65 -0
  131. package/src/integration/rate-limiter.js +55 -0
  132. package/src/integration/release-gate.js +336 -0
  133. package/src/integration/rollback-manager.js +164 -0
  134. package/src/integration/router-adapter.js +269 -0
  135. package/src/integration/telemetry.js +45 -0
  136. package/src/integration/update-checker.js +10 -0
  137. package/src/persistence/event-sourcing.js +75 -0
  138. package/src/persistence/memory.js +501 -0
  139. package/src/persistence/migration-runner.js +16 -0
  140. package/src/persistence/persistence-manager.js +39 -0
  141. package/src/persistence/transaction-log.js +157 -0
  142. package/src/policy/action-capabilities.js +116 -0
  143. package/src/policy/action-policy.js +4 -0
  144. package/src/policy/assessment-engine.js +104 -0
  145. package/src/policy/assumption-tracking.js +66 -0
  146. package/src/policy/budget-enforcer.js +7 -0
  147. package/src/policy/config-validator.js +102 -0
  148. package/src/policy/execution-assessment.js +142 -0
  149. package/src/policy/failure-classification.js +5 -0
  150. package/src/policy/feature-flags.js +65 -0
  151. package/src/policy/governance-enforcement.js +118 -0
  152. package/src/policy/locale.js +15 -0
  153. package/src/policy/multi-signer.js +151 -0
  154. package/src/policy/policy-state-machine.js +97 -0
  155. package/src/policy/ponytail-constraint.js +38 -0
  156. package/src/policy/project-inspector.js +93 -0
  157. package/src/policy/query-builder.js +14 -0
  158. package/src/policy/request-classifier.js +79 -0
  159. package/src/policy/risk-engine.js +266 -0
  160. package/src/policy/scope-enforcement.js +140 -0
  161. package/src/policy/secret-scanner.js +68 -0
  162. package/src/policy/skill-routing.js +104 -0
  163. package/src/policy/strategy-capabilities.js +211 -0
  164. package/src/policy/strategy.js +6 -0
  165. package/src/policy/uncertainty.js +117 -0
  166. package/src/shared/container.js +44 -0
  167. package/src/shared/decision-types.js +24 -0
  168. package/src/shared/simple-test.js +160 -0
  169. package/src/shared/wam-events.js +52 -0
  170. package/src/skills/engine.js +1705 -0
  171. package/src/skills/skill-registry.js +19 -0
  172. package/src/skills/skill-routing.js +340 -0
  173. package/src/state/lifecycle-manager.js +113 -0
  174. package/src/state/operational-state.js +198 -0
  175. package/src/state/projections.js +47 -0
  176. package/src/state/replay-engine.js +5 -0
  177. package/src/state/requirement-ownership.js +20 -0
  178. package/src/state/requirement-state.js +152 -0
  179. package/src/state/run-event-identity.js +172 -0
  180. package/src/state/state-engine.js +19 -0
  181. package/src/state/state-machine.js +119 -0
  182. package/src/state/state-schema.js +318 -0
  183. package/src/state/state-store.js +335 -0
  184. package/src/state/state-transition.js +84 -0
  185. package/src/state/task-dependencies.js +287 -0
  186. package/src/state/task-execution.js +217 -0
  187. package/src/state/task-lifecycle.js +59 -0
  188. package/src/state/task-runs.js +361 -0
  189. package/src/state/task-state-file.js +86 -0
  190. package/src/state/task-state.js +41 -0
  191. package/src/state/task-status.js +54 -0
  192. package/src/state/task-store.js +48 -0
  193. package/src/state/wam-state.js +148 -0
  194. package/src/verification/completion-gate-simple.js +8 -0
  195. package/src/verification/completion-gate.js +183 -0
  196. package/src/verification/contract.js +9 -0
  197. package/src/verification/contradiction-resolution.js +255 -0
  198. package/src/verification/diagnostic-engine.js +659 -0
  199. package/src/verification/false-completion-prevention.js +100 -0
  200. package/src/verification/hook-validator.js +6 -0
  201. package/src/verification/requirements.js +6 -0
  202. package/src/verification/verification-context.js +162 -0
  203. package/src/verification/verification-lifecycle.js +66 -0
  204. package/src/verification/verification-model.js +89 -0
  205. package/src/verification/verification-persistence.js +129 -0
  206. package/src/verification/verification-policy.js +127 -0
  207. package/src/verification/verification.js +415 -0
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Context Router Adapter — transforms router output to assembly format.
3
+ *
4
+ * Bridges Context Router (graph-based) to Context Assembly (N3 packing).
5
+ * Router decides WHAT; Adapter formats for Assembly; Assembly decides HOW.
6
+ *
7
+ * Flow:
8
+ * Task State → Context Graph → Router → Adapter → Assembly → N3
9
+ */
10
+
11
+ import { resolveContext, ADMISSION } from "../context/context-router.js";
12
+ import { buildContextGraph } from "../context/context-graph-builder.js";
13
+
14
+ /**
15
+ * @typedef {Object} RouterAdapterOptions
16
+ * @property {string} taskId
17
+ * @property {number} budget
18
+ * @property {string} root
19
+ * @property {Object} graph - Context Graph instance
20
+ */
21
+
22
+ /**
23
+ * @typedef {Object} AdapterResult
24
+ * @property {Array} capsules - Capsules in assembly format
25
+ * @property {string} sufficiency - "ok" | "insufficient"
26
+ * @property {string[]} missing
27
+ * @property {Object} contract
28
+ * @property {Object} routerResult - Raw router output
29
+ * @property {string} source - "router" | "fallback"
30
+ * @property {string} status - "READY" | "EMPTY" | "INSUFFICIENT" | "CONFLICTED" | "ERROR"
31
+ */
32
+
33
+ /**
34
+ * Transform a graph node to capsule format for assembly.
35
+ *
36
+ * @param {Object} node
37
+ * @returns {Object} Capsule-like object
38
+ */
39
+ function nodeToCapsule(node) {
40
+ return {
41
+ context_id: node.id,
42
+ level: node.type === "task" ? "N2" : node.type === "output" ? "N3" : "N3",
43
+ provenance: node.metadata?.provenance || "inferred",
44
+ purpose: node.metadata?.purpose || `${node.type}: ${node.id}`,
45
+ scope: node.type || "unknown",
46
+ content: node.content || "",
47
+ tokenEstimate: Math.ceil((node.content || "").length / 4) + 20,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Adapt router result to assembly-compatible format.
53
+ *
54
+ * @param {Object} routerResult - Output from resolveContext()
55
+ * @param {Object} options
56
+ * @returns {AdapterResult}
57
+ */
58
+ export function adaptRouterResult(routerResult, options = {}) {
59
+ const { budget = 4000 } = options;
60
+
61
+ if (!routerResult) {
62
+ return {
63
+ capsules: [],
64
+ sufficiency: "insufficient",
65
+ missing: ["router returned null"],
66
+ contract: { conditions: [] },
67
+ routerResult: null,
68
+ source: "fallback",
69
+ };
70
+ }
71
+
72
+ // Transform nodes to capsules
73
+ const capsules = routerResult.nodes.map(nodeToCapsule);
74
+
75
+ // Build contract from missing/omitted
76
+ const conditions = [];
77
+ for (const gap of routerResult.missing || []) {
78
+ conditions.push({
79
+ id: `SC-${conditions.length + 1}`,
80
+ type: "dependency",
81
+ description: gap.description || `Missing: ${gap.requiredBy}`,
82
+ status: "MISSING",
83
+ severity: "MANDATORY",
84
+ });
85
+ }
86
+
87
+ for (const omitted of routerResult.omitted || []) {
88
+ conditions.push({
89
+ id: `SC-${conditions.length + 1}`,
90
+ type: "admission",
91
+ description: `Omitted: ${omitted.id} (${omitted.reason})`,
92
+ status: omitted.admission === ADMISSION.MANDATORY ? "MISSING" : "OPTIONAL",
93
+ severity: omitted.admission,
94
+ });
95
+ }
96
+
97
+ const missing = conditions
98
+ .filter((c) => c.status === "MISSING" && c.severity === "MANDATORY")
99
+ .map((c) => c.description);
100
+
101
+ const sufficiency = routerResult.sufficient ? "ok" : "insufficient";
102
+
103
+ // Map router results to status
104
+ let status = "READY";
105
+ if (!routerResult) status = "ERROR";
106
+ else if (!routerResult.nodes.length) status = "EMPTY";
107
+ else if (!routerResult.sufficient) status = "INSUFFICIENT";
108
+ else if (routerResult.budgetOverflow) status = "INSUFFICIENT";
109
+ else if ((routerResult.omitted || []).some((o) => o.admission === ADMISSION.MANDATORY)) status = "INSUFFICIENT";
110
+
111
+ return {
112
+ capsules,
113
+ sufficiency,
114
+ missing,
115
+ contract: { conditions },
116
+ routerResult,
117
+ source: "router",
118
+ status
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Execute router and adapt result for assembly.
124
+ * This is the main integration point.
125
+ *
126
+ * Canonical path: Context Router is the sole authority for N3 selection.
127
+ * Silent fallbacks are forbidden; the only opt-in legacy selector is
128
+ * gated by env WAM_CONTEXT_SELECTOR=legacy OR options.useLegacySelector=true.
129
+ *
130
+ * @param {Object} graph - Context Graph (canonical ContextGraph instance)
131
+ * @param {RouterAdapterOptions} options
132
+ * @returns {AdapterResult}
133
+ */
134
+ export function routeAndAdapt(graph, options) {
135
+ const { taskId, budget = 4000, useLegacySelector = false } = options;
136
+ const legacySelectorAllowed =
137
+ process.env.WAM_CONTEXT_SELECTOR === "legacy" || useLegacySelector === true;
138
+
139
+ if (!graph || !taskId) {
140
+ return {
141
+ capsules: [],
142
+ sufficiency: "insufficient",
143
+ missing: ["no graph or taskId"],
144
+ contract: { conditions: [] },
145
+ routerResult: null,
146
+ source: "fallback",
147
+ };
148
+ }
149
+
150
+ try {
151
+ const routerResult = resolveContext(graph, { taskId, maxTokens: budget });
152
+ const adapted = adaptRouterResult(routerResult, { budget });
153
+ // C02: mark source as legacy only when caller explicitly opted in
154
+ // AND router returned empty/insufficient — never silent.
155
+ // Equivalent to the previous `adapted.capsules.length === 0` check, but
156
+ // evaluated before N2 injection so the opt-in path is unchanged.
157
+ const routerSelectedNothing = !routerResult.nodes || routerResult.nodes.length === 0;
158
+ if (legacySelectorAllowed && routerSelectedNothing) {
159
+ return { ...adapted, source: "legacy-allowed" };
160
+ }
161
+
162
+ // N2 obligation: the current task node is always assembled. The router
163
+ // selects supporting context (N3) only and never emits the task node
164
+ // itself, so the adapter injects it here (nodeToCapsule maps task -> N2).
165
+ const taskNode = typeof graph.getNode === "function" ? graph.getNode(taskId) : null;
166
+ if (taskNode && !adapted.capsules.some((c) => c.context_id === taskNode.id)) {
167
+ adapted.capsules = [nodeToCapsule(taskNode), ...adapted.capsules];
168
+ }
169
+ return adapted;
170
+ } catch (error) {
171
+ // Surface error: do NOT swap in legacy selector unless explicitly allowed.
172
+ if (!legacySelectorAllowed) {
173
+ return {
174
+ capsules: [],
175
+ sufficiency: "insufficient",
176
+ missing: [`router error: ${error.message}`],
177
+ contract: { conditions: [] },
178
+ routerResult: null,
179
+ source: "router-error",
180
+ };
181
+ }
182
+ return {
183
+ capsules: [],
184
+ sufficiency: "insufficient",
185
+ missing: [`router error: ${error.message}`],
186
+ contract: { conditions: [] },
187
+ routerResult: null,
188
+ source: "fallback",
189
+ };
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Build a minimal context graph from task state.
195
+ * Used when no pre-existing graph is available.
196
+ *
197
+ * @deprecated Callers should use buildRuntimeContextGraph() instead so all
198
+ * runtime layers (evidence, decisions, constraints, artifacts, observations,
199
+ * cognition, hypotheses, experiments) are represented. This helper is kept
200
+ * only for the legacy selector opt-in path (WAM_CONTEXT_SELECTOR=legacy).
201
+ *
202
+ * @param {Object} taskState
203
+ * @param {string} root
204
+ * @returns {Object} Minimal graph-like structure
205
+ */
206
+ export function buildGraphFromTaskState(taskState, root) {
207
+ const nodes = [];
208
+ const edges = [];
209
+
210
+ // Add task node
211
+ if (taskState?.taskId) {
212
+ nodes.push({
213
+ id: taskState.taskId,
214
+ type: "task",
215
+ content: taskState.lastAction || taskState.contract?.objective || "",
216
+ metadata: {
217
+ purpose: "Current task",
218
+ provenance: "user_decided",
219
+ },
220
+ });
221
+ }
222
+
223
+ // Add requirement nodes
224
+ for (const req of taskState?.requirements || []) {
225
+ nodes.push({
226
+ id: req.id,
227
+ type: "output",
228
+ content: req.title || req.description || "",
229
+ metadata: {
230
+ purpose: `Requirement: ${req.title}`,
231
+ provenance: "user_decided",
232
+ },
233
+ });
234
+ edges.push({
235
+ from: taskState.taskId,
236
+ to: req.id,
237
+ type: "depends_on",
238
+ });
239
+ }
240
+
241
+ // Build graph interface
242
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
243
+ return {
244
+ getNode: (id) => nodeMap.get(id) || null,
245
+ getEdgesFrom: (id) => edges.filter((e) => e.from === id),
246
+ getEdgesTo: (id) => edges.filter((e) => e.to === id),
247
+ getEdgesFromByType: (id, type) => edges.filter((e) => e.from === id && e.type === type),
248
+ getEdgesToByType: (id, type) => edges.filter((e) => e.to === id && e.type === type),
249
+ getUpstream: (id, depth) => {
250
+ const result = [];
251
+ const visited = new Set();
252
+ const queue = [id];
253
+ while (queue.length > 0 && result.length < 100) {
254
+ const nodeId = queue.shift();
255
+ if (visited.has(nodeId)) continue;
256
+ visited.add(nodeId);
257
+ const node = nodeMap.get(nodeId);
258
+ if (node) result.push(node);
259
+ for (const edge of edges.filter((e) => e.to === nodeId)) {
260
+ if (!visited.has(edge.from)) queue.push(edge.from);
261
+ }
262
+ }
263
+ return result;
264
+ },
265
+ getContradicting: () => [],
266
+ };
267
+ }
268
+
269
+ export { ADMISSION };
@@ -0,0 +1,45 @@
1
+ export function createTelemetry({ sink, onError, clock = () => Date.now() } = {}) {
2
+ const buffer = [];
3
+
4
+ function emit(event, data) {
5
+ if (typeof event !== 'string' || event.length === 0) {
6
+ if (onError) onError(new Error('Telemetry event name must be a non-empty string'));
7
+ return;
8
+ }
9
+ buffer.push({ event, data, timestamp: clock() });
10
+ }
11
+
12
+ async function flush() {
13
+ if (!sink || buffer.length === 0) {
14
+ const out = buffer.splice(0, buffer.length);
15
+ return { sent: out.length, dropped: 0 };
16
+ }
17
+ const drain = buffer.splice(0, buffer.length);
18
+ let sent = 0;
19
+ let dropped = 0;
20
+ try {
21
+ const result = await sink(drain);
22
+ sent = drain.length;
23
+ if (typeof result === 'number') sent = Math.min(sent, Math.max(0, result));
24
+ dropped = drain.length - sent;
25
+ } catch (err) {
26
+ dropped = drain.length;
27
+ if (onError) onError(err);
28
+ }
29
+ return { sent, dropped };
30
+ }
31
+
32
+ function size() {
33
+ return buffer.length;
34
+ }
35
+
36
+ function clear() {
37
+ buffer.length = 0;
38
+ }
39
+
40
+ function peek() {
41
+ return [...buffer];
42
+ }
43
+
44
+ return { emit, flush, size, clear, peek };
45
+ }
@@ -0,0 +1,10 @@
1
+ export async function checkUpdates(currentVersion, registryUrl) {
2
+ // Simulate network check
3
+ if (registryUrl === 'https://error.com') throw new Error('Registry unreachable');
4
+
5
+ const latest = '1.1.0';
6
+ return {
7
+ latest,
8
+ needsUpdate: currentVersion !== latest
9
+ };
10
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Event Sourcing — append-only event store with reduce.
3
+ *
4
+ * Stores immutable events keyed by stream. Reduces them into a derived
5
+ * state via a user-provided reducer. Production hardening (M5-51).
6
+ */
7
+
8
+ /**
9
+ * @typedef {Object} StoredEvent
10
+ * @property {number} seq
11
+ * @property {string} streamId
12
+ * @property {string} type
13
+ * @property {*} payload
14
+ * @property {number} timestamp
15
+ */
16
+
17
+ /**
18
+ * @typedef {Object} EventStore
19
+ * @property {(streamId: string, type: string, payload?: unknown) => StoredEvent} append
20
+ * @property {(streamId: string) => StoredEvent[]} query
21
+ * @property {(streamId: string, reducer: Function, initial: unknown) => unknown} reduce
22
+ * @property {() => number} totalEvents
23
+ * @property {() => string[]} streams
24
+ */
25
+
26
+ /**
27
+ * Create an event store.
28
+ * @returns {EventStore}
29
+ */
30
+ export function createEventStore() {
31
+ /** @type {Map<string, StoredEvent[]>} */
32
+ const byStream = new Map();
33
+ let seq = 0;
34
+
35
+ function append(streamId, type, payload) {
36
+ if (!streamId || typeof streamId !== "string") throw new TypeError("streamId required");
37
+ if (!type || typeof type !== "string") throw new TypeError("type required");
38
+ const event = {
39
+ seq: ++seq,
40
+ streamId,
41
+ type,
42
+ payload,
43
+ timestamp: Date.now(),
44
+ };
45
+ let list = byStream.get(streamId);
46
+ if (!list) {
47
+ list = [];
48
+ byStream.set(streamId, list);
49
+ }
50
+ list.push(event);
51
+ return event;
52
+ }
53
+
54
+ function query(streamId) {
55
+ const list = byStream.get(streamId);
56
+ return list ? [...list] : [];
57
+ }
58
+
59
+ function reduce(streamId, reducer, initial) {
60
+ const list = byStream.get(streamId) ?? [];
61
+ return list.reduce((state, ev) => reducer(state, ev), initial);
62
+ }
63
+
64
+ function totalEvents() {
65
+ let n = 0;
66
+ for (const list of byStream.values()) n += list.length;
67
+ return n;
68
+ }
69
+
70
+ function streams() {
71
+ return [...byStream.keys()];
72
+ }
73
+
74
+ return { append, query, reduce, totalEvents, streams };
75
+ }