@deksden-com/dd-flow-cli 0.3.0 → 0.4.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.
- package/CHANGELOG.md +55 -0
- package/README.md +74 -5
- package/dist/build-info.json +5 -5
- package/dist/cli/help.js +116 -18
- package/dist/cli/run-cli.js +361 -29
- package/dist/schemas/compatibility.schema.json +81 -2
- package/dist/schemas/engine-manifest.schema.json +61 -0
- package/dist/schemas/flow-guidance.schema.json +17 -0
- package/dist/schemas/global-dashboard-data.schema.json +60 -2
- package/dist/schemas/mb-upgrade-migration-report.schema.json +93 -0
- package/dist/schemas/project-dashboard-data.schema.json +26 -2
- package/dist/schemas/project-summary.schema.json +73 -0
- package/dist/schemas/protocol-dashboard-data.schema.json +25 -2
- package/dist/schemas/status-report.schema.json +4 -2
- package/dist/services/branch-context.js +254 -0
- package/dist/services/cleanup.js +31 -0
- package/dist/services/cli-operation-classifier.js +104 -0
- package/dist/services/compatibility-preflight.js +127 -0
- package/dist/services/config.js +6 -0
- package/dist/services/dashboard-targets.js +95 -0
- package/dist/services/dashboard.js +376 -59
- package/dist/services/engines.js +532 -0
- package/dist/services/flow-guidance.js +8 -1
- package/dist/services/hooks.js +1 -1
- package/dist/services/lanes.js +333 -1
- package/dist/services/merge-queue.js +310 -15
- package/dist/services/merge-worker.js +44 -3
- package/dist/services/migrations.js +231 -0
- package/dist/services/project-summary.js +122 -0
- package/dist/services/projects.js +41 -6
- package/dist/services/protocol-lifecycle.js +144 -0
- package/dist/services/protocols.js +34 -7
- package/dist/services/sessions.js +21 -4
- package/dist/services/status.js +10 -0
- package/dist/services/version-status.js +39 -15
- package/dist/storage/database.js +25 -0
- package/dist/storage/paths.js +12 -0
- package/package.json +3 -2
package/dist/services/lanes.js
CHANGED
|
@@ -11,15 +11,20 @@ const defaultPollIntervalSeconds = 10;
|
|
|
11
11
|
export function getLaneStatus(context, input) {
|
|
12
12
|
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
13
13
|
expireStaleLocks(context, project.id);
|
|
14
|
+
expireStaleWaiters(context, project.id);
|
|
14
15
|
return {
|
|
15
16
|
ok: true,
|
|
16
17
|
lanes: lanesForProject(context, project.id, input.lane),
|
|
17
|
-
locks: laneLocksForProject(context, project.id, input.lane)
|
|
18
|
+
locks: laneLocksForProject(context, project.id, input.lane),
|
|
19
|
+
waiters: laneWaitersForProject(context, project.id, input.lane)
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
22
|
export function expireProjectLaneLocks(context, projectId) {
|
|
21
23
|
expireStaleLocks(context, projectId);
|
|
22
24
|
}
|
|
25
|
+
export function expireProjectLaneWaiters(context, projectId) {
|
|
26
|
+
expireStaleWaiters(context, projectId);
|
|
27
|
+
}
|
|
23
28
|
export function setLaneWorkspace(context, input) {
|
|
24
29
|
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
25
30
|
const lane = normalizeLane(input.lane);
|
|
@@ -172,6 +177,124 @@ export async function waitForLaneLock(context, input) {
|
|
|
172
177
|
await delay(pollIntervalSeconds * 1000);
|
|
173
178
|
}
|
|
174
179
|
}
|
|
180
|
+
export function getLaneWaiters(context, input) {
|
|
181
|
+
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
182
|
+
expireStaleLocks(context, project.id);
|
|
183
|
+
expireStaleWaiters(context, project.id);
|
|
184
|
+
return {
|
|
185
|
+
ok: true,
|
|
186
|
+
lane: input.lane ? normalizeLane(input.lane) : null,
|
|
187
|
+
waiters: laneWaitersForProject(context, project.id, input.lane).map((waiter) => ({
|
|
188
|
+
...waiter,
|
|
189
|
+
position: waiter.status === "queued" ? queuedWaiterPosition(context, project.id, waiter.lane, waiter.id) : null
|
|
190
|
+
}))
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
export function cancelLaneWaiter(context, input) {
|
|
194
|
+
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
195
|
+
const lane = normalizeLane(input.lane);
|
|
196
|
+
const reason = input.reason.trim();
|
|
197
|
+
if (!reason) {
|
|
198
|
+
throw new AppError("validation", "lane waiter cancel requires --reason", 2);
|
|
199
|
+
}
|
|
200
|
+
expireStaleWaiters(context, project.id);
|
|
201
|
+
const now = context.now();
|
|
202
|
+
const waiter = activeLaneWaiterForWorker(context, project.id, lane, input.workerId);
|
|
203
|
+
if (!waiter) {
|
|
204
|
+
return { ok: true, cancelled: false, reason: "no_queued_waiter", lane, worker_id: input.workerId };
|
|
205
|
+
}
|
|
206
|
+
context.db.run(`UPDATE lane_waiters
|
|
207
|
+
SET status = 'cancelled', cancelled_at = ?, reason = ?, updated_at = ?
|
|
208
|
+
WHERE id = ? AND status = 'queued'`, [now, reason, now, waiter.id]);
|
|
209
|
+
appendAudit(context, {
|
|
210
|
+
projectId: project.id,
|
|
211
|
+
eventType: "lane_waiter.cancelled",
|
|
212
|
+
reason,
|
|
213
|
+
payload: { project_id: project.id, lane, worker_id: input.workerId, waiter_id: waiter.id }
|
|
214
|
+
});
|
|
215
|
+
return { ok: true, cancelled: true, waiter: laneWaiterById(context, waiter.id) };
|
|
216
|
+
}
|
|
217
|
+
export function cancelLaneWaitersForWorker(context, input) {
|
|
218
|
+
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
219
|
+
const now = context.now();
|
|
220
|
+
const waiters = context.db.all("SELECT * FROM lane_waiters WHERE project_id = ? AND worker_id = ? AND status = 'queued' ORDER BY id ASC", [project.id, input.workerId]);
|
|
221
|
+
if (waiters.length === 0) {
|
|
222
|
+
return { ok: true, cancelled: 0 };
|
|
223
|
+
}
|
|
224
|
+
context.db.run(`UPDATE lane_waiters
|
|
225
|
+
SET status = 'cancelled', cancelled_at = ?, reason = ?, updated_at = ?
|
|
226
|
+
WHERE project_id = ? AND worker_id = ? AND status = 'queued'`, [now, input.reason, now, project.id, input.workerId]);
|
|
227
|
+
appendAudit(context, {
|
|
228
|
+
projectId: project.id,
|
|
229
|
+
eventType: "lane_waiter.cancelled_for_worker",
|
|
230
|
+
reason: input.reason,
|
|
231
|
+
payload: { project_id: project.id, worker_id: input.workerId, waiter_ids: waiters.map((waiter) => waiter.id) }
|
|
232
|
+
});
|
|
233
|
+
return { ok: true, cancelled: waiters.length, waiter_ids: waiters.map((waiter) => waiter.id) };
|
|
234
|
+
}
|
|
235
|
+
export function expireLaneWaiterById(context, input) {
|
|
236
|
+
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
237
|
+
const now = context.now();
|
|
238
|
+
const result = context.db.run(`UPDATE lane_waiters
|
|
239
|
+
SET status = 'expired', reason = ?, updated_at = ?
|
|
240
|
+
WHERE project_id = ? AND id = ? AND status = 'queued'`, [input.reason, now, project.id, input.waiterId]);
|
|
241
|
+
if (result.changes === 1) {
|
|
242
|
+
appendAudit(context, {
|
|
243
|
+
projectId: project.id,
|
|
244
|
+
eventType: "lane_waiter.expired",
|
|
245
|
+
reason: input.reason,
|
|
246
|
+
payload: { project_id: project.id, waiter_id: input.waiterId }
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return { ok: true, expired: result.changes === 1, waiter_id: input.waiterId };
|
|
250
|
+
}
|
|
251
|
+
export async function waitAcquireLaneLock(context, input) {
|
|
252
|
+
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
253
|
+
const lane = normalizeLane(input.lane);
|
|
254
|
+
requireMatchingLaneWorkspace(context, project.id, lane, input.workspacePath);
|
|
255
|
+
assertWorkerMayAcquireLaneLock(context, project.id, lane, input.workerId);
|
|
256
|
+
const timeoutSeconds = normalizeNonNegativeNumber(input.timeoutSeconds, 0, "timeout");
|
|
257
|
+
const pollIntervalSeconds = normalizePositiveNumber(input.pollIntervalSeconds, defaultPollIntervalSeconds, "poll-interval");
|
|
258
|
+
const ttlSeconds = normalizePositiveNumber(input.ttlSeconds, defaultTtlSeconds, "ttl");
|
|
259
|
+
const startedAt = Date.now();
|
|
260
|
+
const waiterDeadline = timeoutSeconds > 0 ? new Date(startedAt + timeoutSeconds * 1000).toISOString() : null;
|
|
261
|
+
while (true) {
|
|
262
|
+
const result = tryAcquireQueuedLaneLock(context, {
|
|
263
|
+
projectRoot: project.root,
|
|
264
|
+
projectId: project.id,
|
|
265
|
+
lane,
|
|
266
|
+
workerId: input.workerId,
|
|
267
|
+
workspacePath: input.workspacePath,
|
|
268
|
+
ttlSeconds,
|
|
269
|
+
reason: input.reason,
|
|
270
|
+
waiterDeadline,
|
|
271
|
+
invocationStartedAt: new Date(startedAt).toISOString()
|
|
272
|
+
});
|
|
273
|
+
if (result.acquired) {
|
|
274
|
+
return result.payload;
|
|
275
|
+
}
|
|
276
|
+
if (result.payload.status === "expired" && timeoutSeconds > 0 && Date.now() - startedAt >= timeoutSeconds * 1000) {
|
|
277
|
+
return markLaneWaiterTimedOut(context, {
|
|
278
|
+
projectId: project.id,
|
|
279
|
+
lane,
|
|
280
|
+
workerId: input.workerId,
|
|
281
|
+
timeoutSeconds
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
if (result.payload.status === "cancelled" || result.payload.status === "expired") {
|
|
285
|
+
return result.payload;
|
|
286
|
+
}
|
|
287
|
+
if (timeoutSeconds > 0 && Date.now() - startedAt >= timeoutSeconds * 1000) {
|
|
288
|
+
return markLaneWaiterTimedOut(context, {
|
|
289
|
+
projectId: project.id,
|
|
290
|
+
lane,
|
|
291
|
+
workerId: input.workerId,
|
|
292
|
+
timeoutSeconds
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
await delay(pollIntervalSeconds * 1000);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
175
298
|
export function requireLaneLockOwner(context, input) {
|
|
176
299
|
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
177
300
|
const lane = normalizeLane(input.lane);
|
|
@@ -236,12 +359,85 @@ function laneLocksForProject(context, projectId, lane) {
|
|
|
236
359
|
}
|
|
237
360
|
return context.db.all(`SELECT * FROM lane_locks WHERE project_id = ? ORDER BY updated_at DESC, id DESC LIMIT 50`, [projectId]);
|
|
238
361
|
}
|
|
362
|
+
function laneWaitersForProject(context, projectId, lane) {
|
|
363
|
+
if (lane) {
|
|
364
|
+
return context.db.all(`SELECT * FROM lane_waiters
|
|
365
|
+
WHERE project_id = ? AND lane = ?
|
|
366
|
+
ORDER BY
|
|
367
|
+
CASE status WHEN 'queued' THEN 0 ELSE 1 END ASC,
|
|
368
|
+
CASE status WHEN 'queued' THEN queued_at ELSE updated_at END ASC,
|
|
369
|
+
id ASC
|
|
370
|
+
LIMIT 50`, [projectId, normalizeLane(lane)]);
|
|
371
|
+
}
|
|
372
|
+
return context.db.all(`SELECT * FROM lane_waiters
|
|
373
|
+
WHERE project_id = ?
|
|
374
|
+
ORDER BY
|
|
375
|
+
CASE status WHEN 'queued' THEN 0 ELSE 1 END ASC,
|
|
376
|
+
CASE status WHEN 'queued' THEN queued_at ELSE updated_at END ASC,
|
|
377
|
+
id ASC
|
|
378
|
+
LIMIT 100`, [projectId]);
|
|
379
|
+
}
|
|
239
380
|
function activeLaneLock(context, projectId, lane) {
|
|
240
381
|
expireStaleLocks(context, projectId);
|
|
241
382
|
return context.db.get(`SELECT * FROM lane_locks
|
|
242
383
|
WHERE project_id = ? AND lane = ? AND status = 'active'
|
|
243
384
|
ORDER BY updated_at DESC, id DESC LIMIT 1`, [projectId, lane]);
|
|
244
385
|
}
|
|
386
|
+
function activeLaneWaiterForWorker(context, projectId, lane, workerId) {
|
|
387
|
+
return context.db.get(`SELECT * FROM lane_waiters
|
|
388
|
+
WHERE project_id = ? AND lane = ? AND worker_id = ? AND status = 'queued'
|
|
389
|
+
ORDER BY id ASC LIMIT 1`, [projectId, lane, workerId]);
|
|
390
|
+
}
|
|
391
|
+
function latestLaneWaiterForWorker(context, projectId, lane, workerId) {
|
|
392
|
+
return context.db.get(`SELECT * FROM lane_waiters
|
|
393
|
+
WHERE project_id = ? AND lane = ? AND worker_id = ?
|
|
394
|
+
ORDER BY id DESC LIMIT 1`, [projectId, lane, workerId]);
|
|
395
|
+
}
|
|
396
|
+
function firstQueuedLaneWaiter(context, projectId, lane) {
|
|
397
|
+
return context.db.get(`SELECT * FROM lane_waiters
|
|
398
|
+
WHERE project_id = ? AND lane = ? AND status = 'queued'
|
|
399
|
+
ORDER BY queued_at ASC, id ASC LIMIT 1`, [projectId, lane]);
|
|
400
|
+
}
|
|
401
|
+
function laneWaiterById(context, id) {
|
|
402
|
+
return context.db.get("SELECT * FROM lane_waiters WHERE id = ?", [id]);
|
|
403
|
+
}
|
|
404
|
+
function queuedWaiterPosition(context, projectId, lane, waiterId) {
|
|
405
|
+
const waiters = context.db.all(`SELECT id FROM lane_waiters
|
|
406
|
+
WHERE project_id = ? AND lane = ? AND status = 'queued'
|
|
407
|
+
ORDER BY queued_at ASC, id ASC`, [projectId, lane]);
|
|
408
|
+
const index = waiters.findIndex((waiter) => waiter.id === waiterId);
|
|
409
|
+
return index >= 0 ? index + 1 : null;
|
|
410
|
+
}
|
|
411
|
+
function markLaneWaiterTimedOut(context, input) {
|
|
412
|
+
const now = context.now();
|
|
413
|
+
const latest = latestLaneWaiterForWorker(context, input.projectId, input.lane, input.workerId);
|
|
414
|
+
if (latest && ["queued", "expired"].includes(latest.status)) {
|
|
415
|
+
context.db.run(`UPDATE lane_waiters
|
|
416
|
+
SET status = 'timed_out', reason = ?, updated_at = ?
|
|
417
|
+
WHERE id = ? AND status IN ('queued', 'expired')`, ["lane lock wait-acquire timeout", now, latest.id]);
|
|
418
|
+
}
|
|
419
|
+
appendAudit(context, {
|
|
420
|
+
projectId: input.projectId,
|
|
421
|
+
eventType: "lane_waiter.wait_timeout",
|
|
422
|
+
payload: {
|
|
423
|
+
project_id: input.projectId,
|
|
424
|
+
lane: input.lane,
|
|
425
|
+
worker_id: input.workerId,
|
|
426
|
+
timeout_seconds: input.timeoutSeconds,
|
|
427
|
+
waiter_id: latest?.id ?? null
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
return {
|
|
431
|
+
ok: true,
|
|
432
|
+
acquired: false,
|
|
433
|
+
status: "timed_out",
|
|
434
|
+
timed_out: true,
|
|
435
|
+
lane: input.lane,
|
|
436
|
+
worker_id: input.workerId,
|
|
437
|
+
waiter: latestLaneWaiterForWorker(context, input.projectId, input.lane, input.workerId) ?? null,
|
|
438
|
+
lock: activeLaneLock(context, input.projectId, input.lane) ?? null
|
|
439
|
+
};
|
|
440
|
+
}
|
|
245
441
|
function requireOwnedActiveLock(context, projectId, lane, workerId, leaseToken) {
|
|
246
442
|
const lock = activeLaneLock(context, projectId, lane);
|
|
247
443
|
if (!lock) {
|
|
@@ -288,6 +484,142 @@ function expireStaleLocks(context, projectId) {
|
|
|
288
484
|
SET status = 'expired', updated_at = ?
|
|
289
485
|
WHERE project_id = ? AND status = 'active' AND expires_at <= ?`, [now, projectId, now]);
|
|
290
486
|
}
|
|
487
|
+
function expireStaleWaiters(context, projectId) {
|
|
488
|
+
const now = context.now();
|
|
489
|
+
context.db.run(`UPDATE lane_waiters
|
|
490
|
+
SET status = 'expired', updated_at = ?
|
|
491
|
+
WHERE project_id = ? AND status = 'queued' AND expires_at IS NOT NULL AND expires_at <= ?`, [now, projectId, now]);
|
|
492
|
+
}
|
|
493
|
+
function ensureQueuedLaneWaiter(context, input) {
|
|
494
|
+
const existing = activeLaneWaiterForWorker(context, input.projectId, input.lane, input.workerId);
|
|
495
|
+
if (existing) {
|
|
496
|
+
return existing;
|
|
497
|
+
}
|
|
498
|
+
const now = context.now();
|
|
499
|
+
context.db.run(`INSERT INTO lane_waiters
|
|
500
|
+
(project_id, lane, worker_id, status, queued_at, acquired_at, cancelled_at,
|
|
501
|
+
expires_at, reason, metadata_json, created_at, updated_at)
|
|
502
|
+
VALUES (?, ?, ?, 'queued', ?, NULL, NULL, ?, ?, '{}', ?, ?)`, [input.projectId, input.lane, input.workerId, now, input.expiresAt, input.reason, now, now]);
|
|
503
|
+
const waiter = activeLaneWaiterForWorker(context, input.projectId, input.lane, input.workerId);
|
|
504
|
+
if (!waiter) {
|
|
505
|
+
throw new AppError("lane_waiter_create_failed", "Failed to create lane waiter", 1, {
|
|
506
|
+
lane: input.lane,
|
|
507
|
+
worker_id: input.workerId
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
appendAudit(context, {
|
|
511
|
+
projectId: input.projectId,
|
|
512
|
+
eventType: "lane_waiter.queued",
|
|
513
|
+
reason: input.reason,
|
|
514
|
+
payload: { project_id: input.projectId, lane: input.lane, worker_id: input.workerId, waiter_id: waiter.id }
|
|
515
|
+
});
|
|
516
|
+
return waiter;
|
|
517
|
+
}
|
|
518
|
+
function tryAcquireQueuedLaneLock(context, input) {
|
|
519
|
+
expireStaleLocks(context, input.projectId);
|
|
520
|
+
expireStaleWaiters(context, input.projectId);
|
|
521
|
+
context.db.exec("BEGIN IMMEDIATE");
|
|
522
|
+
try {
|
|
523
|
+
const existingLock = activeLaneLock(context, input.projectId, input.lane);
|
|
524
|
+
if (existingLock?.worker_id === input.workerId) {
|
|
525
|
+
context.db.exec("COMMIT");
|
|
526
|
+
return {
|
|
527
|
+
acquired: true,
|
|
528
|
+
payload: {
|
|
529
|
+
ok: true,
|
|
530
|
+
acquired: true,
|
|
531
|
+
status: "acquired",
|
|
532
|
+
lane: input.lane,
|
|
533
|
+
worker_id: input.workerId,
|
|
534
|
+
waiter: null,
|
|
535
|
+
lock: existingLock,
|
|
536
|
+
reused: true,
|
|
537
|
+
position: 0
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
const latest = latestLaneWaiterForWorker(context, input.projectId, input.lane, input.workerId);
|
|
542
|
+
if (latest && ["cancelled", "expired"].includes(latest.status) && latest.updated_at >= input.invocationStartedAt) {
|
|
543
|
+
context.db.exec("COMMIT");
|
|
544
|
+
return {
|
|
545
|
+
acquired: false,
|
|
546
|
+
payload: {
|
|
547
|
+
ok: true,
|
|
548
|
+
acquired: false,
|
|
549
|
+
status: latest.status,
|
|
550
|
+
lane: input.lane,
|
|
551
|
+
worker_id: input.workerId,
|
|
552
|
+
waiter: latest,
|
|
553
|
+
lock: existingLock ?? null,
|
|
554
|
+
position: null
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
const waiter = ensureQueuedLaneWaiter(context, {
|
|
559
|
+
projectId: input.projectId,
|
|
560
|
+
lane: input.lane,
|
|
561
|
+
workerId: input.workerId,
|
|
562
|
+
reason: input.reason,
|
|
563
|
+
expiresAt: input.waiterDeadline
|
|
564
|
+
});
|
|
565
|
+
const head = firstQueuedLaneWaiter(context, input.projectId, input.lane);
|
|
566
|
+
if (!existingLock && head?.id === waiter.id) {
|
|
567
|
+
const lockResult = acquireLaneLock(context, {
|
|
568
|
+
projectRoot: input.projectRoot,
|
|
569
|
+
lane: input.lane,
|
|
570
|
+
workerId: input.workerId,
|
|
571
|
+
workspacePath: input.workspacePath,
|
|
572
|
+
ttlSeconds: input.ttlSeconds,
|
|
573
|
+
reason: input.reason
|
|
574
|
+
});
|
|
575
|
+
const now = context.now();
|
|
576
|
+
context.db.run(`UPDATE lane_waiters
|
|
577
|
+
SET status = 'acquired', acquired_at = ?, updated_at = ?
|
|
578
|
+
WHERE id = ? AND status = 'queued'`, [now, now, waiter.id]);
|
|
579
|
+
const acquiredWaiter = laneWaiterById(context, waiter.id);
|
|
580
|
+
appendAudit(context, {
|
|
581
|
+
projectId: input.projectId,
|
|
582
|
+
eventType: "lane_waiter.acquired",
|
|
583
|
+
reason: input.reason,
|
|
584
|
+
payload: { project_id: input.projectId, lane: input.lane, worker_id: input.workerId, waiter_id: waiter.id }
|
|
585
|
+
});
|
|
586
|
+
context.db.exec("COMMIT");
|
|
587
|
+
return {
|
|
588
|
+
acquired: true,
|
|
589
|
+
payload: {
|
|
590
|
+
ok: true,
|
|
591
|
+
acquired: true,
|
|
592
|
+
status: "acquired",
|
|
593
|
+
lane: input.lane,
|
|
594
|
+
worker_id: input.workerId,
|
|
595
|
+
waiter: acquiredWaiter,
|
|
596
|
+
lock: lockResult.lock,
|
|
597
|
+
reused: lockResult.reused,
|
|
598
|
+
position: 0
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
const position = queuedWaiterPosition(context, input.projectId, input.lane, waiter.id);
|
|
603
|
+
context.db.exec("COMMIT");
|
|
604
|
+
return {
|
|
605
|
+
acquired: false,
|
|
606
|
+
payload: {
|
|
607
|
+
ok: true,
|
|
608
|
+
acquired: false,
|
|
609
|
+
status: "queued",
|
|
610
|
+
lane: input.lane,
|
|
611
|
+
worker_id: input.workerId,
|
|
612
|
+
waiter,
|
|
613
|
+
lock: existingLock ?? null,
|
|
614
|
+
position
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
catch (error) {
|
|
619
|
+
context.db.exec("ROLLBACK");
|
|
620
|
+
throw error;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
291
623
|
function resolveExistingPath(value) {
|
|
292
624
|
const absolute = path.resolve(value);
|
|
293
625
|
if (!fs.existsSync(absolute)) {
|