@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.
- package/package.json +1 -1
- package/AGENTS.md +0 -28
- package/dist/src/db/checkpoint-storage.d.ts +0 -19
- package/dist/src/db/checkpoint-storage.d.ts.map +0 -1
- package/dist/src/db/checkpoint-storage.js +0 -355
- package/dist/src/db/checkpoint-storage.js.map +0 -1
- package/dist/src/db/index.d.ts +0 -30
- package/dist/src/db/index.d.ts.map +0 -1
- package/dist/src/db/index.js +0 -329
- package/dist/src/db/index.js.map +0 -1
- package/dist/src/db/sqlite.d.ts +0 -31
- package/dist/src/db/sqlite.d.ts.map +0 -1
- package/dist/src/db/sqlite.js +0 -558
- package/dist/src/db/sqlite.js.map +0 -1
- package/dist/src/db/types.d.ts +0 -611
- package/dist/src/db/types.d.ts.map +0 -1
- package/dist/src/db/types.js +0 -4
- package/dist/src/db/types.js.map +0 -1
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -285
- package/dist/src/index.js.map +0 -1
- package/dist/src/recovery/checkpointing.d.ts +0 -244
- package/dist/src/recovery/checkpointing.d.ts.map +0 -1
- package/dist/src/recovery/checkpointing.js +0 -511
- package/dist/src/recovery/checkpointing.js.map +0 -1
- package/dist/src/recovery/detection.d.ts +0 -137
- package/dist/src/recovery/detection.d.ts.map +0 -1
- package/dist/src/recovery/detection.js +0 -240
- package/dist/src/recovery/detection.js.map +0 -1
- package/dist/src/recovery/detector.d.ts +0 -34
- package/dist/src/recovery/detector.d.ts.map +0 -1
- package/dist/src/recovery/detector.js +0 -42
- package/dist/src/recovery/detector.js.map +0 -1
- package/dist/src/recovery/index.d.ts +0 -3
- package/dist/src/recovery/index.d.ts.map +0 -1
- package/dist/src/recovery/index.js +0 -3
- package/dist/src/recovery/index.js.map +0 -1
- package/dist/src/recovery/restorer.d.ts +0 -51
- package/dist/src/recovery/restorer.d.ts.map +0 -1
- package/dist/src/recovery/restorer.js +0 -266
- package/dist/src/recovery/restorer.js.map +0 -1
- package/dist/src/schemas.d.ts +0 -142
- package/dist/src/schemas.d.ts.map +0 -1
- package/dist/src/schemas.js +0 -110
- package/dist/src/schemas.js.map +0 -1
- package/src/db/checkpoint-storage.ts +0 -443
- package/src/db/index.d.ts +0 -30
- package/src/db/index.d.ts.map +0 -1
- package/src/db/index.js.map +0 -1
- package/src/db/index.ts +0 -417
- package/src/db/schema.sql +0 -112
- package/src/db/sqlite.d.ts +0 -31
- package/src/db/sqlite.d.ts.map +0 -1
- package/src/db/sqlite.js +0 -667
- package/src/db/sqlite.js.map +0 -1
- package/src/db/sqlite.ts +0 -677
- package/src/db/types.d.ts +0 -612
- package/src/db/types.d.ts.map +0 -1
- package/src/db/types.js +0 -4
- package/src/db/types.js.map +0 -1
- package/src/db/types.ts +0 -771
- package/src/index.ts +0 -332
- package/src/recovery/detector.ts +0 -82
- package/src/recovery/index.ts +0 -3
- 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
|
-
});
|
package/src/recovery/detector.ts
DELETED
|
@@ -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
|
-
}
|
package/src/recovery/index.ts
DELETED