pixivflow 2.19.2 → 2.19.4

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 (32) hide show
  1. package/dist/commands/scheduler-runtime.js +16 -17
  2. package/dist/delivery/DeliveryAck.d.ts +14 -0
  3. package/dist/delivery/DeliveryAck.js +18 -0
  4. package/dist/delivery/DeliveryLedgerPort.d.ts +14 -0
  5. package/dist/delivery/DeliveryLedgerPort.js +43 -0
  6. package/dist/delivery/OutboxWorker.js +13 -0
  7. package/dist/delivery/settleDeliveryTerminal.d.ts +25 -0
  8. package/dist/delivery/settleDeliveryTerminal.js +40 -0
  9. package/dist/download/DownloadManager.d.ts +5 -0
  10. package/dist/download/DownloadManager.js +12 -2
  11. package/dist/download/handlers/IllustrationTargetHandler.d.ts +27 -1
  12. package/dist/download/handlers/IllustrationTargetHandler.js +105 -7
  13. package/dist/download/handlers/NovelTargetHandler.d.ts +27 -1
  14. package/dist/download/handlers/NovelTargetHandler.js +107 -3
  15. package/dist/package.json +1 -1
  16. package/dist/pixiv-client/TargetSearchRunner.js +24 -8
  17. package/dist/scheduler/SlotCoordinator.d.ts +74 -1
  18. package/dist/scheduler/SlotCoordinator.js +108 -1
  19. package/dist/scheduler/WorkIdentity.d.ts +49 -0
  20. package/dist/scheduler/WorkIdentity.js +31 -0
  21. package/dist/storage/repositories/DeliveryRepository.d.ts +9 -0
  22. package/dist/storage/repositories/DeliveryRepository.js +16 -0
  23. package/dist/storage/repositories/OutboxRepository.d.ts +9 -0
  24. package/dist/storage/repositories/OutboxRepository.js +17 -0
  25. package/dist/storage/repositories/SlotRepository.d.ts +27 -0
  26. package/dist/storage/repositories/SlotRepository.js +45 -0
  27. package/dist/topic/TopicPipeline.js +4 -0
  28. package/dist/utils/errors.d.ts +9 -0
  29. package/dist/utils/errors.js +14 -1
  30. package/dist/version.js +1 -1
  31. package/dist/webui/package.json +1 -1
  32. package/package.json +1 -1
@@ -92,6 +92,33 @@ export declare class SlotRepository extends BaseRepository {
92
92
  * candidate replacement is a separate explicit operator action (clearCellWork).
93
93
  */
94
94
  lockCellWork(slotId: string, targetId: string, workId: string, workType: string): SlotItemRecord;
95
+ /**
96
+ * Compare-and-set the cell's work binding, with real concurrency semantics.
97
+ *
98
+ * `lockCellWork` above only stops *this* process from overwriting an existing
99
+ * id (COALESCE); it cannot elect a winner between two callers that both read
100
+ * "no binding". This one can: the write is guarded by `work_id IS NULL OR
101
+ * work_id = @workId`, so if two workers select A and B at the same instant
102
+ * exactly one UPDATE lands. The returned record is authoritative — a losing
103
+ * caller MUST continue with `cell.workId`, never with its own candidate, or
104
+ * the cell would process a work it does not own.
105
+ */
106
+ tryLockCellWork(slotId: string, targetId: string, workId: string, workType: string): {
107
+ cell: SlotItemRecord;
108
+ won: boolean;
109
+ };
110
+ /**
111
+ * Roll back a PROVISIONAL binding taken for a work that produced no local
112
+ * artifact (a candidate that failed while the cell was still unbound).
113
+ *
114
+ * Deliberately narrow: it only clears the row while it still holds exactly
115
+ * `workId` AND is still `selected`. Once the cell moved on (an artifact was
116
+ * persisted -> delivery_pending/submitted/..., or another writer rebound it)
117
+ * the release is refused, so it can never erase a committed work identity.
118
+ * Re-selecting is only ever allowed while nothing was committed — that is what
119
+ * keeps (slotId,targetId) -> workId stable.
120
+ */
121
+ releaseCellWork(slotId: string, targetId: string, workId: string): boolean;
95
122
  /** Explicit operator action: forget the locked work so a re-run picks another candidate. */
96
123
  clearCellWork(slotId: string, targetId: string): void;
97
124
  setCellStatus(slotId: string, targetId: string, status: CellStatus, error?: string): void;
@@ -137,6 +137,51 @@ class SlotRepository extends BaseRepository_1.BaseRepository {
137
137
  .run({ slotId, targetId, workId, workType });
138
138
  return this.getCell(slotId, targetId);
139
139
  }
140
+ /**
141
+ * Compare-and-set the cell's work binding, with real concurrency semantics.
142
+ *
143
+ * `lockCellWork` above only stops *this* process from overwriting an existing
144
+ * id (COALESCE); it cannot elect a winner between two callers that both read
145
+ * "no binding". This one can: the write is guarded by `work_id IS NULL OR
146
+ * work_id = @workId`, so if two workers select A and B at the same instant
147
+ * exactly one UPDATE lands. The returned record is authoritative — a losing
148
+ * caller MUST continue with `cell.workId`, never with its own candidate, or
149
+ * the cell would process a work it does not own.
150
+ */
151
+ tryLockCellWork(slotId, targetId, workId, workType) {
152
+ this.db
153
+ .prepare(`UPDATE schedule_slot_items
154
+ SET work_id = @workId,
155
+ work_type = CASE WHEN work_id IS NULL THEN @workType ELSE work_type END,
156
+ status = CASE WHEN status = 'pending' THEN 'selected' ELSE status END,
157
+ attempt_count = CASE WHEN work_id IS NULL THEN attempt_count + 1 ELSE attempt_count END,
158
+ updated_at = CURRENT_TIMESTAMP
159
+ WHERE slot_id = @slotId AND target_id = @targetId
160
+ AND (work_id IS NULL OR work_id = @workId)`)
161
+ .run({ slotId, targetId, workId, workType });
162
+ const cell = this.getCell(slotId, targetId);
163
+ return { cell, won: cell.workId === workId };
164
+ }
165
+ /**
166
+ * Roll back a PROVISIONAL binding taken for a work that produced no local
167
+ * artifact (a candidate that failed while the cell was still unbound).
168
+ *
169
+ * Deliberately narrow: it only clears the row while it still holds exactly
170
+ * `workId` AND is still `selected`. Once the cell moved on (an artifact was
171
+ * persisted -> delivery_pending/submitted/..., or another writer rebound it)
172
+ * the release is refused, so it can never erase a committed work identity.
173
+ * Re-selecting is only ever allowed while nothing was committed — that is what
174
+ * keeps (slotId,targetId) -> workId stable.
175
+ */
176
+ releaseCellWork(slotId, targetId, workId) {
177
+ const info = this.db
178
+ .prepare(`UPDATE schedule_slot_items
179
+ SET work_id = NULL, status = 'pending', updated_at = CURRENT_TIMESTAMP
180
+ WHERE slot_id = @slotId AND target_id = @targetId
181
+ AND work_id = @workId AND status = 'selected'`)
182
+ .run({ slotId, targetId, workId });
183
+ return info.changes > 0;
184
+ }
140
185
  /** Explicit operator action: forget the locked work so a re-run picks another candidate. */
141
186
  clearCellWork(slotId, targetId) {
142
187
  this.db
@@ -123,6 +123,10 @@ class TopicPipeline {
123
123
  // A cancellation must not be degraded into "no results for this tag": that
124
124
  // would silently continue the cancelled run across the remaining tags.
125
125
  (0, errors_1.rethrowIfCancelled)(error, this.signal);
126
+ // A broken pager contract must not degrade either: reporting "no works
127
+ // today" would hide the failure and let the run claim success.
128
+ if (error instanceof errors_1.PaginationError)
129
+ throw error;
126
130
  logger_1.logger.warn('[TopicCollector] search failed tag=' + tag + ' type=' + contentType + ': ' + (error instanceof Error ? error.message : String(error)));
127
131
  return [];
128
132
  }
@@ -33,6 +33,15 @@ export declare class NetworkError extends PixivFlowError {
33
33
  waitTime?: number;
34
34
  });
35
35
  }
36
+ /**
37
+ * The pagination adapter returned a cursor it had already handed back (the same
38
+ * `next` twice, or a cycle). Re-requesting would repeat the previous page
39
+ * forever, which is exactly the Attempt 2 fallback-day failure, so the pager
40
+ * fails deterministically instead of spinning.
41
+ */
42
+ export declare class PaginationError extends PixivFlowError {
43
+ constructor(message: string, cause?: Error);
44
+ }
36
45
  export declare class DownloadError extends PixivFlowError {
37
46
  readonly itemId?: number | undefined;
38
47
  readonly itemType?: "illustration" | "novel" | undefined;
@@ -4,7 +4,7 @@
4
4
  * Provides consistent error types and handling patterns across the application
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.ErrorRecoveryStrategy = exports.HelpRequest = exports.VersionRequest = exports.OperationCancelledError = exports.DatabaseError = exports.PendingDeliveryError = exports.DownloadError = exports.NetworkError = exports.AuthenticationError = exports.ConfigError = exports.PixivFlowError = exports.PixivTimeoutError = exports.PixivRateLimitError = exports.PixivNotFoundError = exports.PixivNetworkError = exports.PixivHttpError = exports.PixivError = void 0;
7
+ exports.ErrorRecoveryStrategy = exports.HelpRequest = exports.VersionRequest = exports.OperationCancelledError = exports.DatabaseError = exports.PendingDeliveryError = exports.DownloadError = exports.PaginationError = exports.NetworkError = exports.AuthenticationError = exports.ConfigError = exports.PixivFlowError = exports.PixivTimeoutError = exports.PixivRateLimitError = exports.PixivNotFoundError = exports.PixivNetworkError = exports.PixivHttpError = exports.PixivError = void 0;
8
8
  exports.isPixivKitError = isPixivKitError;
9
9
  exports.toNetworkError = toNetworkError;
10
10
  exports.isOperationCancelled = isOperationCancelled;
@@ -92,6 +92,19 @@ class NetworkError extends PixivFlowError {
92
92
  }
93
93
  }
94
94
  exports.NetworkError = NetworkError;
95
+ /**
96
+ * The pagination adapter returned a cursor it had already handed back (the same
97
+ * `next` twice, or a cycle). Re-requesting would repeat the previous page
98
+ * forever, which is exactly the Attempt 2 fallback-day failure, so the pager
99
+ * fails deterministically instead of spinning.
100
+ */
101
+ class PaginationError extends PixivFlowError {
102
+ constructor(message, cause) {
103
+ super(message, 'PAGINATION_ERROR', undefined, cause);
104
+ this.name = 'PaginationError';
105
+ }
106
+ }
107
+ exports.PaginationError = PaginationError;
95
108
  class DownloadError extends PixivFlowError {
96
109
  itemId;
97
110
  itemType;
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: '2.19.2', commit: '4b6938041a10' };
5
+ exports.BUILD = { version: '2.19.4', commit: 'f331cd4bb164' };
6
6
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "type": "commonjs",
3
3
  "name": "pixivflow-webui-backend",
4
- "version": "2.19.2",
4
+ "version": "2.19.4",
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": "2.19.2",
3
+ "version": "2.19.4",
4
4
  "description": "🎨 智能的 Pixiv 自动化下载工具 - 支持批量下载插画和小说、定时任务、Docker部署 | Intelligent Pixiv Automation Downloader with batch download, scheduler, and Docker support",
5
5
  "repository": {
6
6
  "type": "git",