@jamesaphoenix/tx-core 0.4.2 → 0.4.4
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 +108 -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/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/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/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/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/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/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/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/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.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/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 +546 -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/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/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 +3 -3
- 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 +66 -27
- package/dist/services/orchestrator-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/sync-service.d.ts +17 -4
- package/dist/services/sync-service.d.ts.map +1 -1
- package/dist/services/sync-service.js +381 -116
- 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 +165 -35
- 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 +168 -72
- 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/dist/mappers/anchor.d.ts +0 -14
- package/dist/mappers/anchor.d.ts.map +0 -1
- package/dist/mappers/anchor.js +0 -38
- package/dist/mappers/anchor.js.map +0 -1
- package/dist/mappers/candidate.d.ts +0 -23
- package/dist/mappers/candidate.d.ts.map +0 -1
- package/dist/mappers/candidate.js +0 -53
- package/dist/mappers/candidate.js.map +0 -1
- package/dist/mappers/edge.d.ts +0 -10
- package/dist/mappers/edge.d.ts.map +0 -1
- package/dist/mappers/edge.js +0 -19
- package/dist/mappers/edge.js.map +0 -1
- package/dist/repo/anchor-repo.d.ts +0 -52
- package/dist/repo/anchor-repo.d.ts.map +0 -1
- package/dist/repo/anchor-repo.js +0 -204
- package/dist/repo/anchor-repo.js.map +0 -1
- package/dist/repo/candidate-repo.d.ts +0 -16
- package/dist/repo/candidate-repo.d.ts.map +0 -1
- package/dist/repo/candidate-repo.js +0 -143
- package/dist/repo/candidate-repo.js.map +0 -1
- package/dist/repo/edge-repo.d.ts +0 -26
- package/dist/repo/edge-repo.d.ts.map +0 -1
- package/dist/repo/edge-repo.js +0 -227
- package/dist/repo/edge-repo.js.map +0 -1
- package/dist/services/anchor-service.d.ts +0 -147
- package/dist/services/anchor-service.d.ts.map +0 -1
- package/dist/services/anchor-service.js +0 -540
- package/dist/services/anchor-service.js.map +0 -1
- package/dist/services/anchor-verification.d.ts +0 -94
- package/dist/services/anchor-verification.d.ts.map +0 -1
- package/dist/services/anchor-verification.js +0 -617
- package/dist/services/anchor-verification.js.map +0 -1
- package/dist/services/ast-grep-service.d.ts +0 -58
- package/dist/services/ast-grep-service.d.ts.map +0 -1
- package/dist/services/ast-grep-service.js +0 -356
- package/dist/services/ast-grep-service.js.map +0 -1
- package/dist/services/candidate-extractor-service.d.ts +0 -56
- package/dist/services/candidate-extractor-service.d.ts.map +0 -1
- package/dist/services/candidate-extractor-service.js +0 -365
- package/dist/services/candidate-extractor-service.js.map +0 -1
- package/dist/services/edge-service.d.ts +0 -78
- package/dist/services/edge-service.d.ts.map +0 -1
- package/dist/services/edge-service.js +0 -158
- package/dist/services/edge-service.js.map +0 -1
- package/dist/services/feedback-tracker.d.ts +0 -64
- package/dist/services/feedback-tracker.d.ts.map +0 -1
- package/dist/services/feedback-tracker.js +0 -110
- package/dist/services/feedback-tracker.js.map +0 -1
- package/dist/services/graph-expansion.d.ts +0 -155
- package/dist/services/graph-expansion.d.ts.map +0 -1
- package/dist/services/graph-expansion.js +0 -466
- package/dist/services/graph-expansion.js.map +0 -1
- package/dist/services/promotion-service.d.ts +0 -67
- package/dist/services/promotion-service.d.ts.map +0 -1
- package/dist/services/promotion-service.js +0 -151
- package/dist/services/promotion-service.js.map +0 -1
- package/dist/services/swarm-verification.d.ts +0 -104
- package/dist/services/swarm-verification.d.ts.map +0 -1
- package/dist/services/swarm-verification.js +0 -400
- package/dist/services/swarm-verification.js.map +0 -1
package/dist/repo/dep-repo.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { Context, Effect, Layer } from "effect";
|
|
2
2
|
import { SqliteClient } from "../db.js";
|
|
3
|
-
import { DatabaseError } from "../errors.js";
|
|
3
|
+
import { DatabaseError, DependencyNotFoundError, UnexpectedRowCountError } from "../errors.js";
|
|
4
4
|
import { rowToDependency } from "../mappers/task.js";
|
|
5
|
+
import { DEFAULT_QUERY_LIMIT } from "../utils/sql.js";
|
|
6
|
+
// Shared frozen empty array to avoid allocating new arrays for IDs with no dependencies
|
|
7
|
+
const EMPTY_TASK_IDS = Object.freeze([]);
|
|
8
|
+
/**
|
|
9
|
+
* Maximum recursion depth for dependency graph traversal.
|
|
10
|
+
* Prevents unbounded recursive CTEs from hitting SQLite's internal
|
|
11
|
+
* 1000-level recursion limit on deep dependency chains.
|
|
12
|
+
*/
|
|
13
|
+
const MAX_DEPENDENCY_DEPTH = 100;
|
|
5
14
|
export class DependencyRepository extends Context.Tag("DependencyRepository")() {
|
|
6
15
|
}
|
|
7
16
|
export const DependencyRepositoryLive = Layer.effect(DependencyRepository, Effect.gen(function* () {
|
|
@@ -9,15 +18,25 @@ export const DependencyRepositoryLive = Layer.effect(DependencyRepository, Effec
|
|
|
9
18
|
return {
|
|
10
19
|
insert: (blockerId, blockedId) => Effect.try({
|
|
11
20
|
try: () => {
|
|
12
|
-
db.prepare("INSERT INTO task_dependencies (blocker_id, blocked_id, created_at) VALUES (?, ?, ?)").run(blockerId, blockedId, new Date().toISOString());
|
|
21
|
+
const result = db.prepare("INSERT INTO task_dependencies (blocker_id, blocked_id, created_at) VALUES (?, ?, ?)").run(blockerId, blockedId, new Date().toISOString());
|
|
22
|
+
if (result.changes !== 1) {
|
|
23
|
+
throw new UnexpectedRowCountError({
|
|
24
|
+
operation: "dependency insert",
|
|
25
|
+
expected: 1,
|
|
26
|
+
actual: result.changes
|
|
27
|
+
});
|
|
28
|
+
}
|
|
13
29
|
},
|
|
14
30
|
catch: (cause) => new DatabaseError({ cause })
|
|
15
31
|
}),
|
|
16
|
-
remove: (blockerId, blockedId) => Effect.
|
|
17
|
-
|
|
18
|
-
db.prepare("DELETE FROM task_dependencies WHERE blocker_id = ? AND blocked_id = ?").run(blockerId, blockedId)
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
remove: (blockerId, blockedId) => Effect.gen(function* () {
|
|
33
|
+
const result = yield* Effect.try({
|
|
34
|
+
try: () => db.prepare("DELETE FROM task_dependencies WHERE blocker_id = ? AND blocked_id = ?").run(blockerId, blockedId),
|
|
35
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
36
|
+
});
|
|
37
|
+
if (result.changes === 0) {
|
|
38
|
+
yield* Effect.fail(new DependencyNotFoundError({ blockerId, blockedId }));
|
|
39
|
+
}
|
|
21
40
|
}),
|
|
22
41
|
getBlockerIds: (blockedId) => Effect.try({
|
|
23
42
|
try: () => {
|
|
@@ -40,14 +59,20 @@ export const DependencyRepositoryLive = Layer.effect(DependencyRepository, Effec
|
|
|
40
59
|
return result;
|
|
41
60
|
const placeholders = blockedIds.map(() => "?").join(",");
|
|
42
61
|
const rows = db.prepare(`SELECT blocked_id, blocker_id FROM task_dependencies WHERE blocked_id IN (${placeholders})`).all(...blockedIds);
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
result.set(id, []);
|
|
46
|
-
}
|
|
47
|
-
// Group by blocked_id
|
|
62
|
+
// Group rows by blocked_id in a temporary mutable map
|
|
63
|
+
const grouped = new Map();
|
|
48
64
|
for (const row of rows) {
|
|
49
|
-
const existing =
|
|
50
|
-
|
|
65
|
+
const existing = grouped.get(row.blocked_id);
|
|
66
|
+
if (existing) {
|
|
67
|
+
existing.push(row.blocker_id);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
grouped.set(row.blocked_id, [row.blocker_id]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Populate result: use grouped array or shared empty array
|
|
74
|
+
for (const id of blockedIds) {
|
|
75
|
+
result.set(id, grouped.get(id) ?? EMPTY_TASK_IDS);
|
|
51
76
|
}
|
|
52
77
|
return result;
|
|
53
78
|
},
|
|
@@ -60,44 +85,146 @@ export const DependencyRepositoryLive = Layer.effect(DependencyRepository, Effec
|
|
|
60
85
|
return result;
|
|
61
86
|
const placeholders = blockerIds.map(() => "?").join(",");
|
|
62
87
|
const rows = db.prepare(`SELECT blocker_id, blocked_id FROM task_dependencies WHERE blocker_id IN (${placeholders})`).all(...blockerIds);
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
result.set(id, []);
|
|
66
|
-
}
|
|
67
|
-
// Group by blocker_id
|
|
88
|
+
// Group rows by blocker_id in a temporary mutable map
|
|
89
|
+
const grouped = new Map();
|
|
68
90
|
for (const row of rows) {
|
|
69
|
-
const existing =
|
|
70
|
-
|
|
91
|
+
const existing = grouped.get(row.blocker_id);
|
|
92
|
+
if (existing) {
|
|
93
|
+
existing.push(row.blocked_id);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
grouped.set(row.blocker_id, [row.blocked_id]);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Populate result: use grouped array or shared empty array
|
|
100
|
+
for (const id of blockerIds) {
|
|
101
|
+
result.set(id, grouped.get(id) ?? EMPTY_TASK_IDS);
|
|
71
102
|
}
|
|
72
103
|
return result;
|
|
73
104
|
},
|
|
74
105
|
catch: (cause) => new DatabaseError({ cause })
|
|
75
106
|
}),
|
|
76
|
-
//
|
|
77
|
-
|
|
107
|
+
// Cycle detection using recursive CTE: can we reach toId by following blocker chains from fromId?
|
|
108
|
+
// Uses UNION (not UNION ALL) to automatically prevent infinite recursion on existing cycles
|
|
109
|
+
// Depth-limited to MAX_DEPENDENCY_DEPTH to prevent hitting SQLite's internal 1000-level limit
|
|
110
|
+
hasPath: (fromId, toId) => Effect.gen(function* () {
|
|
111
|
+
// Special case: same ID
|
|
112
|
+
if (fromId === toId)
|
|
113
|
+
return true;
|
|
114
|
+
// Recursive CTE performs BFS in a single query
|
|
115
|
+
// UNION deduplicates visited nodes, preventing infinite loops
|
|
116
|
+
// depth tracking prevents unbounded recursion on deep chains
|
|
117
|
+
const result = yield* Effect.try({
|
|
118
|
+
try: () => db.prepare(`
|
|
119
|
+
WITH RECURSIVE reachable(id, depth) AS (
|
|
120
|
+
-- Base case: direct blockers of fromId (depth 1)
|
|
121
|
+
SELECT blocker_id, 1 FROM task_dependencies WHERE blocked_id = ?
|
|
122
|
+
UNION
|
|
123
|
+
-- Recursive case: follow blocker chain, bounded by depth limit
|
|
124
|
+
SELECT d.blocker_id, r.depth + 1
|
|
125
|
+
FROM task_dependencies d
|
|
126
|
+
JOIN reachable r ON d.blocked_id = r.id
|
|
127
|
+
WHERE r.depth < ?
|
|
128
|
+
)
|
|
129
|
+
SELECT
|
|
130
|
+
MAX(CASE WHEN id = ? THEN 1 ELSE 0 END) AS found,
|
|
131
|
+
MAX(depth) AS max_depth
|
|
132
|
+
FROM reachable
|
|
133
|
+
`).get(fromId, MAX_DEPENDENCY_DEPTH, toId),
|
|
134
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
135
|
+
});
|
|
136
|
+
const pathFound = result?.found === 1;
|
|
137
|
+
const depthLimitHit = result?.max_depth != null && result.max_depth >= MAX_DEPENDENCY_DEPTH;
|
|
138
|
+
if (depthLimitHit) {
|
|
139
|
+
yield* Effect.logWarning(`Dependency depth limit (${MAX_DEPENDENCY_DEPTH}) reached checking path from ${fromId} to ${toId}. Graph may have unexplored deeper chains.`);
|
|
140
|
+
}
|
|
141
|
+
return pathFound;
|
|
142
|
+
}),
|
|
143
|
+
getAll: (limit) => Effect.try({
|
|
78
144
|
try: () => {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
while (queue.length > 0) {
|
|
82
|
-
const current = queue.shift();
|
|
83
|
-
if (current === toId)
|
|
84
|
-
return true;
|
|
85
|
-
if (visited.has(current))
|
|
86
|
-
continue;
|
|
87
|
-
visited.add(current);
|
|
88
|
-
const rows = db.prepare("SELECT blocker_id FROM task_dependencies WHERE blocked_id = ?").all(current);
|
|
89
|
-
queue.push(...rows.map(r => r.blocker_id));
|
|
90
|
-
}
|
|
91
|
-
return false;
|
|
145
|
+
const rows = db.prepare("SELECT blocker_id, blocked_id, created_at FROM task_dependencies LIMIT ?").all(limit ?? DEFAULT_QUERY_LIMIT);
|
|
146
|
+
return rows.map(rowToDependency);
|
|
92
147
|
},
|
|
93
148
|
catch: (cause) => new DatabaseError({ cause })
|
|
94
149
|
}),
|
|
95
|
-
|
|
150
|
+
removeByTaskIds: (taskIds) => Effect.try({
|
|
96
151
|
try: () => {
|
|
97
|
-
|
|
98
|
-
|
|
152
|
+
if (taskIds.length === 0)
|
|
153
|
+
return;
|
|
154
|
+
const placeholders = taskIds.map(() => "?").join(",");
|
|
155
|
+
db.prepare(`DELETE FROM task_dependencies WHERE blocker_id IN (${placeholders}) OR blocked_id IN (${placeholders})`).run(...taskIds, ...taskIds);
|
|
99
156
|
},
|
|
100
157
|
catch: (cause) => new DatabaseError({ cause })
|
|
158
|
+
}),
|
|
159
|
+
insertWithCycleCheck: (blockerId, blockedId) => Effect.gen(function* () {
|
|
160
|
+
const txResult = yield* Effect.try({
|
|
161
|
+
try: () => {
|
|
162
|
+
// BEGIN IMMEDIATE acquires write lock immediately, preventing other writers
|
|
163
|
+
// from modifying the dependency graph until we commit/rollback
|
|
164
|
+
db.exec("BEGIN IMMEDIATE");
|
|
165
|
+
try {
|
|
166
|
+
// Special case: same ID always indicates a cycle
|
|
167
|
+
if (blockerId === blockedId) {
|
|
168
|
+
db.exec("ROLLBACK");
|
|
169
|
+
return { _tag: "wouldCycle", depthLimitHit: false };
|
|
170
|
+
}
|
|
171
|
+
// Check if dependency already exists (idempotent)
|
|
172
|
+
const existing = db.prepare("SELECT 1 FROM task_dependencies WHERE blocker_id = ? AND blocked_id = ? LIMIT 1").get(blockerId, blockedId);
|
|
173
|
+
if (existing != null) {
|
|
174
|
+
db.exec("ROLLBACK");
|
|
175
|
+
return { _tag: "alreadyExists", depthLimitHit: false };
|
|
176
|
+
}
|
|
177
|
+
// Check if adding this dependency would create a cycle
|
|
178
|
+
// Uses depth-limited recursive CTE to detect if blockedId can already reach blockerId
|
|
179
|
+
const cycleCheck = db.prepare(`
|
|
180
|
+
WITH RECURSIVE reachable(id, depth) AS (
|
|
181
|
+
SELECT blocker_id, 1 FROM task_dependencies WHERE blocked_id = ?
|
|
182
|
+
UNION
|
|
183
|
+
SELECT d.blocker_id, r.depth + 1
|
|
184
|
+
FROM task_dependencies d
|
|
185
|
+
JOIN reachable r ON d.blocked_id = r.id
|
|
186
|
+
WHERE r.depth < ?
|
|
187
|
+
)
|
|
188
|
+
SELECT
|
|
189
|
+
MAX(CASE WHEN id = ? THEN 1 ELSE 0 END) AS found,
|
|
190
|
+
MAX(depth) AS max_depth
|
|
191
|
+
FROM reachable
|
|
192
|
+
`).get(blockerId, MAX_DEPENDENCY_DEPTH, blockedId);
|
|
193
|
+
const depthLimitHit = cycleCheck?.max_depth != null && cycleCheck.max_depth >= MAX_DEPENDENCY_DEPTH;
|
|
194
|
+
if (cycleCheck?.found === 1) {
|
|
195
|
+
db.exec("ROLLBACK");
|
|
196
|
+
return { _tag: "wouldCycle", depthLimitHit };
|
|
197
|
+
}
|
|
198
|
+
// No cycle detected - safe to insert
|
|
199
|
+
const result = db.prepare("INSERT INTO task_dependencies (blocker_id, blocked_id, created_at) VALUES (?, ?, ?)").run(blockerId, blockedId, new Date().toISOString());
|
|
200
|
+
if (result.changes !== 1) {
|
|
201
|
+
db.exec("ROLLBACK");
|
|
202
|
+
throw new UnexpectedRowCountError({
|
|
203
|
+
operation: "dependency insert (with cycle check)",
|
|
204
|
+
expected: 1,
|
|
205
|
+
actual: result.changes
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
db.exec("COMMIT");
|
|
209
|
+
return { _tag: "inserted", depthLimitHit };
|
|
210
|
+
}
|
|
211
|
+
catch (e) {
|
|
212
|
+
// Ensure rollback on any error
|
|
213
|
+
try {
|
|
214
|
+
db.exec("ROLLBACK");
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// Ignore rollback errors (transaction may already be rolled back)
|
|
218
|
+
}
|
|
219
|
+
throw e;
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
223
|
+
});
|
|
224
|
+
if (txResult.depthLimitHit) {
|
|
225
|
+
yield* Effect.logWarning(`Dependency depth limit (${MAX_DEPENDENCY_DEPTH}) reached during cycle check for ${blockerId} → ${blockedId}. Insert proceeded but deeper cycles may exist.`);
|
|
226
|
+
}
|
|
227
|
+
return { _tag: txResult._tag };
|
|
101
228
|
})
|
|
102
229
|
};
|
|
103
230
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dep-repo.js","sourceRoot":"","sources":["../../src/repo/dep-repo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"dep-repo.js","sourceRoot":"","sources":["../../src/repo/dep-repo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
|
+
import { SqliteClient } from "../db.js";
|
|
3
|
+
import { DatabaseError } from "../errors.js";
|
|
4
|
+
import type { Doc, DocKind, DocStatus, DocLink, DocLinkType, TaskDocLink, TaskDocLinkType, Invariant, InvariantCheck, UpsertInvariantInput } from "@jamesaphoenix/tx-types";
|
|
5
|
+
/** Input for inserting a new doc row. */
|
|
6
|
+
export type InsertDocInput = {
|
|
7
|
+
readonly hash: string;
|
|
8
|
+
readonly kind: DocKind;
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly title: string;
|
|
11
|
+
readonly version: number;
|
|
12
|
+
readonly filePath: string;
|
|
13
|
+
readonly parentDocId: number | null;
|
|
14
|
+
readonly metadata?: string;
|
|
15
|
+
};
|
|
16
|
+
/** Input for updating an existing doc row. */
|
|
17
|
+
export type UpdateDocInput = {
|
|
18
|
+
readonly hash?: string;
|
|
19
|
+
readonly title?: string;
|
|
20
|
+
readonly status?: DocStatus;
|
|
21
|
+
readonly lockedAt?: string;
|
|
22
|
+
readonly metadata?: string;
|
|
23
|
+
};
|
|
24
|
+
declare const DocRepository_base: Context.TagClass<DocRepository, "DocRepository", {
|
|
25
|
+
readonly insert: (input: InsertDocInput) => Effect.Effect<Doc, DatabaseError>;
|
|
26
|
+
readonly findById: (id: number) => Effect.Effect<Doc | null, DatabaseError>;
|
|
27
|
+
readonly findByName: (name: string, version?: number) => Effect.Effect<Doc | null, DatabaseError>;
|
|
28
|
+
readonly findAll: (filter?: {
|
|
29
|
+
kind?: DocKind;
|
|
30
|
+
status?: DocStatus;
|
|
31
|
+
}) => Effect.Effect<readonly Doc[], DatabaseError>;
|
|
32
|
+
readonly update: (id: number, input: UpdateDocInput) => Effect.Effect<void, DatabaseError>;
|
|
33
|
+
readonly lock: (id: number, lockedAt: string) => Effect.Effect<void, DatabaseError>;
|
|
34
|
+
readonly remove: (id: number) => Effect.Effect<void, DatabaseError>;
|
|
35
|
+
readonly createLink: (fromDocId: number, toDocId: number, linkType: DocLinkType) => Effect.Effect<DocLink, DatabaseError>;
|
|
36
|
+
readonly getLinksFrom: (docId: number) => Effect.Effect<readonly DocLink[], DatabaseError>;
|
|
37
|
+
readonly getLinksTo: (docId: number) => Effect.Effect<readonly DocLink[], DatabaseError>;
|
|
38
|
+
readonly getAllLinks: () => Effect.Effect<readonly DocLink[], DatabaseError>;
|
|
39
|
+
readonly createTaskLink: (taskId: string, docId: number, linkType: TaskDocLinkType) => Effect.Effect<TaskDocLink, DatabaseError>;
|
|
40
|
+
readonly getTaskLinksForDoc: (docId: number) => Effect.Effect<readonly TaskDocLink[], DatabaseError>;
|
|
41
|
+
readonly getDocForTask: (taskId: string) => Effect.Effect<Doc | null, DatabaseError>;
|
|
42
|
+
readonly getUnlinkedTaskIds: () => Effect.Effect<readonly string[], DatabaseError>;
|
|
43
|
+
readonly upsertInvariant: (input: UpsertInvariantInput) => Effect.Effect<Invariant, DatabaseError>;
|
|
44
|
+
readonly findInvariantById: (id: string) => Effect.Effect<Invariant | null, DatabaseError>;
|
|
45
|
+
readonly findInvariants: (filter?: {
|
|
46
|
+
docId?: number;
|
|
47
|
+
subsystem?: string;
|
|
48
|
+
enforcement?: string;
|
|
49
|
+
}) => Effect.Effect<readonly Invariant[], DatabaseError>;
|
|
50
|
+
readonly deprecateInvariantsNotIn: (docId: number, activeIds: readonly string[]) => Effect.Effect<void, DatabaseError>;
|
|
51
|
+
readonly insertInvariantCheck: (invariantId: string, passed: boolean, details: string | null, durationMs: number | null) => Effect.Effect<InvariantCheck, DatabaseError>;
|
|
52
|
+
readonly getInvariantChecks: (invariantId: string, limit?: number) => Effect.Effect<readonly InvariantCheck[], DatabaseError>;
|
|
53
|
+
readonly countInvariantsByDoc: (docId: number) => Effect.Effect<number, DatabaseError>;
|
|
54
|
+
}>;
|
|
55
|
+
export declare class DocRepository extends DocRepository_base {
|
|
56
|
+
}
|
|
57
|
+
export declare const DocRepositoryLive: Layer.Layer<DocRepository, never, SqliteClient>;
|
|
58
|
+
export {};
|
|
59
|
+
//# sourceMappingURL=doc-repo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doc-repo.d.ts","sourceRoot":"","sources":["../../src/repo/doc-repo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
|
+
import { SqliteClient } from "../db.js";
|
|
3
|
+
import { DatabaseError, EntityFetchError } from "../errors.js";
|
|
4
|
+
import { rowToDoc, rowToDocLink, rowToTaskDocLink, rowToInvariant, rowToInvariantCheck, } from "../mappers/doc.js";
|
|
5
|
+
export class DocRepository extends Context.Tag("DocRepository")() {
|
|
6
|
+
}
|
|
7
|
+
export const DocRepositoryLive = Layer.effect(DocRepository, Effect.gen(function* () {
|
|
8
|
+
const db = yield* SqliteClient;
|
|
9
|
+
return {
|
|
10
|
+
insert: (input) => Effect.try({
|
|
11
|
+
try: () => {
|
|
12
|
+
const now = new Date().toISOString();
|
|
13
|
+
const result = db.prepare(`INSERT INTO docs (hash, kind, name, title, version, status, file_path, parent_doc_id, created_at, metadata)
|
|
14
|
+
VALUES (?, ?, ?, ?, ?, 'changing', ?, ?, ?, ?)`).run(input.hash, input.kind, input.name, input.title, input.version, input.filePath, input.parentDocId, now, input.metadata ?? "{}");
|
|
15
|
+
const row = db.prepare("SELECT * FROM docs WHERE id = ?").get(result.lastInsertRowid);
|
|
16
|
+
if (!row) {
|
|
17
|
+
throw new EntityFetchError({ entity: "doc", id: result.lastInsertRowid, operation: "insert" });
|
|
18
|
+
}
|
|
19
|
+
return rowToDoc(row);
|
|
20
|
+
},
|
|
21
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
22
|
+
}),
|
|
23
|
+
findById: (id) => Effect.try({
|
|
24
|
+
try: () => {
|
|
25
|
+
const row = db.prepare("SELECT * FROM docs WHERE id = ?").get(id);
|
|
26
|
+
return row ? rowToDoc(row) : null;
|
|
27
|
+
},
|
|
28
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
29
|
+
}),
|
|
30
|
+
findByName: (name, version) => Effect.try({
|
|
31
|
+
try: () => {
|
|
32
|
+
const row = version !== undefined
|
|
33
|
+
? db.prepare("SELECT * FROM docs WHERE name = ? AND version = ?").get(name, version)
|
|
34
|
+
: db.prepare("SELECT * FROM docs WHERE name = ? ORDER BY version DESC LIMIT 1").get(name);
|
|
35
|
+
return row ? rowToDoc(row) : null;
|
|
36
|
+
},
|
|
37
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
38
|
+
}),
|
|
39
|
+
findAll: (filter) => Effect.try({
|
|
40
|
+
try: () => {
|
|
41
|
+
let sql = "SELECT * FROM docs";
|
|
42
|
+
const params = [];
|
|
43
|
+
const conditions = [];
|
|
44
|
+
if (filter?.kind) {
|
|
45
|
+
conditions.push("kind = ?");
|
|
46
|
+
params.push(filter.kind);
|
|
47
|
+
}
|
|
48
|
+
if (filter?.status) {
|
|
49
|
+
conditions.push("status = ?");
|
|
50
|
+
params.push(filter.status);
|
|
51
|
+
}
|
|
52
|
+
if (conditions.length > 0) {
|
|
53
|
+
sql += " WHERE " + conditions.join(" AND ");
|
|
54
|
+
}
|
|
55
|
+
sql += " ORDER BY kind, name, version";
|
|
56
|
+
const rows = db.prepare(sql).all(...params);
|
|
57
|
+
return rows.map(rowToDoc);
|
|
58
|
+
},
|
|
59
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
60
|
+
}),
|
|
61
|
+
update: (id, input) => Effect.try({
|
|
62
|
+
try: () => {
|
|
63
|
+
const sets = [];
|
|
64
|
+
const params = [];
|
|
65
|
+
if (input.hash !== undefined) {
|
|
66
|
+
sets.push("hash = ?");
|
|
67
|
+
params.push(input.hash);
|
|
68
|
+
}
|
|
69
|
+
if (input.title !== undefined) {
|
|
70
|
+
sets.push("title = ?");
|
|
71
|
+
params.push(input.title);
|
|
72
|
+
}
|
|
73
|
+
if (input.status !== undefined) {
|
|
74
|
+
sets.push("status = ?");
|
|
75
|
+
params.push(input.status);
|
|
76
|
+
}
|
|
77
|
+
if (input.lockedAt !== undefined) {
|
|
78
|
+
sets.push("locked_at = ?");
|
|
79
|
+
params.push(input.lockedAt);
|
|
80
|
+
}
|
|
81
|
+
if (input.metadata !== undefined) {
|
|
82
|
+
sets.push("metadata = ?");
|
|
83
|
+
params.push(input.metadata);
|
|
84
|
+
}
|
|
85
|
+
if (sets.length === 0)
|
|
86
|
+
return;
|
|
87
|
+
params.push(id);
|
|
88
|
+
db.prepare(`UPDATE docs SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
89
|
+
},
|
|
90
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
91
|
+
}),
|
|
92
|
+
lock: (id, lockedAt) => Effect.try({
|
|
93
|
+
try: () => {
|
|
94
|
+
db.prepare("UPDATE docs SET status = 'locked', locked_at = ? WHERE id = ?").run(lockedAt, id);
|
|
95
|
+
},
|
|
96
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
97
|
+
}),
|
|
98
|
+
remove: (id) => Effect.try({
|
|
99
|
+
try: () => {
|
|
100
|
+
// CASCADE on doc_links, task_doc_links, invariants handled by FK constraints
|
|
101
|
+
db.prepare("DELETE FROM docs WHERE id = ?").run(id);
|
|
102
|
+
},
|
|
103
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
104
|
+
}),
|
|
105
|
+
// Doc links
|
|
106
|
+
createLink: (fromDocId, toDocId, linkType) => Effect.try({
|
|
107
|
+
try: () => {
|
|
108
|
+
const now = new Date().toISOString();
|
|
109
|
+
const result = db.prepare("INSERT INTO doc_links (from_doc_id, to_doc_id, link_type, created_at) VALUES (?, ?, ?, ?)").run(fromDocId, toDocId, linkType, now);
|
|
110
|
+
const row = db.prepare("SELECT * FROM doc_links WHERE id = ?").get(result.lastInsertRowid);
|
|
111
|
+
if (!row) {
|
|
112
|
+
throw new EntityFetchError({ entity: "doc_link", id: result.lastInsertRowid, operation: "insert" });
|
|
113
|
+
}
|
|
114
|
+
return rowToDocLink(row);
|
|
115
|
+
},
|
|
116
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
117
|
+
}),
|
|
118
|
+
getLinksFrom: (docId) => Effect.try({
|
|
119
|
+
try: () => {
|
|
120
|
+
const rows = db.prepare("SELECT * FROM doc_links WHERE from_doc_id = ?").all(docId);
|
|
121
|
+
return rows.map(rowToDocLink);
|
|
122
|
+
},
|
|
123
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
124
|
+
}),
|
|
125
|
+
getLinksTo: (docId) => Effect.try({
|
|
126
|
+
try: () => {
|
|
127
|
+
const rows = db.prepare("SELECT * FROM doc_links WHERE to_doc_id = ?").all(docId);
|
|
128
|
+
return rows.map(rowToDocLink);
|
|
129
|
+
},
|
|
130
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
131
|
+
}),
|
|
132
|
+
getAllLinks: () => Effect.try({
|
|
133
|
+
try: () => {
|
|
134
|
+
const rows = db.prepare("SELECT * FROM doc_links ORDER BY created_at").all();
|
|
135
|
+
return rows.map(rowToDocLink);
|
|
136
|
+
},
|
|
137
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
138
|
+
}),
|
|
139
|
+
// Task-doc links
|
|
140
|
+
createTaskLink: (taskId, docId, linkType) => Effect.try({
|
|
141
|
+
try: () => {
|
|
142
|
+
const now = new Date().toISOString();
|
|
143
|
+
const result = db.prepare("INSERT INTO task_doc_links (task_id, doc_id, link_type, created_at) VALUES (?, ?, ?, ?)").run(taskId, docId, linkType, now);
|
|
144
|
+
const row = db.prepare("SELECT * FROM task_doc_links WHERE id = ?").get(result.lastInsertRowid);
|
|
145
|
+
if (!row) {
|
|
146
|
+
throw new EntityFetchError({ entity: "task_doc_link", id: result.lastInsertRowid, operation: "insert" });
|
|
147
|
+
}
|
|
148
|
+
return rowToTaskDocLink(row);
|
|
149
|
+
},
|
|
150
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
151
|
+
}),
|
|
152
|
+
getTaskLinksForDoc: (docId) => Effect.try({
|
|
153
|
+
try: () => {
|
|
154
|
+
const rows = db.prepare("SELECT * FROM task_doc_links WHERE doc_id = ?").all(docId);
|
|
155
|
+
return rows.map(rowToTaskDocLink);
|
|
156
|
+
},
|
|
157
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
158
|
+
}),
|
|
159
|
+
getDocForTask: (taskId) => Effect.try({
|
|
160
|
+
try: () => {
|
|
161
|
+
const row = db.prepare(`SELECT d.* FROM docs d
|
|
162
|
+
JOIN task_doc_links tdl ON tdl.doc_id = d.id
|
|
163
|
+
WHERE tdl.task_id = ?
|
|
164
|
+
ORDER BY d.version DESC LIMIT 1`).get(taskId);
|
|
165
|
+
return row ? rowToDoc(row) : null;
|
|
166
|
+
},
|
|
167
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
168
|
+
}),
|
|
169
|
+
getUnlinkedTaskIds: () => Effect.try({
|
|
170
|
+
try: () => {
|
|
171
|
+
const rows = db.prepare(`SELECT t.id FROM tasks t
|
|
172
|
+
LEFT JOIN task_doc_links tdl ON tdl.task_id = t.id
|
|
173
|
+
WHERE tdl.id IS NULL
|
|
174
|
+
ORDER BY t.id`).all();
|
|
175
|
+
return rows.map(r => r.id);
|
|
176
|
+
},
|
|
177
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
178
|
+
}),
|
|
179
|
+
// Invariants
|
|
180
|
+
upsertInvariant: (input) => Effect.try({
|
|
181
|
+
try: () => {
|
|
182
|
+
const now = new Date().toISOString();
|
|
183
|
+
db.prepare(`INSERT INTO invariants (id, rule, enforcement, doc_id, subsystem, test_ref, lint_rule, prompt_ref, status, created_at, metadata)
|
|
184
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'active', ?, '{}')
|
|
185
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
186
|
+
rule = excluded.rule,
|
|
187
|
+
enforcement = excluded.enforcement,
|
|
188
|
+
doc_id = excluded.doc_id,
|
|
189
|
+
subsystem = excluded.subsystem,
|
|
190
|
+
test_ref = excluded.test_ref,
|
|
191
|
+
lint_rule = excluded.lint_rule,
|
|
192
|
+
prompt_ref = excluded.prompt_ref,
|
|
193
|
+
status = 'active'`).run(input.id, input.rule, input.enforcement, input.docId, input.subsystem ?? null, input.testRef ?? null, input.lintRule ?? null, input.promptRef ?? null, now);
|
|
194
|
+
const row = db.prepare("SELECT * FROM invariants WHERE id = ?").get(input.id);
|
|
195
|
+
if (!row) {
|
|
196
|
+
throw new EntityFetchError({ entity: "invariant", id: input.id, operation: "insert" });
|
|
197
|
+
}
|
|
198
|
+
return rowToInvariant(row);
|
|
199
|
+
},
|
|
200
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
201
|
+
}),
|
|
202
|
+
findInvariantById: (id) => Effect.try({
|
|
203
|
+
try: () => {
|
|
204
|
+
const row = db.prepare("SELECT * FROM invariants WHERE id = ?").get(id);
|
|
205
|
+
return row ? rowToInvariant(row) : null;
|
|
206
|
+
},
|
|
207
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
208
|
+
}),
|
|
209
|
+
findInvariants: (filter) => Effect.try({
|
|
210
|
+
try: () => {
|
|
211
|
+
let sql = "SELECT * FROM invariants";
|
|
212
|
+
const params = [];
|
|
213
|
+
const conditions = [];
|
|
214
|
+
if (filter?.docId !== undefined) {
|
|
215
|
+
conditions.push("doc_id = ?");
|
|
216
|
+
params.push(filter.docId);
|
|
217
|
+
}
|
|
218
|
+
if (filter?.subsystem) {
|
|
219
|
+
conditions.push("subsystem = ?");
|
|
220
|
+
params.push(filter.subsystem);
|
|
221
|
+
}
|
|
222
|
+
if (filter?.enforcement) {
|
|
223
|
+
conditions.push("enforcement = ?");
|
|
224
|
+
params.push(filter.enforcement);
|
|
225
|
+
}
|
|
226
|
+
if (conditions.length > 0) {
|
|
227
|
+
sql += " WHERE " + conditions.join(" AND ");
|
|
228
|
+
}
|
|
229
|
+
sql += " ORDER BY id";
|
|
230
|
+
const rows = db.prepare(sql).all(...params);
|
|
231
|
+
return rows.map(rowToInvariant);
|
|
232
|
+
},
|
|
233
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
234
|
+
}),
|
|
235
|
+
deprecateInvariantsNotIn: (docId, activeIds) => Effect.try({
|
|
236
|
+
try: () => {
|
|
237
|
+
if (activeIds.length === 0) {
|
|
238
|
+
// Deprecate all invariants for this doc
|
|
239
|
+
db.prepare("UPDATE invariants SET status = 'deprecated' WHERE doc_id = ? AND status = 'active'").run(docId);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const placeholders = activeIds.map(() => "?").join(", ");
|
|
243
|
+
db.prepare(`UPDATE invariants SET status = 'deprecated'
|
|
244
|
+
WHERE doc_id = ? AND status = 'active' AND id NOT IN (${placeholders})`).run(docId, ...activeIds);
|
|
245
|
+
},
|
|
246
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
247
|
+
}),
|
|
248
|
+
insertInvariantCheck: (invariantId, passed, details, durationMs) => Effect.try({
|
|
249
|
+
try: () => {
|
|
250
|
+
const now = new Date().toISOString();
|
|
251
|
+
const result = db.prepare("INSERT INTO invariant_checks (invariant_id, passed, details, checked_at, duration_ms) VALUES (?, ?, ?, ?, ?)").run(invariantId, passed ? 1 : 0, details, now, durationMs);
|
|
252
|
+
const row = db.prepare("SELECT * FROM invariant_checks WHERE id = ?").get(result.lastInsertRowid);
|
|
253
|
+
if (!row) {
|
|
254
|
+
throw new EntityFetchError({ entity: "invariant_check", id: result.lastInsertRowid, operation: "insert" });
|
|
255
|
+
}
|
|
256
|
+
return rowToInvariantCheck(row);
|
|
257
|
+
},
|
|
258
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
259
|
+
}),
|
|
260
|
+
getInvariantChecks: (invariantId, limit = 20) => Effect.try({
|
|
261
|
+
try: () => {
|
|
262
|
+
const rows = db.prepare("SELECT * FROM invariant_checks WHERE invariant_id = ? ORDER BY checked_at DESC LIMIT ?").all(invariantId, limit);
|
|
263
|
+
return rows.map(rowToInvariantCheck);
|
|
264
|
+
},
|
|
265
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
266
|
+
}),
|
|
267
|
+
countInvariantsByDoc: (docId) => Effect.try({
|
|
268
|
+
try: () => {
|
|
269
|
+
const result = db.prepare("SELECT COUNT(*) as cnt FROM invariants WHERE doc_id = ? AND status = 'active'").get(docId);
|
|
270
|
+
return result.cnt;
|
|
271
|
+
},
|
|
272
|
+
catch: (cause) => new DatabaseError({ cause })
|
|
273
|
+
}),
|
|
274
|
+
};
|
|
275
|
+
}));
|
|
276
|
+
//# sourceMappingURL=doc-repo.js.map
|