pixivflow 3.2.0 → 3.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.
Files changed (58) hide show
  1. package/README.md +26 -1
  2. package/dist/commands/SchedulerCommand.js +31 -51
  3. package/dist/commands/scheduler-runtime.js +47 -7
  4. package/dist/config/defaults.d.ts +2 -0
  5. package/dist/config/defaults.js +6 -0
  6. package/dist/config/types.d.ts +20 -0
  7. package/dist/config/validation.js +11 -0
  8. package/dist/delivery/DeliveryDispatcher.d.ts +14 -0
  9. package/dist/delivery/DeliveryDispatcher.js +14 -0
  10. package/dist/delivery/EventCallbackDelivery.d.ts +54 -0
  11. package/dist/delivery/EventCallbackDelivery.js +89 -0
  12. package/dist/delivery/OutboxWorker.d.ts +11 -0
  13. package/dist/delivery/OutboxWorker.js +51 -1
  14. package/dist/package.json +1 -1
  15. package/dist/scheduler/CandidateSearchParams.d.ts +92 -0
  16. package/dist/scheduler/CandidateSearchParams.js +103 -0
  17. package/dist/scheduler/JobCancellation.d.ts +36 -0
  18. package/dist/scheduler/JobCancellation.js +73 -0
  19. package/dist/scheduler/JobEventStream.d.ts +94 -0
  20. package/dist/scheduler/JobEventStream.js +251 -0
  21. package/dist/scheduler/JobFacade.d.ts +305 -0
  22. package/dist/scheduler/JobFacade.js +670 -0
  23. package/dist/scheduler/JobProjection.d.ts +67 -0
  24. package/dist/scheduler/JobProjection.js +32 -0
  25. package/dist/scheduler/JobView.d.ts +34 -0
  26. package/dist/scheduler/JobView.js +82 -0
  27. package/dist/scheduler/ManualJobAdmission.d.ts +134 -0
  28. package/dist/scheduler/ManualJobAdmission.js +316 -0
  29. package/dist/scheduler/ManualJobService.d.ts +57 -0
  30. package/dist/scheduler/ManualJobService.js +92 -0
  31. package/dist/scheduler/ManualRefetchAdapter.d.ts +26 -0
  32. package/dist/scheduler/ManualRefetchAdapter.js +41 -0
  33. package/dist/scheduler/MultiScheduleManager.d.ts +17 -0
  34. package/dist/scheduler/MultiScheduleManager.js +68 -6
  35. package/dist/scheduler/ProtocolErrors.d.ts +68 -0
  36. package/dist/scheduler/ProtocolErrors.js +94 -0
  37. package/dist/scheduler/ScheduleTriggerServer.d.ts +56 -7
  38. package/dist/scheduler/ScheduleTriggerServer.js +166 -1
  39. package/dist/scheduler/SlotBusinessStatus.d.ts +5 -1
  40. package/dist/scheduler/SlotBusinessStatus.js +6 -0
  41. package/dist/scheduler/SlotCoordinator.d.ts +49 -5
  42. package/dist/scheduler/SlotCoordinator.js +89 -22
  43. package/dist/scheduler/StallSweep.d.ts +65 -0
  44. package/dist/scheduler/StallSweep.js +105 -0
  45. package/dist/scheduler/TargetOutcome.d.ts +39 -1
  46. package/dist/scheduler/TargetOutcome.js +51 -1
  47. package/dist/scheduler/ledger-time.d.ts +23 -0
  48. package/dist/scheduler/ledger-time.js +37 -0
  49. package/dist/storage/DatabaseMigration.js +48 -0
  50. package/dist/storage/repositories/DeliveryRepository.d.ts +6 -0
  51. package/dist/storage/repositories/DeliveryRepository.js +13 -0
  52. package/dist/storage/repositories/OutboxRepository.d.ts +73 -1
  53. package/dist/storage/repositories/OutboxRepository.js +142 -0
  54. package/dist/storage/repositories/SlotRepository.d.ts +34 -0
  55. package/dist/storage/repositories/SlotRepository.js +61 -2
  56. package/dist/version.js +1 -1
  57. package/dist/webui/package.json +1 -1
  58. package/package.json +1 -1
@@ -38,6 +38,11 @@ export interface SlotRecord {
38
38
  recoveryRequestId: string | null;
39
39
  /** Recovery policy preset ('normal'|'relaxed'); null for non-recovery slots. */
40
40
  recoveryMode: string | null;
41
+ /**
42
+ * Occurrence-scoped retrieval view of a generic `candidate_search` job (JSON,
43
+ * §6); null means "run the plan exactly as configured".
44
+ */
45
+ paramsJson: string | null;
41
46
  }
42
47
  export interface SlotItemRecord {
43
48
  id: number;
@@ -83,6 +88,16 @@ export interface SlotItemRecord {
83
88
  export declare class SlotRepository extends BaseRepository {
84
89
  /** Exact manual request/target lookup for authenticated convergence checks. */
85
90
  findManualSlot(requestId: string, targetId: string): SlotRecord | null;
91
+ /**
92
+ * Manual request lookup WITHOUT a target id — the generic job surface resolves
93
+ * a job from its consumer-supplied idempotency key alone.
94
+ *
95
+ * A non-empty `manual_request_id` is unique per slot (partial unique index
96
+ * `idx_slots_manual_request`), so the first row IS the job; the ordering only
97
+ * makes the answer deterministic on a ledger that still holds historical
98
+ * duplicates.
99
+ */
100
+ findManualSlotByKey(requestId: string): SlotRecord | null;
86
101
  /** Exact manual RECOVERY request/target lookup (§manual-recovery). */
87
102
  findRecoverySlot(requestId: string, targetId: string): SlotRecord | null;
88
103
  /**
@@ -109,6 +124,8 @@ export declare class SlotRepository extends BaseRepository {
109
124
  recoveryRequestId?: string | null;
110
125
  /** Manual recovery policy preset ('normal' | 'relaxed'). */
111
126
  recoveryMode?: string | null;
127
+ /** Generic-job retrieval view (§6), stored as JSON; null = as configured. */
128
+ paramsJson?: string | null;
112
129
  }): {
113
130
  slot: SlotRecord;
114
131
  created: boolean;
@@ -220,6 +237,23 @@ export declare class SlotRepository extends BaseRepository {
220
237
  * a read-then-clear race.
221
238
  */
222
239
  recoverableSlots(now?: number): SlotRecord[];
240
+ /**
241
+ * Bounded liveness sweep candidates (§liveness): slots that are still
242
+ * `pending` (nothing ever claimed them) long after they were recorded. A live
243
+ * lease is never a candidate — a claimed slot is not stuck.
244
+ *
245
+ * `createdBefore` is a SQLite UTC datetime; the comparison stays in SQL so a
246
+ * large ledger never has to be loaded into memory to be filtered.
247
+ */
248
+ agedPendingSlots(createdBefore: string, now: number, limit: number): SlotRecord[];
249
+ /**
250
+ * Bounded liveness sweep candidates (§liveness): slots left `running` whose
251
+ * owner stopped heartbeating. The lease must be expired or absent (a live
252
+ * lease means a live worker, however long the work takes), and progress must
253
+ * be older than the stall budget — falling back to `started_at`, then
254
+ * `created_at`, for rows from before heartbeats were recorded.
255
+ */
256
+ stalledRunningSlots(progressBefore: string, now: number, limit: number): SlotRecord[];
223
257
  /**
224
258
  * Non-terminal Slots (pending/running): work this ledger still owes, whether
225
259
  * or not a live worker currently holds their lease.
@@ -17,6 +17,21 @@ class SlotRepository extends BaseRepository_1.BaseRepository {
17
17
  const rows = this.db.prepare(`SELECT * FROM schedule_slots WHERE manual_request_id = ?`).all(requestId);
18
18
  return rows.map((row) => this.toSlot(row)).find((slot) => slot.targetIds.includes(targetId)) ?? null;
19
19
  }
20
+ /**
21
+ * Manual request lookup WITHOUT a target id — the generic job surface resolves
22
+ * a job from its consumer-supplied idempotency key alone.
23
+ *
24
+ * A non-empty `manual_request_id` is unique per slot (partial unique index
25
+ * `idx_slots_manual_request`), so the first row IS the job; the ordering only
26
+ * makes the answer deterministic on a ledger that still holds historical
27
+ * duplicates.
28
+ */
29
+ findManualSlotByKey(requestId) {
30
+ const row = this.db
31
+ .prepare(`SELECT * FROM schedule_slots WHERE manual_request_id = ? ORDER BY created_at, id LIMIT 1`)
32
+ .get(requestId);
33
+ return row ? this.toSlot(row) : null;
34
+ }
20
35
  /** Exact manual RECOVERY request/target lookup (§manual-recovery). */
21
36
  findRecoverySlot(requestId, targetId) {
22
37
  const rows = this.db.prepare(`SELECT * FROM schedule_slots WHERE recovery_request_id = ?`).all(requestId);
@@ -32,11 +47,11 @@ class SlotRepository extends BaseRepository_1.BaseRepository {
32
47
  const insert = this.db.prepare(`INSERT INTO schedule_slots
33
48
  (id, schedule_id, occurrence_at, occurrence_date, occurrence_label, timezone, target_ids,
34
49
  status, trigger_source, slot_date, slot_name, manual_request_id, correlation_id,
35
- recovery_request_id, recovery_mode)
50
+ recovery_request_id, recovery_mode, params_json)
36
51
  VALUES
37
52
  (@id, @scheduleId, @occurrenceAt, @occurrenceDate, @occurrenceLabel, @timezone, @targetIds,
38
53
  'pending', @triggerSource, @slotDate, @slotName, @manualRequestId, @correlationId,
39
- @recoveryRequestId, @recoveryMode)
54
+ @recoveryRequestId, @recoveryMode, @paramsJson)
40
55
  ON CONFLICT(id) DO NOTHING`);
41
56
  const info = insert.run({
42
57
  id,
@@ -53,6 +68,7 @@ class SlotRepository extends BaseRepository_1.BaseRepository {
53
68
  correlationId: data.correlationId ?? null,
54
69
  recoveryRequestId: data.recoveryRequestId ?? null,
55
70
  recoveryMode: data.recoveryMode ?? null,
71
+ paramsJson: data.paramsJson ?? null,
56
72
  });
57
73
  const created = info.changes > 0;
58
74
  return { slot: this.getSlot(id), created };
@@ -364,6 +380,48 @@ class SlotRepository extends BaseRepository_1.BaseRepository {
364
380
  .all({ now });
365
381
  return rows.map((r) => this.toSlot(r));
366
382
  }
383
+ /**
384
+ * Bounded liveness sweep candidates (§liveness): slots that are still
385
+ * `pending` (nothing ever claimed them) long after they were recorded. A live
386
+ * lease is never a candidate — a claimed slot is not stuck.
387
+ *
388
+ * `createdBefore` is a SQLite UTC datetime; the comparison stays in SQL so a
389
+ * large ledger never has to be loaded into memory to be filtered.
390
+ */
391
+ agedPendingSlots(createdBefore, now, limit) {
392
+ const rows = this.db
393
+ .prepare(`SELECT * FROM schedule_slots
394
+ WHERE status = 'pending'
395
+ AND (lease_until IS NULL OR lease_until <= @now)
396
+ AND created_at IS NOT NULL
397
+ AND created_at <= @createdBefore
398
+ ORDER BY created_at ASC
399
+ LIMIT @limit`)
400
+ .all({ createdBefore, now, limit });
401
+ return rows.map((r) => this.toSlot(r));
402
+ }
403
+ /**
404
+ * Bounded liveness sweep candidates (§liveness): slots left `running` whose
405
+ * owner stopped heartbeating. The lease must be expired or absent (a live
406
+ * lease means a live worker, however long the work takes), and progress must
407
+ * be older than the stall budget — falling back to `started_at`, then
408
+ * `created_at`, for rows from before heartbeats were recorded.
409
+ */
410
+ stalledRunningSlots(progressBefore, now, limit) {
411
+ const rows = this.db
412
+ .prepare(`SELECT * FROM schedule_slots
413
+ WHERE status = 'running'
414
+ AND (lease_until IS NULL OR lease_until <= @now)
415
+ AND COALESCE(
416
+ CASE WHEN heartbeat_at IS NOT NULL THEN datetime(heartbeat_at / 1000, 'unixepoch') END,
417
+ started_at,
418
+ created_at
419
+ ) <= @progressBefore
420
+ ORDER BY COALESCE(started_at, created_at) ASC
421
+ LIMIT @limit`)
422
+ .all({ progressBefore, now, limit });
423
+ return rows.map((r) => this.toSlot(r));
424
+ }
367
425
  /**
368
426
  * Non-terminal Slots (pending/running): work this ledger still owes, whether
369
427
  * or not a live worker currently holds their lease.
@@ -442,6 +500,7 @@ class SlotRepository extends BaseRepository_1.BaseRepository {
442
500
  correlationId: row.correlation_id ?? null,
443
501
  recoveryRequestId: row.recovery_request_id ?? null,
444
502
  recoveryMode: row.recovery_mode ?? null,
503
+ paramsJson: row.params_json ?? null,
445
504
  };
446
505
  }
447
506
  toItem(row) {
package/dist/version.js CHANGED
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BUILD = void 0;
4
4
  // GENERATED by scripts/write-version.js — do not edit manually.
5
- exports.BUILD = { version: '3.2.0', commit: 'c195b909063c' };
5
+ exports.BUILD = { version: '3.4.0', commit: '5d231179a9b3' };
6
6
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "type": "commonjs",
3
3
  "name": "pixivflow-webui-backend",
4
- "version": "3.2.0",
4
+ "version": "3.4.0",
5
5
  "description": "PixivFlow WebUI Backend - CommonJS module"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixivflow",
3
- "version": "3.2.0",
3
+ "version": "3.4.0",
4
4
  "description": "🎨 Pixiv 下载、筛选与自动收集工具 - 批量下载插画和小说、按标签/热度/日期筛选、定时任务与可靠 HTTP 交付 | Pixiv downloader and automation toolkit with filtering, scheduling and reliable HTTP delivery",
5
5
  "repository": {
6
6
  "type": "git",