@jamesaphoenix/tx-core 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +480 -0
- package/dist/db.d.ts +28 -14
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +102 -14
- package/dist/db.js.map +1 -1
- package/dist/errors.d.ts +178 -34
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +119 -26
- package/dist/errors.js.map +1 -1
- package/dist/id.d.ts +10 -0
- package/dist/id.d.ts.map +1 -1
- package/dist/id.js +17 -1
- package/dist/id.js.map +1 -1
- package/dist/index.d.ts +15 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +62 -8
- package/dist/index.js.map +1 -1
- package/dist/layer.d.ts +23 -14
- package/dist/layer.d.ts.map +1 -1
- package/dist/layer.js +75 -76
- package/dist/layer.js.map +1 -1
- package/dist/mappers/anchor.d.ts +15 -1
- package/dist/mappers/anchor.d.ts.map +1 -1
- package/dist/mappers/anchor.js +95 -28
- package/dist/mappers/anchor.js.map +1 -1
- package/dist/mappers/attempt.d.ts +3 -1
- package/dist/mappers/attempt.d.ts.map +1 -1
- package/dist/mappers/attempt.js +23 -9
- package/dist/mappers/attempt.js.map +1 -1
- package/dist/mappers/candidate.d.ts +3 -1
- package/dist/mappers/candidate.d.ts.map +1 -1
- package/dist/mappers/candidate.js +46 -16
- package/dist/mappers/candidate.js.map +1 -1
- package/dist/mappers/claim.d.ts +1 -1
- package/dist/mappers/claim.d.ts.map +1 -1
- package/dist/mappers/claim.js +11 -4
- package/dist/mappers/claim.js.map +1 -1
- package/dist/mappers/deduplication.d.ts +13 -1
- package/dist/mappers/deduplication.d.ts.map +1 -1
- package/dist/mappers/deduplication.js +22 -3
- package/dist/mappers/deduplication.js.map +1 -1
- package/dist/mappers/doc.d.ts +24 -0
- package/dist/mappers/doc.d.ts.map +1 -0
- package/dist/mappers/doc.js +161 -0
- package/dist/mappers/doc.js.map +1 -0
- package/dist/mappers/edge.d.ts +10 -1
- package/dist/mappers/edge.d.ts.map +1 -1
- package/dist/mappers/edge.js +74 -12
- package/dist/mappers/edge.js.map +1 -1
- package/dist/mappers/file-learning.d.ts.map +1 -1
- package/dist/mappers/file-learning.js +2 -1
- package/dist/mappers/file-learning.js.map +1 -1
- package/dist/mappers/index.d.ts +6 -7
- package/dist/mappers/index.d.ts.map +1 -1
- package/dist/mappers/index.js +10 -12
- package/dist/mappers/index.js.map +1 -1
- package/dist/mappers/learning.d.ts +9 -1
- package/dist/mappers/learning.d.ts.map +1 -1
- package/dist/mappers/learning.js +94 -14
- package/dist/mappers/learning.js.map +1 -1
- package/dist/mappers/orchestrator-state.d.ts +1 -1
- package/dist/mappers/orchestrator-state.d.ts.map +1 -1
- package/dist/mappers/orchestrator-state.js +31 -5
- package/dist/mappers/orchestrator-state.js.map +1 -1
- package/dist/mappers/parse-date.d.ts +11 -0
- package/dist/mappers/parse-date.d.ts.map +1 -0
- package/dist/mappers/parse-date.js +18 -0
- package/dist/mappers/parse-date.js.map +1 -0
- package/dist/mappers/run.d.ts +14 -4
- package/dist/mappers/run.d.ts.map +1 -1
- package/dist/mappers/run.js +49 -18
- package/dist/mappers/run.js.map +1 -1
- package/dist/mappers/task.d.ts +5 -1
- package/dist/mappers/task.d.ts.map +1 -1
- package/dist/mappers/task.js +66 -16
- package/dist/mappers/task.js.map +1 -1
- package/dist/mappers/tracked-project.d.ts +3 -1
- package/dist/mappers/tracked-project.d.ts.map +1 -1
- package/dist/mappers/tracked-project.js +23 -9
- package/dist/mappers/tracked-project.js.map +1 -1
- package/dist/mappers/worker.d.ts +1 -1
- package/dist/mappers/worker.d.ts.map +1 -1
- package/dist/mappers/worker.js +44 -6
- package/dist/mappers/worker.js.map +1 -1
- package/dist/repo/anchor-repo.d.ts +2 -2
- package/dist/repo/anchor-repo.d.ts.map +1 -1
- package/dist/repo/anchor-repo.js +46 -5
- package/dist/repo/anchor-repo.js.map +1 -1
- package/dist/repo/attempt-repo.d.ts +2 -2
- package/dist/repo/attempt-repo.d.ts.map +1 -1
- package/dist/repo/attempt-repo.js +16 -6
- package/dist/repo/attempt-repo.js.map +1 -1
- package/dist/repo/candidate-repo.d.ts.map +1 -1
- package/dist/repo/candidate-repo.js +22 -1
- package/dist/repo/candidate-repo.js.map +1 -1
- package/dist/repo/claim-repo.d.ts +46 -2
- package/dist/repo/claim-repo.d.ts.map +1 -1
- package/dist/repo/claim-repo.js +113 -6
- package/dist/repo/claim-repo.js.map +1 -1
- package/dist/repo/compaction-repo.d.ts +41 -0
- package/dist/repo/compaction-repo.d.ts.map +1 -0
- package/dist/repo/compaction-repo.js +84 -0
- package/dist/repo/compaction-repo.js.map +1 -0
- package/dist/repo/deduplication-repo.d.ts +9 -1
- package/dist/repo/deduplication-repo.d.ts.map +1 -1
- package/dist/repo/deduplication-repo.js +46 -9
- package/dist/repo/deduplication-repo.js.map +1 -1
- package/dist/repo/dep-repo.d.ts +27 -3
- package/dist/repo/dep-repo.d.ts.map +1 -1
- package/dist/repo/dep-repo.js +166 -39
- package/dist/repo/dep-repo.js.map +1 -1
- package/dist/repo/doc-repo.d.ts +59 -0
- package/dist/repo/doc-repo.d.ts.map +1 -0
- package/dist/repo/doc-repo.js +276 -0
- package/dist/repo/doc-repo.js.map +1 -0
- package/dist/repo/edge-repo.d.ts +1 -1
- package/dist/repo/edge-repo.d.ts.map +1 -1
- package/dist/repo/edge-repo.js +65 -34
- package/dist/repo/edge-repo.js.map +1 -1
- package/dist/repo/file-learning-repo.d.ts +3 -3
- package/dist/repo/file-learning-repo.d.ts.map +1 -1
- package/dist/repo/file-learning-repo.js +19 -8
- package/dist/repo/file-learning-repo.js.map +1 -1
- package/dist/repo/index.d.ts +4 -6
- package/dist/repo/index.d.ts.map +1 -1
- package/dist/repo/index.js +3 -5
- package/dist/repo/index.js.map +1 -1
- package/dist/repo/learning-repo.d.ts +10 -3
- package/dist/repo/learning-repo.d.ts.map +1 -1
- package/dist/repo/learning-repo.js +68 -11
- package/dist/repo/learning-repo.js.map +1 -1
- package/dist/repo/orchestrator-state-repo.d.ts.map +1 -1
- package/dist/repo/orchestrator-state-repo.js +8 -1
- package/dist/repo/orchestrator-state-repo.js.map +1 -1
- package/dist/repo/run-repo.d.ts +3 -3
- package/dist/repo/run-repo.d.ts.map +1 -1
- package/dist/repo/run-repo.js +40 -19
- package/dist/repo/run-repo.js.map +1 -1
- package/dist/repo/task-repo.d.ts +14 -3
- package/dist/repo/task-repo.d.ts.map +1 -1
- package/dist/repo/task-repo.js +194 -20
- package/dist/repo/task-repo.js.map +1 -1
- package/dist/repo/tracked-project-repo.d.ts.map +1 -1
- package/dist/repo/tracked-project-repo.js +15 -1
- package/dist/repo/tracked-project-repo.js.map +1 -1
- package/dist/repo/worker-repo.d.ts +3 -2
- package/dist/repo/worker-repo.d.ts.map +1 -1
- package/dist/repo/worker-repo.js +54 -8
- package/dist/repo/worker-repo.js.map +1 -1
- package/dist/schemas/sync.js +2 -2
- package/dist/schemas/sync.js.map +1 -1
- package/dist/schemas/worker.d.ts +1 -0
- package/dist/schemas/worker.d.ts.map +1 -1
- package/dist/schemas/worker.js +1 -0
- package/dist/schemas/worker.js.map +1 -1
- package/dist/services/agent-service.d.ts +57 -0
- package/dist/services/agent-service.d.ts.map +1 -0
- package/dist/services/agent-service.js +81 -0
- package/dist/services/agent-service.js.map +1 -0
- package/dist/services/anchor-service.js +1 -1
- package/dist/services/anchor-service.js.map +1 -1
- package/dist/services/anchor-verification.d.ts +8 -0
- package/dist/services/anchor-verification.d.ts.map +1 -1
- package/dist/services/anchor-verification.js +237 -37
- package/dist/services/anchor-verification.js.map +1 -1
- package/dist/services/ast-grep-service.d.ts.map +1 -1
- package/dist/services/ast-grep-service.js +93 -22
- package/dist/services/ast-grep-service.js.map +1 -1
- package/dist/services/attempt-service.d.ts.map +1 -1
- package/dist/services/attempt-service.js +1 -4
- package/dist/services/attempt-service.js.map +1 -1
- package/dist/services/auto-sync-service.d.ts +1 -1
- package/dist/services/auto-sync-service.d.ts.map +1 -1
- package/dist/services/auto-sync-service.js +18 -10
- package/dist/services/auto-sync-service.js.map +1 -1
- package/dist/services/claim-service.d.ts +8 -2
- package/dist/services/claim-service.d.ts.map +1 -1
- package/dist/services/claim-service.js +37 -23
- package/dist/services/claim-service.js.map +1 -1
- package/dist/services/compaction-service.d.ts +105 -0
- package/dist/services/compaction-service.d.ts.map +1 -0
- package/dist/services/compaction-service.js +369 -0
- package/dist/services/compaction-service.js.map +1 -0
- package/dist/services/cycle-scan-service.d.ts +32 -0
- package/dist/services/cycle-scan-service.d.ts.map +1 -0
- package/dist/services/cycle-scan-service.js +542 -0
- package/dist/services/cycle-scan-service.js.map +1 -0
- package/dist/services/daemon-service.d.ts +40 -2
- package/dist/services/daemon-service.d.ts.map +1 -1
- package/dist/services/daemon-service.js +199 -52
- package/dist/services/daemon-service.js.map +1 -1
- package/dist/services/deduplication-service.d.ts +8 -4
- package/dist/services/deduplication-service.d.ts.map +1 -1
- package/dist/services/deduplication-service.js +79 -25
- package/dist/services/deduplication-service.js.map +1 -1
- package/dist/services/dep-service.d.ts +2 -2
- package/dist/services/dep-service.d.ts.map +1 -1
- package/dist/services/dep-service.js +9 -5
- package/dist/services/dep-service.js.map +1 -1
- package/dist/services/diversifier-service.d.ts +2 -1
- package/dist/services/diversifier-service.d.ts.map +1 -1
- package/dist/services/diversifier-service.js +37 -43
- package/dist/services/diversifier-service.js.map +1 -1
- package/dist/services/doc-service.d.ts +49 -0
- package/dist/services/doc-service.d.ts.map +1 -0
- package/dist/services/doc-service.js +605 -0
- package/dist/services/doc-service.js.map +1 -0
- package/dist/services/edge-service.js +2 -2
- package/dist/services/edge-service.js.map +1 -1
- package/dist/services/embedding-service.d.ts +66 -2
- package/dist/services/embedding-service.d.ts.map +1 -1
- package/dist/services/embedding-service.js +138 -24
- package/dist/services/embedding-service.js.map +1 -1
- package/dist/services/file-learning-service.d.ts.map +1 -1
- package/dist/services/file-learning-service.js +8 -7
- package/dist/services/file-learning-service.js.map +1 -1
- package/dist/services/file-watcher-service.d.ts.map +1 -1
- package/dist/services/file-watcher-service.js +58 -11
- package/dist/services/file-watcher-service.js.map +1 -1
- package/dist/services/graph-expansion.d.ts +3 -0
- package/dist/services/graph-expansion.d.ts.map +1 -1
- package/dist/services/graph-expansion.js +28 -7
- package/dist/services/graph-expansion.js.map +1 -1
- package/dist/services/hierarchy-service.d.ts +1 -1
- package/dist/services/hierarchy-service.d.ts.map +1 -1
- package/dist/services/hierarchy-service.js +50 -32
- package/dist/services/hierarchy-service.js.map +1 -1
- package/dist/services/index.d.ts +13 -15
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +13 -15
- package/dist/services/index.js.map +1 -1
- package/dist/services/learning-service.d.ts +4 -4
- package/dist/services/learning-service.d.ts.map +1 -1
- package/dist/services/learning-service.js +75 -42
- package/dist/services/learning-service.js.map +1 -1
- package/dist/services/llm-service.d.ts +62 -0
- package/dist/services/llm-service.d.ts.map +1 -0
- package/dist/services/llm-service.js +172 -0
- package/dist/services/llm-service.js.map +1 -0
- package/dist/services/migration-service.d.ts +1 -1
- package/dist/services/migration-service.d.ts.map +1 -1
- package/dist/services/migration-service.js +18 -7
- package/dist/services/migration-service.js.map +1 -1
- package/dist/services/orchestrator-service.d.ts +4 -3
- package/dist/services/orchestrator-service.d.ts.map +1 -1
- package/dist/services/orchestrator-service.js +67 -29
- package/dist/services/orchestrator-service.js.map +1 -1
- package/dist/services/promotion-service.d.ts +1 -1
- package/dist/services/promotion-service.js +1 -1
- package/dist/services/promotion-service.js.map +1 -1
- package/dist/services/query-expansion-service.d.ts +30 -9
- package/dist/services/query-expansion-service.d.ts.map +1 -1
- package/dist/services/query-expansion-service.js +54 -63
- package/dist/services/query-expansion-service.js.map +1 -1
- package/dist/services/ready-service.d.ts +21 -1
- package/dist/services/ready-service.d.ts.map +1 -1
- package/dist/services/ready-service.js +44 -21
- package/dist/services/ready-service.js.map +1 -1
- package/dist/services/retriever-service.d.ts +10 -10
- package/dist/services/retriever-service.d.ts.map +1 -1
- package/dist/services/retriever-service.js +53 -161
- package/dist/services/retriever-service.js.map +1 -1
- package/dist/services/swarm-verification.d.ts +2 -2
- package/dist/services/swarm-verification.d.ts.map +1 -1
- package/dist/services/swarm-verification.js +12 -6
- package/dist/services/swarm-verification.js.map +1 -1
- package/dist/services/sync-service.d.ts +17 -4
- package/dist/services/sync-service.d.ts.map +1 -1
- package/dist/services/sync-service.js +378 -114
- package/dist/services/sync-service.js.map +1 -1
- package/dist/services/task-service.d.ts +6 -4
- package/dist/services/task-service.d.ts.map +1 -1
- package/dist/services/task-service.js +162 -33
- package/dist/services/task-service.js.map +1 -1
- package/dist/services/tracing-service.d.ts +55 -0
- package/dist/services/tracing-service.d.ts.map +1 -0
- package/dist/services/tracing-service.js +99 -0
- package/dist/services/tracing-service.js.map +1 -0
- package/dist/services/transcript-adapter.d.ts +99 -0
- package/dist/services/transcript-adapter.d.ts.map +1 -0
- package/dist/services/transcript-adapter.js +283 -0
- package/dist/services/transcript-adapter.js.map +1 -0
- package/dist/services/validation-service.d.ts +85 -0
- package/dist/services/validation-service.d.ts.map +1 -0
- package/dist/services/validation-service.js +289 -0
- package/dist/services/validation-service.js.map +1 -0
- package/dist/services/worker-process.d.ts +23 -4
- package/dist/services/worker-process.d.ts.map +1 -1
- package/dist/services/worker-process.js +159 -70
- package/dist/services/worker-process.js.map +1 -1
- package/dist/services/worker-service.d.ts.map +1 -1
- package/dist/services/worker-service.js +7 -12
- package/dist/services/worker-service.js.map +1 -1
- package/dist/sync/claude-task-writer.d.ts +49 -0
- package/dist/sync/claude-task-writer.d.ts.map +1 -0
- package/dist/sync/claude-task-writer.js +135 -0
- package/dist/sync/claude-task-writer.js.map +1 -0
- package/dist/utils/doc-hash.d.ts +10 -0
- package/dist/utils/doc-hash.d.ts.map +1 -0
- package/dist/utils/doc-hash.js +14 -0
- package/dist/utils/doc-hash.js.map +1 -0
- package/dist/utils/doc-renderer.d.ts +44 -0
- package/dist/utils/doc-renderer.d.ts.map +1 -0
- package/dist/utils/doc-renderer.js +202 -0
- package/dist/utils/doc-renderer.js.map +1 -0
- package/dist/utils/math.d.ts +5 -1
- package/dist/utils/math.d.ts.map +1 -1
- package/dist/utils/math.js +12 -4
- package/dist/utils/math.js.map +1 -1
- package/dist/utils/sql.d.ts +9 -0
- package/dist/utils/sql.d.ts.map +1 -0
- package/dist/utils/sql.js +9 -0
- package/dist/utils/sql.js.map +1 -0
- package/dist/utils/toml-config.d.ts +22 -0
- package/dist/utils/toml-config.d.ts.map +1 -0
- package/dist/utils/toml-config.js +75 -0
- package/dist/utils/toml-config.js.map +1 -0
- package/dist/worker/hooks.d.ts +102 -0
- package/dist/worker/hooks.d.ts.map +1 -0
- package/dist/worker/hooks.js +11 -0
- package/dist/worker/hooks.js.map +1 -0
- package/dist/worker/index.d.ts +9 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +8 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/run-worker.d.ts +33 -0
- package/dist/worker/run-worker.d.ts.map +1 -0
- package/dist/worker/run-worker.js +265 -0
- package/dist/worker/run-worker.js.map +1 -0
- package/package.json +14 -12
package/README.md
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
# tx
|
|
2
|
+
|
|
3
|
+
**TanStack for AI agents.** Primitives, not frameworks.
|
|
4
|
+
|
|
5
|
+
Headless infrastructure for memory, tasks, and orchestration.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @jamesaphoenix/tx
|
|
9
|
+
tx init
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## The Problem
|
|
15
|
+
|
|
16
|
+
Your agents lose context between sessions. Tasks collide when multiple agents work in parallel. Learnings vanish into conversation history. You're rebuilding the same infrastructure every project.
|
|
17
|
+
|
|
18
|
+
## The Solution
|
|
19
|
+
|
|
20
|
+
Composable primitives that handle the hard parts. You keep control of the orchestration.
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
┌─────────────────────────────────────────────────────────┐
|
|
24
|
+
│ Your Orchestration (your code, your rules) │
|
|
25
|
+
├─────────────────────────────────────────────────────────┤
|
|
26
|
+
│ tx primitives │
|
|
27
|
+
│ │
|
|
28
|
+
│ tx ready tx done tx context tx learn │
|
|
29
|
+
│ tx claim tx block tx handoff tx sync │
|
|
30
|
+
│ │
|
|
31
|
+
└─────────────────────────────────────────────────────────┘
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Primitives
|
|
37
|
+
|
|
38
|
+
### Memory
|
|
39
|
+
|
|
40
|
+
Learnings that persist and surface when relevant.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Store knowledge (with optional file path)
|
|
44
|
+
tx learning:add "Use bcrypt for passwords, not SHA256" --file src/auth/hash.ts
|
|
45
|
+
tx learning:add "Redis cache invalidation has race conditions"
|
|
46
|
+
|
|
47
|
+
# Retrieve via search or task context
|
|
48
|
+
tx learning:search "authentication"
|
|
49
|
+
tx context tx-abc123 # Get relevant learnings for a task
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Learnings can be tagged with file paths for organization. Hybrid search (BM25 + vector) finds relevant knowledge.
|
|
53
|
+
|
|
54
|
+
### Tasks
|
|
55
|
+
|
|
56
|
+
Dependency-aware task management. Agents only see work they can actually do.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Create with dependencies
|
|
60
|
+
tx add "Implement auth service" --score 800
|
|
61
|
+
tx add "Design auth schema" --score 900
|
|
62
|
+
tx block tx-impl tx-schema # impl waits for schema
|
|
63
|
+
|
|
64
|
+
# Work on what's ready
|
|
65
|
+
tx ready # Only unblocked tasks
|
|
66
|
+
tx done tx-schema # Completes → unblocks dependents
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Full hierarchy support. Epics contain milestones contain tasks contain subtasks.
|
|
70
|
+
|
|
71
|
+
### Coordination
|
|
72
|
+
|
|
73
|
+
Primitives for multi-agent workflows without prescribing the pattern.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
tx claim tx-abc123 # Prevent collisions
|
|
77
|
+
tx checkpoint tx-abc123 \
|
|
78
|
+
--note "API done, UI next" # Save progress
|
|
79
|
+
tx handoff tx-abc123 \
|
|
80
|
+
--to reviewer # Transfer with context
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Your Loop, Your Rules
|
|
86
|
+
|
|
87
|
+
We ship **example loops**, not **the loop**:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Simple: one agent, one task
|
|
91
|
+
while task=$(tx ready --limit 1 --json | jq -r '.[0].id'); do
|
|
92
|
+
claude "Work on task $task, then run: tx done $task"
|
|
93
|
+
done
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Parallel: N agents pulling from queue
|
|
98
|
+
for i in {1..5}; do
|
|
99
|
+
(while task=$(tx claim --next); do
|
|
100
|
+
claude "Complete $task" && tx done $task
|
|
101
|
+
done) &
|
|
102
|
+
done
|
|
103
|
+
wait
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Human-in-loop: agent proposes, human approves
|
|
108
|
+
task=$(tx ready --limit 1)
|
|
109
|
+
claude "Plan implementation for $task" > plan.md
|
|
110
|
+
read -p "Approve? [y/n] " && claude "Execute plan.md"
|
|
111
|
+
tx done $task
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**The flow is yours.** Serial, parallel, swarm, human-in-loop. Your call.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Why tx?
|
|
119
|
+
|
|
120
|
+
| | Native Tasks | CLAUDE.md | tx |
|
|
121
|
+
|---|---|---|---|
|
|
122
|
+
| **Persistence** | Session-scoped | File grows forever | Git-native, branch-aware |
|
|
123
|
+
| **Multi-agent** | Collisions | Manual coordination | Claim, block, handoff |
|
|
124
|
+
| **Knowledge** | Lost each session | Static dump | Graph RAG, contextual retrieval |
|
|
125
|
+
| **Orchestration** | None | None | Primitives for any pattern |
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Design Principles
|
|
130
|
+
|
|
131
|
+
- **No opinions on orchestration.** Serial, parallel, swarm, human-in-loop. Your call.
|
|
132
|
+
- **Powerful defaults.** `tx ready` just works. So does dependency resolution.
|
|
133
|
+
- **Escape hatches everywhere.** Raw SQL access, JSONL export, custom scoring.
|
|
134
|
+
- **Framework agnostic.** CLI, MCP, REST API, TypeScript SDK. Use what fits.
|
|
135
|
+
- **Local-first.** SQLite + git. No server required. Works offline.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Non-Goals
|
|
140
|
+
|
|
141
|
+
- **Not an agent framework.** You bring your own orchestration.
|
|
142
|
+
- **Not a hosted memory product.** Local-first, your data stays yours.
|
|
143
|
+
- **Not a prompt library.** Primitives, not templates.
|
|
144
|
+
- **Not a replacement for your issue tracker.** (Unless you want it to be.)
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Three Systems
|
|
149
|
+
|
|
150
|
+
### 1. Knowledge System
|
|
151
|
+
|
|
152
|
+
**Working today:**
|
|
153
|
+
- Learnings stored with file path tags
|
|
154
|
+
- Basic hybrid search (BM25 + vector)
|
|
155
|
+
- Retrieval by task ID via `tx context`
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
tx learning:add "Use bcrypt for passwords" --file src/auth/hash.ts
|
|
159
|
+
tx learning:search "authentication"
|
|
160
|
+
tx context tx-abc123 # Get learnings relevant to a task
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Research in progress:**
|
|
164
|
+
- Symbol anchoring (AST-based code references, not just file paths)
|
|
165
|
+
- Knowledge graph expansion (automatic relationship discovery)
|
|
166
|
+
- Auto-invalidation when code changes
|
|
167
|
+
|
|
168
|
+
### 2. Task System
|
|
169
|
+
|
|
170
|
+
**Working today:**
|
|
171
|
+
- N-level hierarchy (epics → tasks → subtasks)
|
|
172
|
+
- Explicit dependencies with cycle detection
|
|
173
|
+
- Priority scoring
|
|
174
|
+
- Claim/release with lease expiry
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
Epic: "User Authentication"
|
|
178
|
+
├── Task: "Design schema" ✓ done
|
|
179
|
+
├── Task: "Implement service" ● ready (unblocked)
|
|
180
|
+
│ └── blocks: "Write tests", "Add endpoints"
|
|
181
|
+
└── Task: "Write tests" ○ blocked
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Research in progress:**
|
|
185
|
+
- LLM-based reprioritization
|
|
186
|
+
- Automatic task decomposition
|
|
187
|
+
|
|
188
|
+
### 3. Worker System
|
|
189
|
+
|
|
190
|
+
**Working today:**
|
|
191
|
+
- `runWorker()` with execute/captureIO hooks
|
|
192
|
+
- Lease-based claims (prevents collisions)
|
|
193
|
+
- Automatic lease renewal
|
|
194
|
+
- Coordinator reconciliation (dead worker recovery)
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
runWorker({
|
|
198
|
+
execute: async (task, ctx) => {
|
|
199
|
+
await ctx.renewLease() // For long tasks
|
|
200
|
+
return { success: true }
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Research in progress:**
|
|
206
|
+
- Daemon watching `~/.claude/projects/**/*.jsonl`
|
|
207
|
+
- Automatic learning extraction from sessions
|
|
208
|
+
- Confidence scoring for auto-promotion
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Worker Orchestration (TypeScript SDK)
|
|
213
|
+
|
|
214
|
+
For programmatic control, the TypeScript SDK provides `runWorker()` — a headless worker that executes tasks using your hooks.
|
|
215
|
+
|
|
216
|
+
### Two Hooks. That's It.
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { runWorker } from "@jamesaphoenix/tx-core"
|
|
220
|
+
|
|
221
|
+
runWorker({
|
|
222
|
+
name: "my-worker",
|
|
223
|
+
execute: async (task, ctx) => {
|
|
224
|
+
// YOUR LOGIC HERE
|
|
225
|
+
console.log(`Working on: ${task.title}`)
|
|
226
|
+
|
|
227
|
+
// Use ctx.renewLease() for long tasks
|
|
228
|
+
await ctx.renewLease()
|
|
229
|
+
|
|
230
|
+
// Return success or failure
|
|
231
|
+
return { success: true, output: "Done!" }
|
|
232
|
+
},
|
|
233
|
+
captureIO: (runId, task) => ({
|
|
234
|
+
transcriptPath: `.tx/runs/${runId}.jsonl`,
|
|
235
|
+
stderrPath: `.tx/runs/${runId}.stderr`
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
| Hook | Required | Purpose |
|
|
241
|
+
|------|----------|---------|
|
|
242
|
+
| `execute` | Yes | Your task execution logic |
|
|
243
|
+
| `captureIO` | No | Paths for transcript/stderr/stdout capture |
|
|
244
|
+
|
|
245
|
+
### WorkerContext
|
|
246
|
+
|
|
247
|
+
The `ctx` object provides tx primitives:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
interface WorkerContext {
|
|
251
|
+
workerId: string // This worker's ID
|
|
252
|
+
runId: string // Unique ID for this execution
|
|
253
|
+
renewLease: () => Promise<void> // Extend lease for long tasks
|
|
254
|
+
log: (message: string) => void // Log with worker prefix
|
|
255
|
+
state: Record<string, unknown> // Mutable state within task
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Custom Context
|
|
260
|
+
|
|
261
|
+
Pass your own primitives via generics:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
interface MyContext {
|
|
265
|
+
llm: AnthropicClient
|
|
266
|
+
db: Database
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
runWorker<MyContext>({
|
|
270
|
+
context: {
|
|
271
|
+
llm: new Anthropic(),
|
|
272
|
+
db: myDatabase
|
|
273
|
+
},
|
|
274
|
+
execute: async (task, ctx) => {
|
|
275
|
+
// ctx.llm and ctx.db available here
|
|
276
|
+
const response = await ctx.llm.messages.create(...)
|
|
277
|
+
return { success: true }
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Claims and Leases
|
|
283
|
+
|
|
284
|
+
Workers use a lease-based system to prevent collisions:
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
Worker A claims task → Lease expires in 30 min → Renew or lose it
|
|
288
|
+
Worker B tries to claim same task → Rejected (already claimed)
|
|
289
|
+
Worker A dies → Lease expires → Coordinator reclaims task
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Key points:
|
|
293
|
+
- **Claims are atomic** — only one worker can claim a task
|
|
294
|
+
- **Leases expire** — prevents stuck tasks from dead workers
|
|
295
|
+
- **Auto-renewal** — `runWorker()` renews automatically; use `ctx.renewLease()` for extra-long tasks
|
|
296
|
+
- **Coordinator reconciles** — dead workers detected, orphaned tasks recovered
|
|
297
|
+
|
|
298
|
+
### Example Worker Loops
|
|
299
|
+
|
|
300
|
+
#### Basic: One Worker
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
import { Effect, Layer } from "effect"
|
|
304
|
+
import { runWorker, makeMinimalLayer, SqliteClientLive } from "@jamesaphoenix/tx-core"
|
|
305
|
+
|
|
306
|
+
const layer = makeMinimalLayer.pipe(
|
|
307
|
+
Layer.provide(SqliteClientLive(".tx/tasks.db"))
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
Effect.runPromise(
|
|
311
|
+
runWorker({
|
|
312
|
+
execute: async (task, ctx) => {
|
|
313
|
+
ctx.log(`Processing: ${task.title}`)
|
|
314
|
+
// ... your logic
|
|
315
|
+
return { success: true }
|
|
316
|
+
}
|
|
317
|
+
}).pipe(Effect.provide(layer))
|
|
318
|
+
)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
#### With Claude Code
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import { spawn } from "child_process"
|
|
325
|
+
|
|
326
|
+
runWorker({
|
|
327
|
+
execute: async (task, ctx) => {
|
|
328
|
+
return new Promise((resolve) => {
|
|
329
|
+
const proc = spawn("claude", [
|
|
330
|
+
"--print",
|
|
331
|
+
`Work on task ${task.id}: ${task.title}`
|
|
332
|
+
])
|
|
333
|
+
|
|
334
|
+
proc.on("close", (code) => {
|
|
335
|
+
resolve({
|
|
336
|
+
success: code === 0,
|
|
337
|
+
error: code !== 0 ? `Exit code ${code}` : undefined
|
|
338
|
+
})
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
}
|
|
342
|
+
})
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
#### Parallel Workers
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
// Start N workers (each in its own process or fiber)
|
|
349
|
+
for (let i = 0; i < 5; i++) {
|
|
350
|
+
Effect.fork(
|
|
351
|
+
runWorker({
|
|
352
|
+
name: `worker-${i}`,
|
|
353
|
+
execute: async (task, ctx) => {
|
|
354
|
+
// Workers automatically coordinate via claims
|
|
355
|
+
return { success: true }
|
|
356
|
+
}
|
|
357
|
+
})
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
#### Long-Running Tasks
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
runWorker({
|
|
366
|
+
execute: async (task, ctx) => {
|
|
367
|
+
for (let step = 0; step < 100; step++) {
|
|
368
|
+
// Periodic lease renewal for tasks > 30 min
|
|
369
|
+
if (step % 10 === 0) {
|
|
370
|
+
await ctx.renewLease()
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Track progress in mutable state
|
|
374
|
+
ctx.state.progress = step
|
|
375
|
+
|
|
376
|
+
await doExpensiveWork(step)
|
|
377
|
+
}
|
|
378
|
+
return { success: true }
|
|
379
|
+
}
|
|
380
|
+
})
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Interfaces
|
|
386
|
+
|
|
387
|
+
| Interface | Use Case |
|
|
388
|
+
|-----------|----------|
|
|
389
|
+
| **CLI** | Scripts, terminal workflows, RALPH loops |
|
|
390
|
+
| **MCP Server** | Claude Code integration (16 tools) |
|
|
391
|
+
| **REST API** | Custom dashboards, external integrations |
|
|
392
|
+
| **TypeScript SDK** | Programmatic access from your agents |
|
|
393
|
+
| **Dashboard** | Visual monitoring and management |
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Quick Reference
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
# Tasks
|
|
401
|
+
tx add <title> # Create
|
|
402
|
+
tx ready # List unblocked
|
|
403
|
+
tx done <id> # Complete
|
|
404
|
+
tx block <id> <blocker> # Add dependency
|
|
405
|
+
tx tree <id> # Show hierarchy
|
|
406
|
+
|
|
407
|
+
# Memory
|
|
408
|
+
tx learning:add <content> # Store
|
|
409
|
+
tx learning:search <query> # Find
|
|
410
|
+
tx context <task-id> # Contextual retrieval
|
|
411
|
+
|
|
412
|
+
# Coordination
|
|
413
|
+
tx claim <id> # Prevent collisions
|
|
414
|
+
tx handoff <id> --to <agent>
|
|
415
|
+
tx checkpoint <id> --note "..."
|
|
416
|
+
|
|
417
|
+
# Sync
|
|
418
|
+
tx sync export # SQLite → JSONL (git-friendly)
|
|
419
|
+
tx sync import # JSONL → SQLite
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Storage
|
|
425
|
+
|
|
426
|
+
```
|
|
427
|
+
.tx/
|
|
428
|
+
├── tasks.db # SQLite (gitignored)
|
|
429
|
+
├── tasks.jsonl # Git-tracked
|
|
430
|
+
├── learnings.jsonl # Git-tracked
|
|
431
|
+
└── runs.jsonl # Git-tracked
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Local SQLite for speed. JSONL for git sync. Branch your knowledge with your code.
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## Status
|
|
439
|
+
|
|
440
|
+
**Shipping now (concrete, tested):**
|
|
441
|
+
- Core task primitives: add, ready, done, block, claim, handoff
|
|
442
|
+
- Dependency management with cycle detection
|
|
443
|
+
- Worker orchestration via `runWorker()` with claims/leases
|
|
444
|
+
- Learnings with file path tagging
|
|
445
|
+
- Hybrid search (BM25 + vector)
|
|
446
|
+
- CLI (20+ commands), MCP server (16 tools)
|
|
447
|
+
- 389+ tests
|
|
448
|
+
|
|
449
|
+
**Research in progress (not yet stable):**
|
|
450
|
+
- Symbol anchoring (AST-based code references)
|
|
451
|
+
- Knowledge graph expansion
|
|
452
|
+
- Auto-invalidation when code changes
|
|
453
|
+
- Daemon-based learning extraction
|
|
454
|
+
- LLM reprioritization
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## Documentation
|
|
459
|
+
|
|
460
|
+
- **[CLAUDE.md](CLAUDE.md)**: Doctrine and quick reference
|
|
461
|
+
- **[docs/](docs/)**: Full documentation (17 PRDs, 17 Design Docs)
|
|
462
|
+
|
|
463
|
+
### Docs Site
|
|
464
|
+
|
|
465
|
+
Run the documentation site locally:
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
cd apps/docs
|
|
469
|
+
npm run dev # Development server at http://localhost:3000
|
|
470
|
+
npm run build # Production build
|
|
471
|
+
npm run start # Serve production build
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
The docs site is built with [Fumadocs](https://fumadocs.vercel.app/) and Next.js, featuring full-text search, syntax highlighting, and automatic navigation from the markdown files in `docs/`.
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## License
|
|
479
|
+
|
|
480
|
+
MIT
|
package/dist/db.d.ts
CHANGED
|
@@ -1,42 +1,56 @@
|
|
|
1
1
|
import { Context, Effect, Layer } from "effect";
|
|
2
|
-
import
|
|
2
|
+
import { DatabaseError } from "./errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Result type for SQL statement run operations.
|
|
5
|
+
* Compatible with bun:sqlite's return type.
|
|
6
|
+
*/
|
|
7
|
+
export interface SqliteRunResult {
|
|
8
|
+
lastInsertRowid: number | bigint;
|
|
9
|
+
changes: number;
|
|
10
|
+
}
|
|
3
11
|
/**
|
|
4
12
|
* Minimal interface for SQL statement objects.
|
|
5
|
-
* Describes what we need from
|
|
13
|
+
* Describes what we need from bun:sqlite's Statement type.
|
|
6
14
|
*/
|
|
7
15
|
export interface SqliteStatement<TResult = unknown> {
|
|
8
|
-
run(...params: unknown[]):
|
|
9
|
-
get(...params: unknown[]): TResult |
|
|
16
|
+
run(...params: unknown[]): SqliteRunResult;
|
|
17
|
+
get(...params: unknown[]): TResult | null;
|
|
10
18
|
all(...params: unknown[]): TResult[];
|
|
11
19
|
}
|
|
12
20
|
/**
|
|
13
21
|
* Minimal interface for the SQLite database.
|
|
14
|
-
* Describes what we need from
|
|
22
|
+
* Describes what we need from bun:sqlite's Database type.
|
|
15
23
|
* This allows declaration generation without exposing private types.
|
|
16
24
|
*/
|
|
17
25
|
export interface SqliteDatabase {
|
|
18
26
|
prepare<T = unknown>(sql: string): SqliteStatement<T>;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
simple?: boolean;
|
|
22
|
-
}): unknown;
|
|
27
|
+
run(sql: string, ...params: unknown[]): SqliteRunResult;
|
|
28
|
+
exec(sql: string): void;
|
|
23
29
|
close(): void;
|
|
30
|
+
/** Wrap a function in a transaction. Commits on success, rolls back on throw. */
|
|
31
|
+
transaction<T>(fn: () => T): () => T;
|
|
24
32
|
}
|
|
25
33
|
declare const SqliteClient_base: Context.TagClass<SqliteClient, "SqliteClient", SqliteDatabase>;
|
|
26
|
-
/** The SqliteClient service provides a
|
|
34
|
+
/** The SqliteClient service provides a bun:sqlite Database instance. */
|
|
27
35
|
export declare class SqliteClient extends SqliteClient_base {
|
|
28
36
|
}
|
|
29
37
|
/**
|
|
30
38
|
* Get the current schema version from a database instance.
|
|
31
39
|
* Exported for use in tests and CLI commands.
|
|
40
|
+
*
|
|
41
|
+
* Accepts SqliteDatabase interface to work with both bun:sqlite and better-sqlite3.
|
|
32
42
|
*/
|
|
33
|
-
export declare const getSchemaVersion: (db:
|
|
43
|
+
export declare const getSchemaVersion: (db: SqliteDatabase) => number;
|
|
34
44
|
/**
|
|
35
45
|
* Apply all pending migrations to the database.
|
|
36
46
|
* Uses the centralized MIGRATIONS from migration-service.ts.
|
|
47
|
+
* Each migration is wrapped in BEGIN IMMEDIATE/COMMIT/ROLLBACK
|
|
48
|
+
* to ensure atomicity and prevent concurrent application.
|
|
49
|
+
*
|
|
50
|
+
* Accepts SqliteDatabase interface to work with both bun:sqlite and better-sqlite3.
|
|
37
51
|
*/
|
|
38
|
-
export declare const applyMigrations: (db:
|
|
39
|
-
export declare const makeSqliteClient: (dbPath: string) => Effect.Effect<
|
|
40
|
-
export declare const SqliteClientLive: (dbPath: string) => Layer.Layer<SqliteClient,
|
|
52
|
+
export declare const applyMigrations: (db: SqliteDatabase) => void;
|
|
53
|
+
export declare const makeSqliteClient: (dbPath: string) => Effect.Effect<SqliteDatabase, DatabaseError>;
|
|
54
|
+
export declare const SqliteClientLive: (dbPath: string) => Layer.Layer<SqliteClient, DatabaseError, never>;
|
|
41
55
|
export {};
|
|
42
56
|
//# sourceMappingURL=db.d.ts.map
|
package/dist/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAI/C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAM3C;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,GAAG,MAAM,CAAA;IAChC,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe,CAAC,OAAO,GAAG,OAAO;IAChD,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,eAAe,CAAA;IAC1C,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,GAAG,IAAI,CAAA;IACzC,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAA;CACrC;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,CAAA;IACrD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,eAAe,CAAA;IACvD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,KAAK,IAAI,IAAI,CAAA;IACb,iFAAiF;IACjF,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,CAAA;CACrC;;AAED,wEAAwE;AACxE,qBAAa,YAAa,SAAQ,iBAG/B;CAAG;AAEN;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,IAAI,cAAc,KAAG,MAOrD,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,IAAI,cAAc,KAAG,IAepD,CAAA;AA6CD,eAAO,MAAM,gBAAgB,GAAI,QAAQ,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,aAAa,CAoCzF,CAAA;AAEJ,eAAO,MAAM,gBAAgB,GAAI,QAAQ,MAAM,oDAO5C,CAAA"}
|
package/dist/db.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { Context, Effect, Layer } from "effect";
|
|
2
|
-
import Database from "better-sqlite3";
|
|
3
2
|
import { mkdirSync, existsSync } from "fs";
|
|
4
3
|
import { dirname } from "path";
|
|
5
4
|
import { MIGRATIONS } from "./services/migration-service.js";
|
|
6
|
-
|
|
5
|
+
import { DatabaseError } from "./errors.js";
|
|
6
|
+
/** The SqliteClient service provides a bun:sqlite Database instance. */
|
|
7
7
|
export class SqliteClient extends Context.Tag("SqliteClient")() {
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* Get the current schema version from a database instance.
|
|
11
11
|
* Exported for use in tests and CLI commands.
|
|
12
|
+
*
|
|
13
|
+
* Accepts SqliteDatabase interface to work with both bun:sqlite and better-sqlite3.
|
|
12
14
|
*/
|
|
13
15
|
export const getSchemaVersion = (db) => {
|
|
14
16
|
try {
|
|
@@ -22,25 +24,111 @@ export const getSchemaVersion = (db) => {
|
|
|
22
24
|
/**
|
|
23
25
|
* Apply all pending migrations to the database.
|
|
24
26
|
* Uses the centralized MIGRATIONS from migration-service.ts.
|
|
27
|
+
* Each migration is wrapped in BEGIN IMMEDIATE/COMMIT/ROLLBACK
|
|
28
|
+
* to ensure atomicity and prevent concurrent application.
|
|
29
|
+
*
|
|
30
|
+
* Accepts SqliteDatabase interface to work with both bun:sqlite and better-sqlite3.
|
|
25
31
|
*/
|
|
26
32
|
export const applyMigrations = (db) => {
|
|
27
33
|
const currentVersion = getSchemaVersion(db);
|
|
28
34
|
for (const migration of MIGRATIONS) {
|
|
29
35
|
if (migration.version > currentVersion) {
|
|
30
|
-
db.exec(
|
|
36
|
+
db.exec("BEGIN IMMEDIATE");
|
|
37
|
+
try {
|
|
38
|
+
db.exec(migration.sql);
|
|
39
|
+
db.exec("COMMIT");
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
db.exec("ROLLBACK");
|
|
43
|
+
throw e;
|
|
44
|
+
}
|
|
31
45
|
}
|
|
32
46
|
}
|
|
33
47
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Wrap a better-sqlite3 Database as SqliteDatabase.
|
|
50
|
+
* Used by makeSqliteClient when running under Vitest.
|
|
51
|
+
* Inlined here to avoid circular dependency on test-utils.
|
|
52
|
+
*/
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
54
|
+
function wrapBetterSqlite3ForClient(raw) {
|
|
55
|
+
return {
|
|
56
|
+
prepare(sql) {
|
|
57
|
+
const stmt = raw.prepare(sql);
|
|
58
|
+
return {
|
|
59
|
+
run(...params) {
|
|
60
|
+
const r = stmt.run(...params);
|
|
61
|
+
return { lastInsertRowid: r.lastInsertRowid ?? 0, changes: r.changes ?? 0 };
|
|
62
|
+
},
|
|
63
|
+
get(...params) { return stmt.get(...params) ?? null; },
|
|
64
|
+
all(...params) { return stmt.all(...params); }
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
run(sql, ...params) {
|
|
68
|
+
const trimmed = sql.trim();
|
|
69
|
+
if (trimmed.toUpperCase().startsWith("PRAGMA")) {
|
|
70
|
+
raw.pragma(trimmed.slice(6).trim());
|
|
71
|
+
return { lastInsertRowid: 0, changes: 0 };
|
|
72
|
+
}
|
|
73
|
+
const r = raw.prepare(sql).run(...params);
|
|
74
|
+
return { lastInsertRowid: r.lastInsertRowid ?? 0, changes: r.changes ?? 0 };
|
|
75
|
+
},
|
|
76
|
+
exec(sql) { raw.exec(sql); },
|
|
77
|
+
close() { raw.close(); },
|
|
78
|
+
transaction(fn) {
|
|
79
|
+
if (typeof raw.transaction === "function") {
|
|
80
|
+
return raw.transaction(fn);
|
|
81
|
+
}
|
|
82
|
+
return () => {
|
|
83
|
+
raw.exec("BEGIN");
|
|
84
|
+
try {
|
|
85
|
+
const r = fn();
|
|
86
|
+
raw.exec("COMMIT");
|
|
87
|
+
return r;
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
raw.exec("ROLLBACK");
|
|
91
|
+
throw e;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export const makeSqliteClient = (dbPath) => Effect.tryPromise({
|
|
98
|
+
try: async () => {
|
|
99
|
+
const dir = dirname(dbPath);
|
|
100
|
+
if (!existsSync(dir)) {
|
|
101
|
+
mkdirSync(dir, { recursive: true });
|
|
102
|
+
}
|
|
103
|
+
// Detect Vitest (uses better-sqlite3) vs Bun (uses bun:sqlite)
|
|
104
|
+
// Check both VITEST env var AND that we're NOT running under Bun.
|
|
105
|
+
// Tests spawn CLI subprocesses that inherit VITEST=true but should use bun:sqlite.
|
|
106
|
+
const isVitest = typeof process !== "undefined" && process.env.VITEST === "true"
|
|
107
|
+
&& typeof globalThis.Bun === "undefined";
|
|
108
|
+
if (isVitest) {
|
|
109
|
+
// Vitest: use better-sqlite3 wrapped as SqliteDatabase
|
|
110
|
+
const { default: BetterDatabase } = await import("better-sqlite3");
|
|
111
|
+
const raw = new BetterDatabase(dbPath);
|
|
112
|
+
raw.pragma("journal_mode = WAL");
|
|
113
|
+
raw.pragma("foreign_keys = ON");
|
|
114
|
+
raw.pragma("busy_timeout = " + (process.env.TX_DB_BUSY_TIMEOUT || "5000"));
|
|
115
|
+
const db = wrapBetterSqlite3ForClient(raw);
|
|
116
|
+
applyMigrations(db);
|
|
117
|
+
return db;
|
|
118
|
+
}
|
|
119
|
+
// Bun: use bun:sqlite
|
|
120
|
+
const { Database } = await import("bun:sqlite");
|
|
121
|
+
const db = new Database(dbPath);
|
|
122
|
+
db.run("PRAGMA busy_timeout = " + (process.env.TX_DB_BUSY_TIMEOUT || "5000"));
|
|
123
|
+
db.run("PRAGMA journal_mode = WAL");
|
|
124
|
+
db.run("PRAGMA foreign_keys = ON");
|
|
125
|
+
applyMigrations(db);
|
|
126
|
+
return db;
|
|
127
|
+
},
|
|
128
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
44
129
|
});
|
|
45
|
-
export const SqliteClientLive = (dbPath) => Layer.
|
|
130
|
+
export const SqliteClientLive = (dbPath) => Layer.scoped(SqliteClient, Effect.acquireRelease(makeSqliteClient(dbPath), (db) => Effect.sync(() => { try {
|
|
131
|
+
db.close();
|
|
132
|
+
}
|
|
133
|
+
catch { /* already closed */ } })));
|
|
46
134
|
//# sourceMappingURL=db.js.map
|