enhanced-printer 1.0.8 → 1.0.10

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");
@@ -63,8 +64,15 @@ async function print(options) {
63
64
  if (options.subset !== undefined) {
64
65
  printOptions.subset = options.subset;
65
66
  }
66
- // Set the document name for job tracking
67
- printOptions.win32 = ['-print-settings', `${options.filePath}`];
67
+ // Build SumatraPDF print-settings for label clarity
68
+ // Scaling is CRITICAL - 'noscale' prints at actual PDF size without distortion
69
+ const scaling = options.scaling || 'noscale';
70
+ const printSettings = [scaling];
71
+ if (options.monochrome) {
72
+ printSettings.push('monochrome');
73
+ }
74
+ // Set the document name for job tracking and pass print settings to SumatraPDF
75
+ printOptions.win32 = ['-print-settings', printSettings.join(',')];
68
76
  // Print the document
69
77
  await (0, pdf_to_printer_1.print)(options.filePath, printOptions);
70
78
  // Try to retrieve job ID
@@ -150,6 +158,13 @@ async function cancelJob(customJobName, printerName) {
150
158
  return false;
151
159
  }
152
160
  }
161
+ /**
162
+ * Watches a print job until it completes, errors, or times out.
163
+ * Handles the Windows spooler race condition where completed jobs disappear from the queue.
164
+ */
165
+ async function watchJob(customJobName, printerName, options = {}) {
166
+ return (0, job_tracker_1.watchJob)(customJobName, printerName, options);
167
+ }
153
168
  /**
154
169
  * Executes a PowerShell script and returns output
155
170
  */
package/lib/types.d.ts CHANGED
@@ -42,6 +42,13 @@ export interface PrintOptions {
42
42
  pages?: string;
43
43
  /** Print only odd or even pages (optional) */
44
44
  subset?: 'odd' | 'even';
45
+ /**
46
+ * Scaling mode for printing (optional - defaults to 'noscale' for label clarity)
47
+ * - 'noscale': Print at actual PDF size (BEST for labels - no distortion)
48
+ * - 'fit': Fit to printable area (may distort aspect ratio)
49
+ * - 'shrink': Shrink if larger than page (may reduce quality)
50
+ */
51
+ scaling?: 'noscale' | 'fit' | 'shrink';
45
52
  }
46
53
  /**
47
54
  * Result of a print operation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enhanced-printer",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
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",