hzl-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/__tests__/backup/backup-restore.test.d.ts +2 -0
  2. package/dist/__tests__/backup/backup-restore.test.d.ts.map +1 -0
  3. package/dist/__tests__/backup/backup-restore.test.js +200 -0
  4. package/dist/__tests__/backup/backup-restore.test.js.map +1 -0
  5. package/dist/__tests__/backup/import-export.test.d.ts +2 -0
  6. package/dist/__tests__/backup/import-export.test.d.ts.map +1 -0
  7. package/dist/__tests__/backup/import-export.test.js +341 -0
  8. package/dist/__tests__/backup/import-export.test.js.map +1 -0
  9. package/dist/__tests__/concurrency/stress.test.d.ts +2 -0
  10. package/dist/__tests__/concurrency/stress.test.d.ts.map +1 -0
  11. package/dist/__tests__/concurrency/stress.test.js +274 -0
  12. package/dist/__tests__/concurrency/stress.test.js.map +1 -0
  13. package/dist/__tests__/concurrency/worker.d.ts +2 -0
  14. package/dist/__tests__/concurrency/worker.d.ts.map +1 -0
  15. package/dist/__tests__/concurrency/worker.js +84 -0
  16. package/dist/__tests__/concurrency/worker.js.map +1 -0
  17. package/dist/__tests__/migrations/upgrade.test.d.ts +2 -0
  18. package/dist/__tests__/migrations/upgrade.test.d.ts.map +1 -0
  19. package/dist/__tests__/migrations/upgrade.test.js +203 -0
  20. package/dist/__tests__/migrations/upgrade.test.js.map +1 -0
  21. package/dist/__tests__/projections/rebuild-equivalence.test.d.ts +2 -0
  22. package/dist/__tests__/projections/rebuild-equivalence.test.d.ts.map +1 -0
  23. package/dist/__tests__/projections/rebuild-equivalence.test.js +276 -0
  24. package/dist/__tests__/projections/rebuild-equivalence.test.js.map +1 -0
  25. package/dist/__tests__/properties/invariants.test.d.ts +2 -0
  26. package/dist/__tests__/properties/invariants.test.d.ts.map +1 -0
  27. package/dist/__tests__/properties/invariants.test.js +314 -0
  28. package/dist/__tests__/properties/invariants.test.js.map +1 -0
  29. package/dist/db/connection.d.ts +13 -0
  30. package/dist/db/connection.d.ts.map +1 -0
  31. package/dist/db/connection.js +52 -0
  32. package/dist/db/connection.js.map +1 -0
  33. package/dist/db/connection.test.d.ts +2 -0
  34. package/dist/db/connection.test.d.ts.map +1 -0
  35. package/dist/db/connection.test.js +63 -0
  36. package/dist/db/connection.test.js.map +1 -0
  37. package/dist/db/migrations/v2.d.ts +2 -0
  38. package/dist/db/migrations/v2.d.ts.map +1 -0
  39. package/dist/db/migrations/v2.js +4 -0
  40. package/dist/db/migrations/v2.js.map +1 -0
  41. package/dist/db/migrations.d.ts +4 -0
  42. package/dist/db/migrations.d.ts.map +1 -0
  43. package/dist/db/migrations.js +45 -0
  44. package/dist/db/migrations.js.map +1 -0
  45. package/dist/db/migrations.test.d.ts +2 -0
  46. package/dist/db/migrations.test.d.ts.map +1 -0
  47. package/dist/db/migrations.test.js +75 -0
  48. package/dist/db/migrations.test.js.map +1 -0
  49. package/dist/db/schema.d.ts +3 -0
  50. package/dist/db/schema.d.ts.map +1 -0
  51. package/dist/db/schema.js +114 -0
  52. package/dist/db/schema.js.map +1 -0
  53. package/dist/events/store.d.ts +33 -0
  54. package/dist/events/store.d.ts.map +1 -0
  55. package/dist/events/store.js +81 -0
  56. package/dist/events/store.js.map +1 -0
  57. package/dist/events/store.test.d.ts +2 -0
  58. package/dist/events/store.test.d.ts.map +1 -0
  59. package/dist/events/store.test.js +138 -0
  60. package/dist/events/store.test.js.map +1 -0
  61. package/dist/events/types.d.ts +106 -0
  62. package/dist/events/types.d.ts.map +1 -0
  63. package/dist/events/types.js +87 -0
  64. package/dist/events/types.js.map +1 -0
  65. package/dist/events/validation.test.d.ts +2 -0
  66. package/dist/events/validation.test.d.ts.map +1 -0
  67. package/dist/events/validation.test.js +83 -0
  68. package/dist/events/validation.test.js.map +1 -0
  69. package/dist/fixtures/sample-data.d.ts +16 -0
  70. package/dist/fixtures/sample-data.d.ts.map +1 -0
  71. package/dist/fixtures/sample-data.js +148 -0
  72. package/dist/fixtures/sample-data.js.map +1 -0
  73. package/dist/index.d.ts +27 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +44 -0
  76. package/dist/index.js.map +1 -0
  77. package/dist/index.test.d.ts +2 -0
  78. package/dist/index.test.d.ts.map +1 -0
  79. package/dist/index.test.js +102 -0
  80. package/dist/index.test.js.map +1 -0
  81. package/dist/projections/comments-checkpoints.d.ts +11 -0
  82. package/dist/projections/comments-checkpoints.d.ts.map +1 -0
  83. package/dist/projections/comments-checkpoints.js +33 -0
  84. package/dist/projections/comments-checkpoints.js.map +1 -0
  85. package/dist/projections/comments-checkpoints.test.d.ts +2 -0
  86. package/dist/projections/comments-checkpoints.test.d.ts.map +1 -0
  87. package/dist/projections/comments-checkpoints.test.js +72 -0
  88. package/dist/projections/comments-checkpoints.test.js.map +1 -0
  89. package/dist/projections/dependencies.d.ts +12 -0
  90. package/dist/projections/dependencies.d.ts.map +1 -0
  91. package/dist/projections/dependencies.js +39 -0
  92. package/dist/projections/dependencies.js.map +1 -0
  93. package/dist/projections/dependencies.test.d.ts +2 -0
  94. package/dist/projections/dependencies.test.d.ts.map +1 -0
  95. package/dist/projections/dependencies.test.js +97 -0
  96. package/dist/projections/dependencies.test.js.map +1 -0
  97. package/dist/projections/engine.d.ts +18 -0
  98. package/dist/projections/engine.d.ts.map +1 -0
  99. package/dist/projections/engine.js +56 -0
  100. package/dist/projections/engine.js.map +1 -0
  101. package/dist/projections/engine.test.d.ts +2 -0
  102. package/dist/projections/engine.test.d.ts.map +1 -0
  103. package/dist/projections/engine.test.js +92 -0
  104. package/dist/projections/engine.test.js.map +1 -0
  105. package/dist/projections/rebuild.d.ts +4 -0
  106. package/dist/projections/rebuild.d.ts.map +1 -0
  107. package/dist/projections/rebuild.js +26 -0
  108. package/dist/projections/rebuild.js.map +1 -0
  109. package/dist/projections/rebuild.test.d.ts +2 -0
  110. package/dist/projections/rebuild.test.d.ts.map +1 -0
  111. package/dist/projections/rebuild.test.js +59 -0
  112. package/dist/projections/rebuild.test.js.map +1 -0
  113. package/dist/projections/search.d.ts +11 -0
  114. package/dist/projections/search.d.ts.map +1 -0
  115. package/dist/projections/search.js +39 -0
  116. package/dist/projections/search.js.map +1 -0
  117. package/dist/projections/search.test.d.ts +2 -0
  118. package/dist/projections/search.test.d.ts.map +1 -0
  119. package/dist/projections/search.test.js +78 -0
  120. package/dist/projections/search.test.js.map +1 -0
  121. package/dist/projections/tags.d.ts +12 -0
  122. package/dist/projections/tags.d.ts.map +1 -0
  123. package/dist/projections/tags.js +41 -0
  124. package/dist/projections/tags.js.map +1 -0
  125. package/dist/projections/tags.test.d.ts +2 -0
  126. package/dist/projections/tags.test.d.ts.map +1 -0
  127. package/dist/projections/tags.test.js +69 -0
  128. package/dist/projections/tags.test.js.map +1 -0
  129. package/dist/projections/tasks-current.d.ts +14 -0
  130. package/dist/projections/tasks-current.d.ts.map +1 -0
  131. package/dist/projections/tasks-current.js +110 -0
  132. package/dist/projections/tasks-current.js.map +1 -0
  133. package/dist/projections/tasks-current.test.d.ts +2 -0
  134. package/dist/projections/tasks-current.test.d.ts.map +1 -0
  135. package/dist/projections/tasks-current.test.js +215 -0
  136. package/dist/projections/tasks-current.test.js.map +1 -0
  137. package/dist/projections/types.d.ts +13 -0
  138. package/dist/projections/types.d.ts.map +1 -0
  139. package/dist/projections/types.js +2 -0
  140. package/dist/projections/types.js.map +1 -0
  141. package/dist/services/backup-service.d.ts +16 -0
  142. package/dist/services/backup-service.d.ts.map +1 -0
  143. package/dist/services/backup-service.js +114 -0
  144. package/dist/services/backup-service.js.map +1 -0
  145. package/dist/services/search-service.d.ts +27 -0
  146. package/dist/services/search-service.d.ts.map +1 -0
  147. package/dist/services/search-service.js +33 -0
  148. package/dist/services/search-service.js.map +1 -0
  149. package/dist/services/search-service.test.d.ts +2 -0
  150. package/dist/services/search-service.test.d.ts.map +1 -0
  151. package/dist/services/search-service.test.js +66 -0
  152. package/dist/services/search-service.test.js.map +1 -0
  153. package/dist/services/task-service.d.ts +147 -0
  154. package/dist/services/task-service.d.ts.map +1 -0
  155. package/dist/services/task-service.js +442 -0
  156. package/dist/services/task-service.js.map +1 -0
  157. package/dist/services/task-service.test.d.ts +2 -0
  158. package/dist/services/task-service.test.d.ts.map +1 -0
  159. package/dist/services/task-service.test.js +399 -0
  160. package/dist/services/task-service.test.js.map +1 -0
  161. package/dist/services/validation-service.d.ts +29 -0
  162. package/dist/services/validation-service.d.ts.map +1 -0
  163. package/dist/services/validation-service.js +74 -0
  164. package/dist/services/validation-service.js.map +1 -0
  165. package/dist/services/validation-service.test.d.ts +2 -0
  166. package/dist/services/validation-service.test.d.ts.map +1 -0
  167. package/dist/services/validation-service.test.js +67 -0
  168. package/dist/services/validation-service.test.js.map +1 -0
  169. package/dist/utils/id.d.ts +3 -0
  170. package/dist/utils/id.d.ts.map +1 -0
  171. package/dist/utils/id.js +12 -0
  172. package/dist/utils/id.js.map +1 -0
  173. package/dist/utils/id.test.d.ts +2 -0
  174. package/dist/utils/id.test.d.ts.map +1 -0
  175. package/dist/utils/id.test.js +24 -0
  176. package/dist/utils/id.test.js.map +1 -0
  177. package/package.json +83 -0
@@ -0,0 +1,110 @@
1
+ import { EventType, TaskStatus } from '../events/types.js';
2
+ const JSON_FIELDS = new Set(['tags', 'links', 'metadata']);
3
+ export class TasksCurrentProjector {
4
+ name = 'tasks_current';
5
+ apply(event, db) {
6
+ switch (event.type) {
7
+ case EventType.TaskCreated:
8
+ this.handleTaskCreated(event, db);
9
+ break;
10
+ case EventType.StatusChanged:
11
+ this.handleStatusChanged(event, db);
12
+ break;
13
+ case EventType.TaskMoved:
14
+ this.handleTaskMoved(event, db);
15
+ break;
16
+ case EventType.TaskUpdated:
17
+ this.handleTaskUpdated(event, db);
18
+ break;
19
+ case EventType.TaskArchived:
20
+ this.handleTaskArchived(event, db);
21
+ break;
22
+ }
23
+ }
24
+ reset(db) {
25
+ db.exec('DELETE FROM tasks_current');
26
+ }
27
+ handleTaskCreated(event, db) {
28
+ const data = event.data;
29
+ db.prepare(`
30
+ INSERT INTO tasks_current (
31
+ task_id, title, project, status, parent_id, description,
32
+ links, tags, priority, due_at, metadata,
33
+ created_at, updated_at, last_event_id
34
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
35
+ `).run(event.task_id, data.title, data.project, TaskStatus.Backlog, data.parent_id ?? null, data.description ?? null, JSON.stringify(data.links ?? []), JSON.stringify(data.tags ?? []), data.priority ?? 0, data.due_at ?? null, JSON.stringify(data.metadata ?? {}), event.timestamp, event.timestamp, event.rowid);
36
+ }
37
+ handleStatusChanged(event, db) {
38
+ const data = event.data;
39
+ const toStatus = data.to;
40
+ if (toStatus === TaskStatus.InProgress) {
41
+ db.prepare(`
42
+ UPDATE tasks_current SET
43
+ status = ?,
44
+ claimed_at = ?,
45
+ claimed_by_author = ?,
46
+ claimed_by_agent_id = ?,
47
+ lease_until = ?,
48
+ updated_at = ?,
49
+ last_event_id = ?
50
+ WHERE task_id = ?
51
+ `).run(toStatus, event.timestamp, event.author ?? null, event.agent_id ?? null, data.lease_until ?? null, event.timestamp, event.rowid, event.task_id);
52
+ }
53
+ else if (data.from === TaskStatus.InProgress) {
54
+ db.prepare(`
55
+ UPDATE tasks_current SET
56
+ status = ?,
57
+ claimed_at = NULL,
58
+ claimed_by_author = NULL,
59
+ claimed_by_agent_id = NULL,
60
+ lease_until = NULL,
61
+ updated_at = ?,
62
+ last_event_id = ?
63
+ WHERE task_id = ?
64
+ `).run(toStatus, event.timestamp, event.rowid, event.task_id);
65
+ }
66
+ else {
67
+ db.prepare(`
68
+ UPDATE tasks_current SET
69
+ status = ?,
70
+ updated_at = ?,
71
+ last_event_id = ?
72
+ WHERE task_id = ?
73
+ `).run(toStatus, event.timestamp, event.rowid, event.task_id);
74
+ }
75
+ }
76
+ handleTaskMoved(event, db) {
77
+ const data = event.data;
78
+ db.prepare(`
79
+ UPDATE tasks_current SET
80
+ project = ?,
81
+ updated_at = ?,
82
+ last_event_id = ?
83
+ WHERE task_id = ?
84
+ `).run(data.to_project, event.timestamp, event.rowid, event.task_id);
85
+ }
86
+ handleTaskUpdated(event, db) {
87
+ const data = event.data;
88
+ const field = data.field;
89
+ const newValue = JSON_FIELDS.has(field)
90
+ ? JSON.stringify(data.new_value)
91
+ : data.new_value;
92
+ db.prepare(`
93
+ UPDATE tasks_current SET
94
+ ${field} = ?,
95
+ updated_at = ?,
96
+ last_event_id = ?
97
+ WHERE task_id = ?
98
+ `).run(newValue, event.timestamp, event.rowid, event.task_id);
99
+ }
100
+ handleTaskArchived(event, db) {
101
+ db.prepare(`
102
+ UPDATE tasks_current SET
103
+ status = ?,
104
+ updated_at = ?,
105
+ last_event_id = ?
106
+ WHERE task_id = ?
107
+ `).run(TaskStatus.Archived, event.timestamp, event.rowid, event.task_id);
108
+ }
109
+ }
110
+ //# sourceMappingURL=tasks-current.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tasks-current.js","sourceRoot":"","sources":["../../src/projections/tasks-current.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tasks-current.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tasks-current.test.d.ts","sourceRoot":"","sources":["../../src/projections/tasks-current.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,215 @@
1
+ // packages/hzl-core/src/projections/tasks-current.test.ts
2
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
3
+ import Database from 'better-sqlite3';
4
+ import { TasksCurrentProjector } from './tasks-current.js';
5
+ import { runMigrations } from '../db/migrations.js';
6
+ import { EventStore } from '../events/store.js';
7
+ import { EventType, TaskStatus } from '../events/types.js';
8
+ describe('TasksCurrentProjector', () => {
9
+ let db;
10
+ let eventStore;
11
+ let projector;
12
+ beforeEach(() => {
13
+ db = new Database(':memory:');
14
+ runMigrations(db);
15
+ eventStore = new EventStore(db);
16
+ projector = new TasksCurrentProjector();
17
+ });
18
+ afterEach(() => {
19
+ db.close();
20
+ });
21
+ describe('task_created', () => {
22
+ it('inserts new task with defaults', () => {
23
+ const event = eventStore.append({
24
+ task_id: 'TASK1',
25
+ type: EventType.TaskCreated,
26
+ data: { title: 'Test task', project: 'inbox' },
27
+ });
28
+ projector.apply(event, db);
29
+ const task = db.prepare('SELECT * FROM tasks_current WHERE task_id = ?').get('TASK1');
30
+ expect(task.title).toBe('Test task');
31
+ expect(task.project).toBe('inbox');
32
+ expect(task.status).toBe('backlog');
33
+ expect(task.priority).toBe(0);
34
+ expect(JSON.parse(task.tags)).toEqual([]);
35
+ });
36
+ it('inserts task with all optional fields', () => {
37
+ const event = eventStore.append({
38
+ task_id: 'TASK1',
39
+ type: EventType.TaskCreated,
40
+ data: {
41
+ title: 'Full task',
42
+ project: 'project-a',
43
+ description: 'A description',
44
+ tags: ['urgent', 'backend'],
45
+ priority: 2,
46
+ links: ['doc.md'],
47
+ metadata: { key: 'value' },
48
+ },
49
+ });
50
+ projector.apply(event, db);
51
+ const task = db.prepare('SELECT * FROM tasks_current WHERE task_id = ?').get('TASK1');
52
+ expect(task.description).toBe('A description');
53
+ expect(JSON.parse(task.tags)).toEqual(['urgent', 'backend']);
54
+ expect(task.priority).toBe(2);
55
+ expect(JSON.parse(task.links)).toEqual(['doc.md']);
56
+ expect(JSON.parse(task.metadata)).toEqual({ key: 'value' });
57
+ });
58
+ });
59
+ describe('status_changed', () => {
60
+ it('updates status', () => {
61
+ const createEvent = eventStore.append({
62
+ task_id: 'TASK1',
63
+ type: EventType.TaskCreated,
64
+ data: { title: 'Test', project: 'inbox' },
65
+ });
66
+ projector.apply(createEvent, db);
67
+ const statusEvent = eventStore.append({
68
+ task_id: 'TASK1',
69
+ type: EventType.StatusChanged,
70
+ data: { from: TaskStatus.Backlog, to: TaskStatus.Ready },
71
+ });
72
+ projector.apply(statusEvent, db);
73
+ const task = db.prepare('SELECT * FROM tasks_current WHERE task_id = ?').get('TASK1');
74
+ expect(task.status).toBe('ready');
75
+ });
76
+ it('sets claim fields when transitioning to in_progress', () => {
77
+ const createEvent = eventStore.append({
78
+ task_id: 'TASK1',
79
+ type: EventType.TaskCreated,
80
+ data: { title: 'Test', project: 'inbox' },
81
+ author: 'agent-1',
82
+ agent_id: 'AGENT001',
83
+ });
84
+ projector.apply(createEvent, db);
85
+ const claimEvent = eventStore.append({
86
+ task_id: 'TASK1',
87
+ type: EventType.StatusChanged,
88
+ data: {
89
+ from: TaskStatus.Ready,
90
+ to: TaskStatus.InProgress,
91
+ lease_until: '2026-01-30T12:00:00Z',
92
+ },
93
+ author: 'agent-1',
94
+ agent_id: 'AGENT001',
95
+ });
96
+ projector.apply(claimEvent, db);
97
+ const task = db.prepare('SELECT * FROM tasks_current WHERE task_id = ?').get('TASK1');
98
+ expect(task.status).toBe('in_progress');
99
+ expect(task.claimed_at).toBeDefined();
100
+ expect(task.claimed_by_author).toBe('agent-1');
101
+ expect(task.claimed_by_agent_id).toBe('AGENT001');
102
+ expect(task.lease_until).toBe('2026-01-30T12:00:00Z');
103
+ });
104
+ it('clears claim fields when released', () => {
105
+ const createEvent = eventStore.append({
106
+ task_id: 'TASK1',
107
+ type: EventType.TaskCreated,
108
+ data: { title: 'Test', project: 'inbox' },
109
+ });
110
+ projector.apply(createEvent, db);
111
+ const claimEvent = eventStore.append({
112
+ task_id: 'TASK1',
113
+ type: EventType.StatusChanged,
114
+ data: { from: TaskStatus.Ready, to: TaskStatus.InProgress },
115
+ author: 'agent-1',
116
+ });
117
+ projector.apply(claimEvent, db);
118
+ const releaseEvent = eventStore.append({
119
+ task_id: 'TASK1',
120
+ type: EventType.StatusChanged,
121
+ data: { from: TaskStatus.InProgress, to: TaskStatus.Ready },
122
+ });
123
+ projector.apply(releaseEvent, db);
124
+ const task = db.prepare('SELECT * FROM tasks_current WHERE task_id = ?').get('TASK1');
125
+ expect(task.status).toBe('ready');
126
+ expect(task.claimed_at).toBeNull();
127
+ expect(task.claimed_by_author).toBeNull();
128
+ expect(task.lease_until).toBeNull();
129
+ });
130
+ });
131
+ describe('task_moved', () => {
132
+ it('updates project', () => {
133
+ const createEvent = eventStore.append({
134
+ task_id: 'TASK1',
135
+ type: EventType.TaskCreated,
136
+ data: { title: 'Test', project: 'inbox' },
137
+ });
138
+ projector.apply(createEvent, db);
139
+ const moveEvent = eventStore.append({
140
+ task_id: 'TASK1',
141
+ type: EventType.TaskMoved,
142
+ data: { from_project: 'inbox', to_project: 'project-a' },
143
+ });
144
+ projector.apply(moveEvent, db);
145
+ const task = db.prepare('SELECT * FROM tasks_current WHERE task_id = ?').get('TASK1');
146
+ expect(task.project).toBe('project-a');
147
+ });
148
+ });
149
+ describe('task_updated', () => {
150
+ it('updates title', () => {
151
+ const createEvent = eventStore.append({
152
+ task_id: 'TASK1',
153
+ type: EventType.TaskCreated,
154
+ data: { title: 'Original', project: 'inbox' },
155
+ });
156
+ projector.apply(createEvent, db);
157
+ const updateEvent = eventStore.append({
158
+ task_id: 'TASK1',
159
+ type: EventType.TaskUpdated,
160
+ data: { field: 'title', old_value: 'Original', new_value: 'Updated' },
161
+ });
162
+ projector.apply(updateEvent, db);
163
+ const task = db.prepare('SELECT * FROM tasks_current WHERE task_id = ?').get('TASK1');
164
+ expect(task.title).toBe('Updated');
165
+ });
166
+ it('updates tags as JSON', () => {
167
+ const createEvent = eventStore.append({
168
+ task_id: 'TASK1',
169
+ type: EventType.TaskCreated,
170
+ data: { title: 'Test', project: 'inbox' },
171
+ });
172
+ projector.apply(createEvent, db);
173
+ const updateEvent = eventStore.append({
174
+ task_id: 'TASK1',
175
+ type: EventType.TaskUpdated,
176
+ data: { field: 'tags', new_value: ['new-tag'] },
177
+ });
178
+ projector.apply(updateEvent, db);
179
+ const task = db.prepare('SELECT * FROM tasks_current WHERE task_id = ?').get('TASK1');
180
+ expect(JSON.parse(task.tags)).toEqual(['new-tag']);
181
+ });
182
+ });
183
+ describe('task_archived', () => {
184
+ it('sets status to archived', () => {
185
+ const createEvent = eventStore.append({
186
+ task_id: 'TASK1',
187
+ type: EventType.TaskCreated,
188
+ data: { title: 'Test', project: 'inbox' },
189
+ });
190
+ projector.apply(createEvent, db);
191
+ const archiveEvent = eventStore.append({
192
+ task_id: 'TASK1',
193
+ type: EventType.TaskArchived,
194
+ data: { reason: 'No longer needed' },
195
+ });
196
+ projector.apply(archiveEvent, db);
197
+ const task = db.prepare('SELECT * FROM tasks_current WHERE task_id = ?').get('TASK1');
198
+ expect(task.status).toBe('archived');
199
+ });
200
+ });
201
+ describe('reset', () => {
202
+ it('clears all task data', () => {
203
+ const createEvent = eventStore.append({
204
+ task_id: 'TASK1',
205
+ type: EventType.TaskCreated,
206
+ data: { title: 'Test', project: 'inbox' },
207
+ });
208
+ projector.apply(createEvent, db);
209
+ projector.reset(db);
210
+ const count = db.prepare('SELECT COUNT(*) as cnt FROM tasks_current').get();
211
+ expect(count.cnt).toBe(0);
212
+ });
213
+ });
214
+ });
215
+ //# sourceMappingURL=tasks-current.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tasks-current.test.js","sourceRoot":"","sources":["../../src/projections/tasks-current.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type { PersistedEventEnvelope } from '../events/store.js';
3
+ export interface Projector {
4
+ name: string;
5
+ apply(event: PersistedEventEnvelope, db: Database.Database): void;
6
+ reset?(db: Database.Database): void;
7
+ }
8
+ export interface ProjectionState {
9
+ name: string;
10
+ last_event_id: number;
11
+ updated_at: string;
12
+ }
13
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/projections/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAEjE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,KAAK,EAAE,sBAAsB,EAAE,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAAC;IAClE,KAAK,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAAC;CACrC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/projections/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ import Database from 'better-sqlite3';
2
+ export interface ImportResult {
3
+ imported: number;
4
+ skipped: number;
5
+ errors: number;
6
+ }
7
+ export declare class BackupService {
8
+ private db;
9
+ constructor(db: Database.Database);
10
+ backup(destPath: string): Promise<void>;
11
+ restore(srcPath: string, destPath: string): Promise<void>;
12
+ exportEvents(destPath: string): Promise<void>;
13
+ importEvents(srcPath: string): Promise<ImportResult>;
14
+ private rebuildProjections;
15
+ }
16
+ //# sourceMappingURL=backup-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup-service.d.ts","sourceRoot":"","sources":["../../src/services/backup-service.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAYtC,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,aAAa;IACZ,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,QAAQ,CAAC,QAAQ;IAEnC,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMvC,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBzD,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuC7C,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAoD1D,OAAO,CAAC,kBAAkB;CAS3B"}
@@ -0,0 +1,114 @@
1
+ import Database from 'better-sqlite3';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import readline from 'readline';
5
+ import { ProjectionEngine } from '../projections/engine.js';
6
+ import { TasksCurrentProjector } from '../projections/tasks-current.js';
7
+ import { DependenciesProjector } from '../projections/dependencies.js';
8
+ import { TagsProjector } from '../projections/tags.js';
9
+ import { SearchProjector } from '../projections/search.js';
10
+ import { CommentsCheckpointsProjector } from '../projections/comments-checkpoints.js';
11
+ import { rebuildAllProjections } from '../projections/rebuild.js';
12
+ export class BackupService {
13
+ db;
14
+ constructor(db) {
15
+ this.db = db;
16
+ }
17
+ async backup(destPath) {
18
+ const dir = path.dirname(destPath);
19
+ fs.mkdirSync(dir, { recursive: true });
20
+ await this.db.backup(destPath);
21
+ }
22
+ async restore(srcPath, destPath) {
23
+ if (!fs.existsSync(srcPath)) {
24
+ throw new Error(`Backup file not found: ${srcPath}`);
25
+ }
26
+ try {
27
+ const testDb = new Database(srcPath, { readonly: true });
28
+ testDb.prepare("SELECT name FROM sqlite_master WHERE type='table'").get();
29
+ testDb.close();
30
+ }
31
+ catch (err) {
32
+ throw new Error(`Invalid backup file: ${srcPath}`);
33
+ }
34
+ const dir = path.dirname(destPath);
35
+ fs.mkdirSync(dir, { recursive: true });
36
+ fs.copyFileSync(srcPath, destPath);
37
+ }
38
+ async exportEvents(destPath) {
39
+ const dir = path.dirname(destPath);
40
+ fs.mkdirSync(dir, { recursive: true });
41
+ const stream = fs.createWriteStream(destPath, { encoding: 'utf-8' });
42
+ const rows = this.db
43
+ .prepare(`
44
+ SELECT event_id, task_id, type, data, author, agent_id, session_id,
45
+ correlation_id, causation_id, timestamp
46
+ FROM events
47
+ ORDER BY id ASC
48
+ `)
49
+ .all();
50
+ for (const row of rows) {
51
+ const payload = {
52
+ event_id: row.event_id,
53
+ task_id: row.task_id,
54
+ type: row.type,
55
+ data: JSON.parse(row.data),
56
+ author: row.author ?? undefined,
57
+ agent_id: row.agent_id ?? undefined,
58
+ session_id: row.session_id ?? undefined,
59
+ correlation_id: row.correlation_id ?? undefined,
60
+ causation_id: row.causation_id ?? undefined,
61
+ timestamp: row.timestamp,
62
+ };
63
+ stream.write(`${JSON.stringify(payload)}\n`);
64
+ }
65
+ await new Promise((resolve, reject) => {
66
+ stream.end(() => resolve());
67
+ stream.on('error', reject);
68
+ });
69
+ }
70
+ async importEvents(srcPath) {
71
+ if (!fs.existsSync(srcPath)) {
72
+ throw new Error(`Export file not found: ${srcPath}`);
73
+ }
74
+ const insertStmt = this.db.prepare(`
75
+ INSERT OR IGNORE INTO events
76
+ (event_id, task_id, type, data, author, agent_id, session_id, correlation_id, causation_id, timestamp)
77
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
78
+ `);
79
+ let imported = 0;
80
+ let skipped = 0;
81
+ let errors = 0;
82
+ const fileStream = fs.createReadStream(srcPath, { encoding: 'utf-8' });
83
+ const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity });
84
+ for await (const line of rl) {
85
+ if (!line.trim())
86
+ continue;
87
+ try {
88
+ const event = JSON.parse(line);
89
+ const result = insertStmt.run(event.event_id, event.task_id, event.type, JSON.stringify(event.data ?? {}), event.author ?? null, event.agent_id ?? null, event.session_id ?? null, event.correlation_id ?? null, event.causation_id ?? null, event.timestamp);
90
+ if (result.changes === 0) {
91
+ skipped += 1;
92
+ }
93
+ else {
94
+ imported += 1;
95
+ }
96
+ }
97
+ catch (err) {
98
+ errors += 1;
99
+ }
100
+ }
101
+ this.rebuildProjections();
102
+ return { imported, skipped, errors };
103
+ }
104
+ rebuildProjections() {
105
+ const engine = new ProjectionEngine(this.db);
106
+ engine.register(new TasksCurrentProjector());
107
+ engine.register(new DependenciesProjector());
108
+ engine.register(new TagsProjector());
109
+ engine.register(new SearchProjector());
110
+ engine.register(new CommentsCheckpointsProjector());
111
+ rebuildAllProjections(this.db, engine);
112
+ }
113
+ }
114
+ //# sourceMappingURL=backup-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup-service.js","sourceRoot":"","sources":["../../src/services/backup-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type Database from 'better-sqlite3';
2
+ export interface SearchTaskResult {
3
+ task_id: string;
4
+ title: string;
5
+ project: string;
6
+ status: string;
7
+ description: string | null;
8
+ priority: number;
9
+ rank: number;
10
+ }
11
+ export interface SearchResult {
12
+ tasks: SearchTaskResult[];
13
+ total: number;
14
+ limit: number;
15
+ offset: number;
16
+ }
17
+ export interface SearchOptions {
18
+ project?: string;
19
+ limit?: number;
20
+ offset?: number;
21
+ }
22
+ export declare class SearchService {
23
+ private db;
24
+ constructor(db: Database.Database);
25
+ search(query: string, opts?: SearchOptions): SearchResult;
26
+ }
27
+ //# sourceMappingURL=search-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-service.d.ts","sourceRoot":"","sources":["../../src/services/search-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,MAAM,WAAW,gBAAgB;IAAG,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;CAAE;AAClK,MAAM,WAAW,YAAY;IAAG,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;CAAE;AAC1G,MAAM,WAAW,aAAa;IAAG,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAAE;AAErF,qBAAa,aAAa;IACZ,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,QAAQ,CAAC,QAAQ;IAEzC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,YAAY;CA6B1D"}
@@ -0,0 +1,33 @@
1
+ export class SearchService {
2
+ db;
3
+ constructor(db) {
4
+ this.db = db;
5
+ }
6
+ search(query, opts) {
7
+ const limit = opts?.limit ?? 50;
8
+ const offset = opts?.offset ?? 0;
9
+ const trimmedQuery = query.trim();
10
+ if (!trimmedQuery)
11
+ return { tasks: [], total: 0, limit, offset };
12
+ const safeQuery = trimmedQuery.split(/\s+/).filter(w => w.length > 0).map(w => w.replace(/[^a-zA-Z0-9]/g, '')).filter(w => w.length > 0).join(' ');
13
+ if (!safeQuery)
14
+ return { tasks: [], total: 0, limit, offset };
15
+ let countQuery, searchQuery;
16
+ const params = [];
17
+ if (opts?.project) {
18
+ countQuery = `SELECT COUNT(*) as total FROM task_search s JOIN tasks_current t ON s.task_id = t.task_id WHERE task_search MATCH ? AND t.project = ?`;
19
+ searchQuery = `SELECT t.task_id, t.title, t.project, t.status, t.description, t.priority, rank FROM task_search s JOIN tasks_current t ON s.task_id = t.task_id WHERE task_search MATCH ? AND t.project = ? ORDER BY rank LIMIT ? OFFSET ?`;
20
+ params.push(safeQuery, opts.project, limit, offset);
21
+ }
22
+ else {
23
+ countQuery = `SELECT COUNT(*) as total FROM task_search s JOIN tasks_current t ON s.task_id = t.task_id WHERE task_search MATCH ?`;
24
+ searchQuery = `SELECT t.task_id, t.title, t.project, t.status, t.description, t.priority, rank FROM task_search s JOIN tasks_current t ON s.task_id = t.task_id WHERE task_search MATCH ? ORDER BY rank LIMIT ? OFFSET ?`;
25
+ params.push(safeQuery, limit, offset);
26
+ }
27
+ const countParams = opts?.project ? [safeQuery, opts.project] : [safeQuery];
28
+ const total = this.db.prepare(countQuery).get(...countParams).total;
29
+ const rows = this.db.prepare(searchQuery).all(...params);
30
+ return { tasks: rows, total, limit, offset };
31
+ }
32
+ }
33
+ //# sourceMappingURL=search-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-service.js","sourceRoot":"","sources":["../../src/services/search-service.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,aAAa;IACJ;IAApB,YAAoB,EAAqB;QAArB,OAAE,GAAF,EAAE,CAAmB;IAAG,CAAC;IAE7C,MAAM,CAAC,KAAa,EAAE,IAAoB;QACxC,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC;QACjC,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAElC,IAAI,CAAC,YAAY;YAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAEjE,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnJ,IAAI,CAAC,SAAS;YAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAE9D,IAAI,UAAkB,EAAE,WAAmB,CAAC;QAC5C,MAAM,MAAM,GAAU,EAAE,CAAC;QAEzB,IAAI,IAAI,EAAE,OAAO,EAAE,CAAC;YAClB,UAAU,GAAG,uIAAuI,CAAC;YACrJ,WAAW,GAAG,6NAA6N,CAAC;YAC5O,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QACtD,CAAC;aAAM,CAAC;YACN,UAAU,GAAG,qHAAqH,CAAC;YACnI,WAAW,GAAG,2MAA2M,CAAC;YAC1N,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QACxC,CAAC;QAED,MAAM,WAAW,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAC5E,MAAM,KAAK,GAAI,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,GAAG,WAAW,CAAuB,CAAC,KAAK,CAAC;QAC3F,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,GAAG,MAAM,CAAuB,CAAC;QAE/E,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAC/C,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=search-service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-service.test.d.ts","sourceRoot":"","sources":["../../src/services/search-service.test.ts"],"names":[],"mappings":""}