@fleettools/squawk 0.1.0 → 0.1.1

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 (66) hide show
  1. package/package.json +1 -1
  2. package/AGENTS.md +0 -28
  3. package/dist/src/db/checkpoint-storage.d.ts +0 -19
  4. package/dist/src/db/checkpoint-storage.d.ts.map +0 -1
  5. package/dist/src/db/checkpoint-storage.js +0 -355
  6. package/dist/src/db/checkpoint-storage.js.map +0 -1
  7. package/dist/src/db/index.d.ts +0 -30
  8. package/dist/src/db/index.d.ts.map +0 -1
  9. package/dist/src/db/index.js +0 -329
  10. package/dist/src/db/index.js.map +0 -1
  11. package/dist/src/db/sqlite.d.ts +0 -31
  12. package/dist/src/db/sqlite.d.ts.map +0 -1
  13. package/dist/src/db/sqlite.js +0 -558
  14. package/dist/src/db/sqlite.js.map +0 -1
  15. package/dist/src/db/types.d.ts +0 -611
  16. package/dist/src/db/types.d.ts.map +0 -1
  17. package/dist/src/db/types.js +0 -4
  18. package/dist/src/db/types.js.map +0 -1
  19. package/dist/src/index.d.ts +0 -2
  20. package/dist/src/index.d.ts.map +0 -1
  21. package/dist/src/index.js +0 -285
  22. package/dist/src/index.js.map +0 -1
  23. package/dist/src/recovery/checkpointing.d.ts +0 -244
  24. package/dist/src/recovery/checkpointing.d.ts.map +0 -1
  25. package/dist/src/recovery/checkpointing.js +0 -511
  26. package/dist/src/recovery/checkpointing.js.map +0 -1
  27. package/dist/src/recovery/detection.d.ts +0 -137
  28. package/dist/src/recovery/detection.d.ts.map +0 -1
  29. package/dist/src/recovery/detection.js +0 -240
  30. package/dist/src/recovery/detection.js.map +0 -1
  31. package/dist/src/recovery/detector.d.ts +0 -34
  32. package/dist/src/recovery/detector.d.ts.map +0 -1
  33. package/dist/src/recovery/detector.js +0 -42
  34. package/dist/src/recovery/detector.js.map +0 -1
  35. package/dist/src/recovery/index.d.ts +0 -3
  36. package/dist/src/recovery/index.d.ts.map +0 -1
  37. package/dist/src/recovery/index.js +0 -3
  38. package/dist/src/recovery/index.js.map +0 -1
  39. package/dist/src/recovery/restorer.d.ts +0 -51
  40. package/dist/src/recovery/restorer.d.ts.map +0 -1
  41. package/dist/src/recovery/restorer.js +0 -266
  42. package/dist/src/recovery/restorer.js.map +0 -1
  43. package/dist/src/schemas.d.ts +0 -142
  44. package/dist/src/schemas.d.ts.map +0 -1
  45. package/dist/src/schemas.js +0 -110
  46. package/dist/src/schemas.js.map +0 -1
  47. package/src/db/checkpoint-storage.ts +0 -443
  48. package/src/db/index.d.ts +0 -30
  49. package/src/db/index.d.ts.map +0 -1
  50. package/src/db/index.js.map +0 -1
  51. package/src/db/index.ts +0 -417
  52. package/src/db/schema.sql +0 -112
  53. package/src/db/sqlite.d.ts +0 -31
  54. package/src/db/sqlite.d.ts.map +0 -1
  55. package/src/db/sqlite.js +0 -667
  56. package/src/db/sqlite.js.map +0 -1
  57. package/src/db/sqlite.ts +0 -677
  58. package/src/db/types.d.ts +0 -612
  59. package/src/db/types.d.ts.map +0 -1
  60. package/src/db/types.js +0 -4
  61. package/src/db/types.js.map +0 -1
  62. package/src/db/types.ts +0 -771
  63. package/src/index.ts +0 -332
  64. package/src/recovery/detector.ts +0 -82
  65. package/src/recovery/index.ts +0 -3
  66. package/src/recovery/restorer.ts +0 -377
package/src/index.ts DELETED
@@ -1,332 +0,0 @@
1
-
2
-
3
- import { closeDatabase, mailboxOps, eventOps, cursorOps, lockOps } from './db/index.js';
4
-
5
- console.log('Squawk API database initialized');
6
-
7
- const server = Bun.serve({
8
- port: parseInt(process.env.SQUAWK_PORT || '3001', 10),
9
- async fetch(request) {
10
- const url = new URL(request.url);
11
- const path = url.pathname;
12
-
13
- // CORS headers
14
- const headers: Record<string, string> = {
15
- 'Access-Control-Allow-Origin': '*',
16
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
17
- 'Access-Control-Allow-Headers': 'Content-Type',
18
- };
19
-
20
- if (request.method === 'OPTIONS') {
21
- return new Response(null, { headers });
22
- }
23
-
24
- if (path === '/health') {
25
- return new Response(JSON.stringify({
26
- status: 'healthy',
27
- service: 'squawk',
28
- timestamp: new Date().toISOString(),
29
- }), {
30
- headers: { ...headers, 'Content-Type': 'application/json' },
31
- });
32
- }
33
-
34
- // === MAILBOX ENDPOINTS ===
35
-
36
-
37
- if (path === '/api/v1/mailbox/append' && request.method === 'POST') {
38
- try {
39
- const body = await request.json() as { stream_id?: string; events?: any[] };
40
- const { stream_id, events } = body;
41
-
42
- if (!stream_id || !Array.isArray(events)) {
43
- return new Response(JSON.stringify({ error: 'stream_id and events array are required' }), {
44
- status: 400,
45
- headers: { ...headers, 'Content-Type': 'application/json' },
46
- });
47
- }
48
-
49
- if (!(await mailboxOps.exists(stream_id))) {
50
- await mailboxOps.create(stream_id);
51
- }
52
-
53
- const formattedEvents = events.map((e: any) => ({
54
- type: e.type,
55
- stream_id,
56
- data: JSON.stringify(e.data),
57
- occurred_at: new Date().toISOString(),
58
- causation_id: e.causation_id || null,
59
- metadata: e.metadata ? JSON.stringify(e.metadata) : null,
60
- }));
61
-
62
- const inserted = await eventOps.append(stream_id, formattedEvents);
63
- const mailbox = await mailboxOps.getById(stream_id);
64
- const mailboxEvents = await eventOps.getByMailbox(stream_id);
65
-
66
- return new Response(JSON.stringify({
67
- mailbox: { ...mailbox, events: mailboxEvents },
68
- inserted: inserted.length
69
- }), {
70
- headers: { ...headers, 'Content-Type': 'application/json' },
71
- });
72
- } catch (error) {
73
- console.error('Error appending to mailbox:', error);
74
- return new Response(JSON.stringify({ error: 'Failed to append to mailbox' }), {
75
- status: 500,
76
- headers: { ...headers, 'Content-Type': 'application/json' },
77
- });
78
- }
79
- }
80
-
81
-
82
- if (path.startsWith('/api/v1/mailbox/') && request.method === 'GET') {
83
- const streamId = path.split('/').pop();
84
-
85
- if (!streamId) {
86
- return new Response(JSON.stringify({ error: 'Invalid stream ID' }), {
87
- status: 400,
88
- headers: { ...headers, 'Content-Type': 'application/json' },
89
- });
90
- }
91
-
92
- try {
93
- const mailbox = await mailboxOps.getById(streamId);
94
-
95
- if (!mailbox) {
96
- return new Response(JSON.stringify({ error: 'Mailbox not found' }), {
97
- status: 404,
98
- headers: { ...headers, 'Content-Type': 'application/json' },
99
- });
100
- }
101
-
102
- const events = eventOps.getByMailbox(streamId);
103
- return new Response(JSON.stringify({ mailbox: { ...mailbox, events } }), {
104
- headers: { ...headers, 'Content-Type': 'application/json' },
105
- });
106
- } catch (error) {
107
- console.error('Error getting mailbox:', error);
108
- return new Response(JSON.stringify({ error: 'Failed to get mailbox' }), {
109
- status: 500,
110
- headers: { ...headers, 'Content-Type': 'application/json' },
111
- });
112
- }
113
- }
114
-
115
- // === CURSOR ENDPOINTS ===
116
-
117
-
118
- if (path === '/api/v1/cursor/advance' && request.method === 'POST') {
119
- try {
120
- const body = await request.json() as { stream_id?: string; position?: number };
121
- const { stream_id, position } = body;
122
-
123
- if (!stream_id || typeof position !== 'number') {
124
- return new Response(JSON.stringify({ error: 'stream_id and position are required' }), {
125
- status: 400,
126
- headers: { ...headers, 'Content-Type': 'application/json' },
127
- });
128
- }
129
-
130
- if (!(await mailboxOps.exists(stream_id))) {
131
- await mailboxOps.create(stream_id);
132
- }
133
-
134
- const cursor = await cursorOps.upsert({ stream_id, position: position!, updated_at: new Date().toISOString() });
135
- return new Response(JSON.stringify({ cursor }), {
136
- headers: { ...headers, 'Content-Type': 'application/json' },
137
- });
138
- } catch (error) {
139
- console.error('Error advancing cursor:', error);
140
- return new Response(JSON.stringify({ error: 'Failed to advance cursor' }), {
141
- status: 500,
142
- headers: { ...headers, 'Content-Type': 'application/json' },
143
- });
144
- }
145
- }
146
-
147
-
148
- if (path.startsWith('/api/v1/cursor/') && request.method === 'GET') {
149
- const cursorId = path.split('/').pop();
150
-
151
- if (!cursorId) {
152
- return new Response(JSON.stringify({ error: 'Invalid cursor ID' }), {
153
- status: 400,
154
- headers: { ...headers, 'Content-Type': 'application/json' },
155
- });
156
- }
157
-
158
- try {
159
- const cursor = await cursorOps.getById(cursorId);
160
-
161
- if (!cursor) {
162
- return new Response(JSON.stringify({ error: 'Cursor not found' }), {
163
- status: 404,
164
- headers: { ...headers, 'Content-Type': 'application/json' },
165
- });
166
- }
167
-
168
- return new Response(JSON.stringify({ cursor }), {
169
- headers: { ...headers, 'Content-Type': 'application/json' },
170
- });
171
- } catch (error) {
172
- console.error('Error getting cursor:', error);
173
- return new Response(JSON.stringify({ error: 'Failed to get cursor' }), {
174
- status: 500,
175
- headers: { ...headers, 'Content-Type': 'application/json' },
176
- });
177
- }
178
- }
179
-
180
- // === LOCK ENDPOINTS ===
181
-
182
-
183
- if (path === '/api/v1/lock/acquire' && request.method === 'POST') {
184
- try {
185
- const body = await request.json() as { file?: string; specialist_id?: string; timeout_ms?: number };
186
- const { file, specialist_id, timeout_ms = 30000 } = body;
187
-
188
- if (!file || !specialist_id) {
189
- return new Response(JSON.stringify({ error: 'file and specialist_id are required' }), {
190
- status: 400,
191
- headers: { ...headers, 'Content-Type': 'application/json' },
192
- });
193
- }
194
-
195
- const lock = await lockOps.acquire({
196
- file: file!,
197
- reserved_by: specialist_id!,
198
- reserved_at: new Date().toISOString(),
199
- released_at: null,
200
- purpose: 'edit',
201
- checksum: null,
202
- timeout_ms,
203
- metadata: null,
204
- });
205
-
206
- return new Response(JSON.stringify({ lock }), {
207
- headers: { ...headers, 'Content-Type': 'application/json' },
208
- });
209
- } catch (error) {
210
- console.error('Error acquiring lock:', error);
211
- return new Response(JSON.stringify({ error: 'Failed to acquire lock' }), {
212
- status: 500,
213
- headers: { ...headers, 'Content-Type': 'application/json' },
214
- });
215
- }
216
- }
217
-
218
-
219
- if (path === '/api/v1/lock/release' && request.method === 'POST') {
220
- try {
221
- const body = await request.json() as { lock_id?: string; specialist_id?: string };
222
- const { lock_id, specialist_id } = body;
223
-
224
- if (!lock_id) {
225
- return new Response(JSON.stringify({ error: 'lock_id is required' }), {
226
- status: 400,
227
- headers: { ...headers, 'Content-Type': 'application/json' },
228
- });
229
- }
230
-
231
- const lock = await lockOps.getById(lock_id!);
232
-
233
- if (!lock) {
234
- return new Response(JSON.stringify({ error: 'Lock not found' }), {
235
- status: 404,
236
- headers: { ...headers, 'Content-Type': 'application/json' },
237
- });
238
- }
239
-
240
- if (lock.reserved_by !== specialist_id) {
241
- return new Response(JSON.stringify({ error: 'Cannot release lock: wrong specialist' }), {
242
- status: 403,
243
- headers: { ...headers, 'Content-Type': 'application/json' },
244
- });
245
- }
246
-
247
- const updatedLock = await lockOps.release(lock_id!);
248
- return new Response(JSON.stringify({ lock: updatedLock }), {
249
- headers: { ...headers, 'Content-Type': 'application/json' },
250
- });
251
- } catch (error) {
252
- console.error('Error releasing lock:', error);
253
- return new Response(JSON.stringify({ error: 'Failed to release lock' }), {
254
- status: 500,
255
- headers: { ...headers, 'Content-Type': 'application/json' },
256
- });
257
- }
258
- }
259
-
260
-
261
- if (path === '/api/v1/locks' && request.method === 'GET') {
262
- try {
263
- const locks = await lockOps.getAll();
264
- return new Response(JSON.stringify({ locks }), {
265
- headers: { ...headers, 'Content-Type': 'application/json' },
266
- });
267
- } catch (error) {
268
- console.error('Error listing locks:', error);
269
- return new Response(JSON.stringify({ error: 'Failed to list locks' }), {
270
- status: 500,
271
- headers: { ...headers, 'Content-Type': 'application/json' },
272
- });
273
- }
274
- }
275
-
276
- // === COORDINATOR ENDPOINTS ===
277
-
278
-
279
- if (path === '/api/v1/coordinator/status' && request.method === 'GET') {
280
- try {
281
- const mailboxes = await mailboxOps.getAll();
282
- const locks = await lockOps.getAll();
283
-
284
- return new Response(JSON.stringify({
285
- active_mailboxes: mailboxes.length,
286
- active_locks: locks.length,
287
- timestamp: new Date().toISOString(),
288
- }), {
289
- headers: { ...headers, 'Content-Type': 'application/json' },
290
- });
291
- } catch (error) {
292
- console.error('Error getting coordinator status:', error);
293
- return new Response(JSON.stringify({ error: 'Failed to get status' }), {
294
- status: 500,
295
- headers: { ...headers, 'Content-Type': 'application/json' },
296
- });
297
- }
298
- }
299
-
300
- // 404
301
- return new Response(JSON.stringify({ error: 'Not found' }), {
302
- status: 404,
303
- headers: { ...headers, 'Content-Type': 'application/json' },
304
- });
305
- },
306
- });
307
-
308
- setInterval(async () => {
309
- const released = await lockOps.releaseExpired();
310
- if (released > 0) {
311
- console.log(`Released ${released} expired locks`);
312
- }
313
- }, 30000); // Check every 30 seconds
314
-
315
- console.log(`Squawk API server listening on port ${server.port}`);
316
- console.log('Endpoints:');
317
- console.log(' POST /api/v1/mailbox/append - Append events to mailbox');
318
- console.log(' GET /api/v1/mailbox/:streamId - Get mailbox contents');
319
- console.log(' POST /api/v1/cursor/advance - Advance cursor position');
320
- console.log(' GET /api/v1/cursor/:cursorId - Get cursor position');
321
- console.log(' POST /api/v1/lock/acquire - Acquire file lock (CTK)');
322
- console.log(' POST /api/v1/lock/release - Release file lock (CTK)');
323
- console.log(' GET /api/v1/locks - List all active locks');
324
- console.log(' GET /api/v1/coordinator/status - Get coordinator status');
325
- console.log(' GET /health - Health check');
326
-
327
- process.on('SIGINT', () => {
328
- console.log('Shutting down...');
329
- closeDatabase();
330
- server.stop();
331
- process.exit(0);
332
- });
@@ -1,82 +0,0 @@
1
-
2
- import type { Mission, Checkpoint } from '../db/types.js';
3
-
4
- export interface RecoveryCandidate {
5
- mission_id: string;
6
- mission_title: string;
7
- last_activity_at: string;
8
- inactivity_duration_ms: number;
9
- checkpoint_id?: string;
10
- checkpoint_progress?: number;
11
- checkpoint_timestamp?: string;
12
- }
13
-
14
- export interface DetectionOptions {
15
- activityThresholdMs?: number
16
- includeCompleted?: boolean;
17
- }
18
-
19
- const DEFAULT_ACTIVITY_THRESHOLD_MS = 5 * 60 * 1000;
20
-
21
- export class RecoveryDetector {
22
- constructor(
23
- private db: {
24
- missions: { getByStatus: (status: string) => Promise<Mission[]> };
25
- events: { getLatestByStream: (type: string, id: string) => Promise<any | null> };
26
- checkpoints: { getLatestByMission: (missionId: string) => Promise<Checkpoint | null> };
27
- }
28
- ) {}
29
-
30
- async detectRecoveryCandidates(
31
- options: DetectionOptions = {}
32
- ): Promise<RecoveryCandidate[]> {
33
- const {
34
- activityThresholdMs = DEFAULT_ACTIVITY_THRESHOLD_MS,
35
- includeCompleted = false,
36
- } = options;
37
-
38
- const now = Date.now();
39
- const candidates: RecoveryCandidate[] = [];
40
-
41
- const activeMissions = await this.db.missions.getByStatus('in_progress');
42
-
43
- for (const mission of activeMissions) {
44
- const latestEvent = await this.db.events.getLatestByStream('mission', mission.id);
45
-
46
- if (!latestEvent) continue;
47
-
48
- const lastActivityAt = new Date(latestEvent.occurred_at).getTime();
49
- const inactivityDuration = now - lastActivityAt;
50
-
51
- if (inactivityDuration > activityThresholdMs) {
52
- const checkpoint = await this.db.checkpoints.getLatestByMission(mission.id);
53
-
54
- candidates.push({
55
- mission_id: mission.id,
56
- mission_title: mission.title,
57
- last_activity_at: latestEvent.occurred_at,
58
- inactivity_duration_ms: inactivityDuration,
59
- checkpoint_id: checkpoint?.id,
60
- checkpoint_progress: checkpoint?.progress_percent,
61
- checkpoint_timestamp: checkpoint?.timestamp,
62
- });
63
- }
64
- }
65
-
66
- return candidates;
67
- }
68
-
69
- async checkForRecovery(options: DetectionOptions = {}): Promise<{
70
- needed: boolean;
71
- candidates: RecoveryCandidate[];
72
- }> {
73
- const candidates = await this.detectRecoveryCandidates(options);
74
-
75
- const recoverableCandidates = candidates.filter(c => c.checkpoint_id);
76
-
77
- return {
78
- needed: recoverableCandidates.length > 0,
79
- candidates: recoverableCandidates,
80
- };
81
- }
82
- }
@@ -1,3 +0,0 @@
1
-
2
- export { RecoveryDetector, type RecoveryCandidate, type DetectionOptions } from './detector.js';
3
- export { StateRestorer, type RestoreResult, type RestoreOptions } from './restorer.js';