enhanced-printer 1.0.7 → 1.0.9

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/README.md CHANGED
@@ -59,6 +59,7 @@ printPDF();
59
59
  | `getJobInfo()` | Get info of a specific job by custom job name |
60
60
  | `getJobs()` | Get all print jobs for a printer |
61
61
  | `cancelJob()` | Cancel a print job by custom job name |
62
+ | `watchJob()` | Poll a job until it completes, errors, or times out |
62
63
 
63
64
  ## API Reference
64
65
 
@@ -210,6 +211,72 @@ console.log('Cancelled:', cancelled);
210
211
 
211
212
  ---
212
213
 
214
+ ### `watchJob(customJobName: string, printerName?: string, options?: WatchJobOptions): Promise<WatchJobResult>`
215
+
216
+ Polls a print job at regular intervals until it completes, enters an error state, is aborted, or times out.
217
+
218
+ > **Why use this?** Windows removes completed jobs from the print queue immediately. Without `watchJob`, polling `getJobInfo` after completion returns `null`, which is indistinguishable from an error. `watchJob` tracks the last known status and correctly resolves a vanished job as `COMPLETED`.
219
+
220
+ **Options:**
221
+
222
+ ```typescript
223
+ interface WatchJobOptions {
224
+ pollInterval?: number; // ms between polls (default: 500)
225
+ timeout?: number; // max wait ms — 0 or omit = no timeout (default: 0)
226
+ onStatusChange?: (status: string[]) => void; // called on every status change
227
+ signal?: AbortSignal; // AbortController signal for manual stop
228
+ }
229
+ ```
230
+
231
+ **Returns:**
232
+
233
+ ```typescript
234
+ interface WatchJobResult {
235
+ finalStatus: string[]; // e.g. ["COMPLETED"], ["ERROR"], ["ABORTED"]
236
+ completed: boolean; // true = success, false = error / timed out / aborted
237
+ }
238
+ ```
239
+
240
+ **Example — basic watch with timeout:**
241
+
242
+ ```javascript
243
+ const { finalStatus, completed } = await watchJob('Invoice_12345', undefined, {
244
+ timeout: 30000, // stop after 30 seconds
245
+ pollInterval: 500,
246
+ onStatusChange: (status) => console.log('Status:', status)
247
+ });
248
+
249
+ console.log('Completed:', completed); // true
250
+ console.log('Final status:', finalStatus); // ["COMPLETED"]
251
+ ```
252
+
253
+ **Example — watch indefinitely (no timeout):**
254
+
255
+ ```javascript
256
+ // timeout: 0 (default) = never times out, watches until job finishes or errors
257
+ const { finalStatus, completed } = await watchJob('Invoice_12345');
258
+ ```
259
+
260
+ **Example — manual stop via AbortController:**
261
+
262
+ ```javascript
263
+ const controller = new AbortController();
264
+
265
+ // Start watching (doesn't block — run via Promise if needed)
266
+ const watchPromise = watchJob('Invoice_12345', undefined, {
267
+ signal: controller.signal,
268
+ onStatusChange: (status) => console.log('Status:', status)
269
+ });
270
+
271
+ // Stop watching manually (e.g. user cancelled, component unmounted, etc.)
272
+ controller.abort();
273
+
274
+ const { finalStatus } = await watchPromise;
275
+ console.log(finalStatus); // ["ABORTED"]
276
+ ```
277
+
278
+ ---
279
+
213
280
  ## Usage Examples
214
281
 
215
282
  ### Using Printer Defaults
package/lib/index.d.ts CHANGED
@@ -10,5 +10,6 @@
10
10
  * - Automatic use of printer defaults when settings not specified
11
11
  * - Query job status and printer information
12
12
  */
13
- export { print, getPrinters, getDefaultPrinter, getJobInfo, getJobs, cancelJob } from './print-manager';
13
+ export { print, getPrinters, getDefaultPrinter, getJobInfo, getJobs, cancelJob, watchJob } from './print-manager';
14
14
  export { PrintOptions, PrintResult, PrinterInfo, JobInfo } from './types';
15
+ export { WatchJobOptions, WatchJobResult } from './job-tracker';
package/lib/index.js CHANGED
@@ -12,7 +12,7 @@
12
12
  * - Query job status and printer information
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.cancelJob = exports.getJobs = exports.getJobInfo = exports.getDefaultPrinter = exports.getPrinters = exports.print = void 0;
15
+ exports.watchJob = exports.cancelJob = exports.getJobs = exports.getJobInfo = exports.getDefaultPrinter = exports.getPrinters = exports.print = void 0;
16
16
  var print_manager_1 = require("./print-manager");
17
17
  Object.defineProperty(exports, "print", { enumerable: true, get: function () { return print_manager_1.print; } });
18
18
  Object.defineProperty(exports, "getPrinters", { enumerable: true, get: function () { return print_manager_1.getPrinters; } });
@@ -20,3 +20,4 @@ Object.defineProperty(exports, "getDefaultPrinter", { enumerable: true, get: fun
20
20
  Object.defineProperty(exports, "getJobInfo", { enumerable: true, get: function () { return print_manager_1.getJobInfo; } });
21
21
  Object.defineProperty(exports, "getJobs", { enumerable: true, get: function () { return print_manager_1.getJobs; } });
22
22
  Object.defineProperty(exports, "cancelJob", { enumerable: true, get: function () { return print_manager_1.cancelJob; } });
23
+ Object.defineProperty(exports, "watchJob", { enumerable: true, get: function () { return print_manager_1.watchJob; } });
@@ -12,6 +12,48 @@ export declare function getJobInfoBySystemId(printerName: string, jobId: number)
12
12
  * Resolves the custom job name to the system job ID, then queries the spooler
13
13
  */
14
14
  export declare function getJobInfo(customJobName: string, printerName?: string): Promise<JobInfo | null>;
15
+ export interface WatchJobResult {
16
+ /** Final resolved status of the job */
17
+ finalStatus: string[];
18
+ /** True if the job completed successfully, false if it errored or timed out */
19
+ completed: boolean;
20
+ }
21
+ export interface WatchJobOptions {
22
+ /** Milliseconds between each status poll (default: 500) */
23
+ pollInterval?: number;
24
+ /**
25
+ * Maximum time to wait in milliseconds before giving up.
26
+ * Set to 0 or omit to watch indefinitely (no timeout).
27
+ * Default: 0 (no timeout)
28
+ */
29
+ timeout?: number;
30
+ /** Called every time the job status changes */
31
+ onStatusChange?: (status: string[]) => void;
32
+ /**
33
+ * An AbortSignal to stop watching manually.
34
+ * Call controller.abort() on the corresponding AbortController to stop.
35
+ * @example
36
+ * const controller = new AbortController();
37
+ * watchJob('MyJob', undefined, { signal: controller.signal });
38
+ * // later...
39
+ * controller.abort(); // stops watching
40
+ */
41
+ signal?: AbortSignal;
42
+ }
43
+ /**
44
+ * Polls a print job until it finishes, is aborted, times out, or enters an error state.
45
+ *
46
+ * Handles the Windows spooler race condition where completed jobs are
47
+ * immediately removed from the queue (making them indistinguishable from
48
+ * missing jobs). If the job disappears and the last known status was not
49
+ * an error, it is treated as successfully COMPLETED.
50
+ *
51
+ * @example No timeout, manual stop via AbortController:
52
+ * const controller = new AbortController();
53
+ * const result = await watchJob('MyJob', undefined, { signal: controller.signal });
54
+ * // elsewhere: controller.abort();
55
+ */
56
+ export declare function watchJob(customJobName: string, printerName?: string, options?: WatchJobOptions): Promise<WatchJobResult>;
15
57
  /**
16
58
  * Gets all print jobs for a printer with enhanced details
17
59
  */
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getJobIdByName = getJobIdByName;
4
4
  exports.getJobInfoBySystemId = getJobInfoBySystemId;
5
5
  exports.getJobInfo = getJobInfo;
6
+ exports.watchJob = watchJob;
6
7
  exports.getAllJobs = getAllJobs;
7
8
  const child_process_1 = require("child_process");
8
9
  const job_name_mapper_1 = require("./job-name-mapper");
@@ -110,6 +111,70 @@ async function getJobInfo(customJobName, printerName) {
110
111
  }
111
112
  return getJobInfoBySystemId(mapping.printerName, mapping.jobId);
112
113
  }
114
+ /** Status flags that indicate a job has stopped due to a problem */
115
+ const ERROR_STATUSES = ['ERROR', 'OFFLINE', 'PAPER_OUT', 'USER_INTERVENTION', 'DELETED', 'DELETING'];
116
+ /** Status flags that indicate a job has finished successfully */
117
+ const DONE_STATUSES = ['PRINTED', 'COMPLETE'];
118
+ /**
119
+ * Polls a print job until it finishes, is aborted, times out, or enters an error state.
120
+ *
121
+ * Handles the Windows spooler race condition where completed jobs are
122
+ * immediately removed from the queue (making them indistinguishable from
123
+ * missing jobs). If the job disappears and the last known status was not
124
+ * an error, it is treated as successfully COMPLETED.
125
+ *
126
+ * @example No timeout, manual stop via AbortController:
127
+ * const controller = new AbortController();
128
+ * const result = await watchJob('MyJob', undefined, { signal: controller.signal });
129
+ * // elsewhere: controller.abort();
130
+ */
131
+ async function watchJob(customJobName, printerName, options = {}) {
132
+ const { pollInterval = 500, timeout = 0, onStatusChange, signal } = options;
133
+ const hasTimeout = timeout > 0;
134
+ const deadline = hasTimeout ? Date.now() + timeout : Infinity;
135
+ let lastKnownStatus = ['INQUEUE'];
136
+ while (Date.now() < deadline) {
137
+ // Check if manually aborted
138
+ if (signal?.aborted) {
139
+ return { finalStatus: ['ABORTED'], completed: false };
140
+ }
141
+ const jobInfo = await getJobInfo(customJobName, printerName);
142
+ if (jobInfo === null) {
143
+ // Job has disappeared from the queue.
144
+ // If the last known status was a hard error, surface that.
145
+ // Otherwise, Windows removed it because it finished — treat as COMPLETED.
146
+ const hadError = lastKnownStatus.some(s => ERROR_STATUSES.includes(s));
147
+ return {
148
+ finalStatus: hadError ? lastKnownStatus : ['COMPLETED'],
149
+ completed: !hadError
150
+ };
151
+ }
152
+ const currentStatus = jobInfo.status;
153
+ // Notify caller if status changed
154
+ if (JSON.stringify(currentStatus) !== JSON.stringify(lastKnownStatus)) {
155
+ lastKnownStatus = currentStatus;
156
+ onStatusChange?.(currentStatus);
157
+ }
158
+ // Job explicitly marked as done
159
+ if (currentStatus.some(s => DONE_STATUSES.includes(s))) {
160
+ return { finalStatus: ['COMPLETED'], completed: true };
161
+ }
162
+ // Job is in a hard-error state — stop polling
163
+ if (currentStatus.some(s => ERROR_STATUSES.includes(s))) {
164
+ return { finalStatus: currentStatus, completed: false };
165
+ }
166
+ // Sleep for pollInterval, but wake early if aborted
167
+ await new Promise(resolve => {
168
+ const timer = setTimeout(resolve, pollInterval);
169
+ signal?.addEventListener('abort', () => {
170
+ clearTimeout(timer);
171
+ resolve();
172
+ }, { once: true });
173
+ });
174
+ }
175
+ // Timed out
176
+ return { finalStatus: lastKnownStatus, completed: false };
177
+ }
113
178
  /**
114
179
  * Gets all print jobs for a printer with enhanced details
115
180
  */
@@ -1,4 +1,5 @@
1
1
  import { PrintOptions, PrintResult, PrinterInfo, JobInfo } from './types';
2
+ import { WatchJobOptions, WatchJobResult } from './job-tracker';
2
3
  /**
3
4
  * Prints a PDF file with the specified options
4
5
  */
@@ -23,3 +24,8 @@ export declare function getJobs(printerName: string): Promise<JobInfo[]>;
23
24
  * Cancels a print job using custom job name
24
25
  */
25
26
  export declare function cancelJob(customJobName: string, printerName?: string): Promise<boolean>;
27
+ /**
28
+ * Watches a print job until it completes, errors, or times out.
29
+ * Handles the Windows spooler race condition where completed jobs disappear from the queue.
30
+ */
31
+ export declare function watchJob(customJobName: string, printerName?: string, options?: WatchJobOptions): Promise<WatchJobResult>;
@@ -6,6 +6,7 @@ exports.getDefaultPrinter = getDefaultPrinter;
6
6
  exports.getJobInfo = getJobInfo;
7
7
  exports.getJobs = getJobs;
8
8
  exports.cancelJob = cancelJob;
9
+ exports.watchJob = watchJob;
9
10
  const pdf_to_printer_1 = require("pdf-to-printer");
10
11
  const uuid_1 = require("uuid");
11
12
  const child_process_1 = require("child_process");
@@ -150,6 +151,13 @@ async function cancelJob(customJobName, printerName) {
150
151
  return false;
151
152
  }
152
153
  }
154
+ /**
155
+ * Watches a print job until it completes, errors, or times out.
156
+ * Handles the Windows spooler race condition where completed jobs disappear from the queue.
157
+ */
158
+ async function watchJob(customJobName, printerName, options = {}) {
159
+ return (0, job_tracker_1.watchJob)(customJobName, printerName, options);
160
+ }
153
161
  /**
154
162
  * Executes a PowerShell script and returns output
155
163
  */
@@ -170,7 +178,7 @@ function executePowerShell(script) {
170
178
  errorOutput += data.toString();
171
179
  });
172
180
  ps.on('close', (code) => {
173
- if (code === 0 && output.trim()) {
181
+ if (code === 0) {
174
182
  resolve(output.trim());
175
183
  }
176
184
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enhanced-printer",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Advanced PDF printing library with job tracking, custom page sizes, and tray selection for Node.js and Electron",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",