labelinn 1.1.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/lib/index.js ADDED
@@ -0,0 +1,556 @@
1
+ /**
2
+ * LabelInn Node.js SDK
3
+ *
4
+ * Usage:
5
+ * const LabelInn = require('labelinn');
6
+ * const client = new LabelInn('sk_live_xxxxx');
7
+ *
8
+ * // Print a label from a template
9
+ * const job = await client.print.create({
10
+ * printer_id: 'abc123',
11
+ * payload_type: 'template',
12
+ * design_id: 'tmpl_shipping',
13
+ * data: { barcode: 'TR123', name: 'Ali' },
14
+ * });
15
+ *
16
+ * // Check job status
17
+ * const status = await client.print.get(job.id);
18
+ */
19
+
20
+ 'use strict';
21
+
22
+ const BASE_URL = 'https://labelinn.com/v1';
23
+
24
+ class LabelInnError extends Error {
25
+ constructor(status, code, message, raw) {
26
+ super(message);
27
+ this.name = 'LabelInnError';
28
+ this.status = status;
29
+ this.code = code;
30
+ this.raw = raw;
31
+ }
32
+ }
33
+
34
+ class LabelInnRateLimitError extends LabelInnError {
35
+ constructor(status, code, message, retryAfter, raw) {
36
+ super(status, code, message, raw);
37
+ this.name = 'LabelInnRateLimitError';
38
+ this.retryAfter = retryAfter;
39
+ }
40
+ }
41
+
42
+ // ─── HTTP Client ─────────────────────────────────────────────
43
+
44
+ class HttpClient {
45
+ constructor(apiKey, opts = {}) {
46
+ this.apiKey = apiKey;
47
+ this.baseUrl = opts.baseUrl || BASE_URL;
48
+ this.timeout = opts.timeout || 30000;
49
+ }
50
+
51
+ async request(method, path, body, opts = {}) {
52
+ const url = `${this.baseUrl}${path}`;
53
+ const headers = {
54
+ 'Authorization': `Bearer ${this.apiKey}`,
55
+ 'Content-Type': 'application/json',
56
+ 'User-Agent': 'labelinn-node/1.0.0',
57
+ };
58
+ if (opts.idempotencyKey) {
59
+ headers['X-Idempotency-Key'] = opts.idempotencyKey;
60
+ }
61
+
62
+ const fetchOpts = {
63
+ method,
64
+ headers,
65
+ signal: AbortSignal.timeout(this.timeout),
66
+ };
67
+ if (body && method !== 'GET') {
68
+ fetchOpts.body = JSON.stringify(body);
69
+ }
70
+
71
+ const response = await fetch(url, fetchOpts);
72
+
73
+ // Parse rate limit headers
74
+ const rateLimit = {
75
+ limit: parseInt(response.headers.get('x-ratelimit-limit') || '0', 10),
76
+ remaining: parseInt(response.headers.get('x-ratelimit-remaining') || '0', 10),
77
+ reset: parseInt(response.headers.get('x-ratelimit-reset') || '0', 10),
78
+ };
79
+
80
+ let data;
81
+ try {
82
+ data = await response.json();
83
+ } catch {
84
+ data = null;
85
+ }
86
+
87
+ if (!response.ok) {
88
+ const err = data?.error || {};
89
+ if (response.status === 429) {
90
+ throw new LabelInnRateLimitError(
91
+ 429, err.code || 'RATE_LIMIT_EXCEEDED',
92
+ err.message || 'Rate limit exceeded',
93
+ err.retry_after_seconds || rateLimit.reset,
94
+ data
95
+ );
96
+ }
97
+ throw new LabelInnError(
98
+ response.status,
99
+ err.code || 'API_ERROR',
100
+ err.message || `HTTP ${response.status}`,
101
+ data
102
+ );
103
+ }
104
+
105
+ const result = data?.data || data;
106
+ result._rateLimit = rateLimit;
107
+ if (data?._idempotent) result._idempotent = true;
108
+ return result;
109
+ }
110
+
111
+ get(path) { return this.request('GET', path); }
112
+ post(path, body, opts) { return this.request('POST', path, body, opts); }
113
+ put(path, body) { return this.request('PUT', path, body); }
114
+ patch(path, body) { return this.request('PATCH', path, body); }
115
+ del(path) { return this.request('DELETE', path); }
116
+ }
117
+
118
+ // ─── Resource: Print Jobs ────────────────────────────────────
119
+
120
+ class PrintResource {
121
+ constructor(http) { this._http = http; }
122
+
123
+ /**
124
+ * Submit a print job.
125
+ *
126
+ * @param {object} params
127
+ * @param {string} params.printer_id - Target printer ID
128
+ * @param {string} params.payload_type - "zpl" | "image" | "template"
129
+ * @param {string} [params.payload_data] - ZPL string or base64 image
130
+ * @param {string} [params.image_url] - HTTPS URL to image (for payload_type:"image")
131
+ * @param {string} [params.design_id] - Template ID (for payload_type:"template")
132
+ * @param {object|object[]} [params.data] - Variable values for template
133
+ * @param {string} [params.job_name] - Human-readable job name
134
+ * @param {number} [params.copies] - Number of copies (1-1000)
135
+ * @param {number} [params.media_width_mm] - Label width in mm
136
+ * @param {number} [params.media_height_mm] - Label height in mm
137
+ * @param {string} [opts.idempotencyKey] - Prevents duplicate prints
138
+ * @returns {Promise<object>} Created job
139
+ */
140
+ create(params, opts = {}) {
141
+ return this._http.post('/print/jobs', params, opts);
142
+ }
143
+
144
+ /**
145
+ * List print jobs.
146
+ * @param {object} [query] - { status, printer_id, limit, page_token }
147
+ */
148
+ list(query = {}) {
149
+ const qs = new URLSearchParams();
150
+ for (const [k, v] of Object.entries(query)) {
151
+ if (v !== undefined && v !== null) qs.set(k, String(v));
152
+ }
153
+ const qstr = qs.toString();
154
+ return this._http.get(`/print/jobs${qstr ? '?' + qstr : ''}`);
155
+ }
156
+
157
+ /**
158
+ * Get a single print job by ID.
159
+ * @param {string} jobId
160
+ */
161
+ get(jobId) {
162
+ return this._http.get(`/print/jobs/${jobId}`);
163
+ }
164
+
165
+ /**
166
+ * Cancel a queued print job.
167
+ * @param {string} jobId
168
+ */
169
+ cancel(jobId) {
170
+ return this._http.del(`/print/jobs/${jobId}`);
171
+ }
172
+
173
+ /**
174
+ * Reprint a previous job.
175
+ * @param {string} jobId
176
+ * @param {object} [overrides] - { printer_id, copies, data }
177
+ */
178
+ reprint(jobId, overrides = {}) {
179
+ return this._http.post(`/print/jobs/${jobId}/reprint`, overrides);
180
+ }
181
+
182
+ /**
183
+ * Retry a failed job (creates new job from original).
184
+ * @param {string} jobId
185
+ * @param {object} [params] - { printer_id } optional override
186
+ */
187
+ retry(jobId, params = {}) {
188
+ return this._http.post(`/print/jobs/${jobId}/retry`, params);
189
+ }
190
+
191
+ /**
192
+ * Submit up to 100 print jobs in a single request.
193
+ * @param {object[]} jobs - Array of job objects
194
+ */
195
+ batch(jobs) {
196
+ return this._http.post('/print/batch', { jobs });
197
+ }
198
+ }
199
+
200
+ // ─── Resource: Fleet (Printers) ──────────────────────────────
201
+
202
+ class FleetResource {
203
+ constructor(http) {
204
+ this._http = http;
205
+ this.groups = new FleetGroupsResource(http);
206
+ }
207
+
208
+ /**
209
+ * List all printers.
210
+ * @param {object} [query] - { status, type, connection }
211
+ */
212
+ list(query = {}) {
213
+ const qs = new URLSearchParams();
214
+ for (const [k, v] of Object.entries(query)) {
215
+ if (v !== undefined && v !== null) qs.set(k, String(v));
216
+ }
217
+ const qstr = qs.toString();
218
+ return this._http.get(`/fleet/printers${qstr ? '?' + qstr : ''}`);
219
+ }
220
+
221
+ /**
222
+ * Get printer details + telemetry.
223
+ * @param {string} printerId
224
+ */
225
+ get(printerId) {
226
+ return this._http.get(`/fleet/printers/${printerId}`);
227
+ }
228
+
229
+ /**
230
+ * Get live printer status (lightweight).
231
+ * @param {string} printerId
232
+ */
233
+ status(printerId) {
234
+ return this._http.get(`/fleet/printers/${printerId}/status`);
235
+ }
236
+
237
+ /**
238
+ * Update printer metadata.
239
+ * @param {string} printerId
240
+ * @param {object} params - { name, metadata }
241
+ */
242
+ update(printerId, params) {
243
+ return this._http.put(`/fleet/printers/${printerId}`, params);
244
+ }
245
+
246
+ /**
247
+ * Flash/beep a printer to identify it.
248
+ * @param {string} printerId
249
+ */
250
+ identify(printerId) {
251
+ return this._http.post(`/fleet/printers/${printerId}/identify`);
252
+ }
253
+
254
+ /**
255
+ * Set media definition on a printer.
256
+ * @param {string} printerId
257
+ * @param {object} params - { width_mm, height_mm, media_type, gap_mm, ... }
258
+ */
259
+ setMedia(printerId, params) {
260
+ return this._http.put(`/fleet/printers/${printerId}/media`, params);
261
+ }
262
+ }
263
+
264
+ class FleetGroupsResource {
265
+ constructor(http) { this._http = http; }
266
+
267
+ create(params) { return this._http.post('/fleet/groups', params); }
268
+ list(query = {}) {
269
+ const qs = new URLSearchParams();
270
+ for (const [k, v] of Object.entries(query)) {
271
+ if (v !== undefined && v !== null) qs.set(k, String(v));
272
+ }
273
+ const qstr = qs.toString();
274
+ return this._http.get(`/fleet/groups${qstr ? '?' + qstr : ''}`);
275
+ }
276
+ get(groupId) { return this._http.get(`/fleet/groups/${groupId}`); }
277
+ update(groupId, params) { return this._http.put(`/fleet/groups/${groupId}`, params); }
278
+ delete(groupId) { return this._http.del(`/fleet/groups/${groupId}`); }
279
+ }
280
+
281
+ // ─── Resource: Designs (Templates) ───────────────────────────
282
+
283
+ class DesignsResource {
284
+ constructor(http) { this._http = http; }
285
+
286
+ list(query = {}) {
287
+ const qs = new URLSearchParams();
288
+ for (const [k, v] of Object.entries(query)) {
289
+ if (v !== undefined && v !== null) qs.set(k, String(v));
290
+ }
291
+ const qstr = qs.toString();
292
+ return this._http.get(`/designs${qstr ? '?' + qstr : ''}`);
293
+ }
294
+
295
+ get(designId) {
296
+ return this._http.get(`/designs/${designId}`);
297
+ }
298
+
299
+ create(params) {
300
+ return this._http.post('/designs', params);
301
+ }
302
+
303
+ update(designId, params) {
304
+ return this._http.put(`/designs/${designId}`, params);
305
+ }
306
+
307
+ delete(designId) {
308
+ return this._http.del(`/designs/${designId}`);
309
+ }
310
+
311
+ // ── Elements ──
312
+
313
+ listElements(designId) {
314
+ return this._http.get(`/designs/${designId}/elements`);
315
+ }
316
+
317
+ addElement(designId, params) {
318
+ return this._http.post(`/designs/${designId}/elements`, params);
319
+ }
320
+
321
+ updateElement(designId, elementId, params) {
322
+ return this._http.patch(`/designs/${designId}/elements/${elementId}`, params);
323
+ }
324
+
325
+ deleteElement(designId, elementId) {
326
+ return this._http.del(`/designs/${designId}/elements/${elementId}`);
327
+ }
328
+
329
+ // ── Variables & Actions ──
330
+
331
+ listVariables(designId) {
332
+ return this._http.get(`/designs/${designId}/variables`);
333
+ }
334
+
335
+ clone(designId) {
336
+ return this._http.post(`/designs/${designId}/clone`);
337
+ }
338
+
339
+ render(designId, params = {}) {
340
+ return this._http.post(`/designs/${designId}/render`, params);
341
+ }
342
+
343
+ /**
344
+ * Print a design directly with data.
345
+ * @param {string} designId
346
+ * @param {object} params - { printer_id, data, copies }
347
+ */
348
+ print(designId, params) {
349
+ return this._http.post(`/designs/${designId}/print`, params);
350
+ }
351
+
352
+ /**
353
+ * List revision history for a design.
354
+ * @param {string} designId
355
+ * @param {object} [query] - { limit }
356
+ */
357
+ revisions(designId, query = {}) {
358
+ const qs = new URLSearchParams();
359
+ for (const [k, v] of Object.entries(query)) {
360
+ if (v !== undefined && v !== null) qs.set(k, String(v));
361
+ }
362
+ const qstr = qs.toString();
363
+ return this._http.get(`/designs/${designId}/revisions${qstr ? '?' + qstr : ''}`);
364
+ }
365
+ }
366
+
367
+ // ─── Resource: Webhooks ──────────────────────────────────────
368
+
369
+ class WebhooksResource {
370
+ constructor(http) { this._http = http; }
371
+
372
+ /**
373
+ * Subscribe to events.
374
+ * @param {object} params - { url, events, description }
375
+ */
376
+ create(params) {
377
+ return this._http.post('/webhooks', params);
378
+ }
379
+
380
+ list() {
381
+ return this._http.get('/webhooks');
382
+ }
383
+
384
+ delete(webhookId) {
385
+ return this._http.del(`/webhooks/${webhookId}`);
386
+ }
387
+
388
+ /**
389
+ * Send a test ping to a webhook endpoint.
390
+ * @param {string} webhookId
391
+ */
392
+ test(webhookId) {
393
+ return this._http.post(`/webhooks/${webhookId}/test`);
394
+ }
395
+
396
+ /**
397
+ * Update a webhook subscription.
398
+ * @param {string} webhookId
399
+ * @param {object} params - { url, events, description, enabled }
400
+ */
401
+ update(webhookId, params) {
402
+ return this._http.put(`/webhooks/${webhookId}`, params);
403
+ }
404
+
405
+ /**
406
+ * List recent delivery attempts for a webhook.
407
+ * @param {string} webhookId
408
+ * @param {object} [query] - { limit }
409
+ */
410
+ deliveries(webhookId, query = {}) {
411
+ const qs = new URLSearchParams();
412
+ for (const [k, v] of Object.entries(query)) {
413
+ if (v !== undefined && v !== null) qs.set(k, String(v));
414
+ }
415
+ const qstr = qs.toString();
416
+ return this._http.get(`/webhooks/${webhookId}/deliveries${qstr ? '?' + qstr : ''}`);
417
+ }
418
+ }
419
+
420
+ // ─── Main Client ─────────────────────────────────────────────
421
+
422
+ class ConnectResource {
423
+ constructor(http) { this._http = http; }
424
+
425
+ /**
426
+ * Push data in any format (XML, CSV, JSON).
427
+ * @param {object} params - { payload, source_id?, format?, name?, config? }
428
+ */
429
+ ingest(params) {
430
+ return this._http.post('/connect/ingest', params);
431
+ }
432
+
433
+ /**
434
+ * Parse data without storing (dry run).
435
+ * @param {object} params - { payload, format?, config? }
436
+ */
437
+ testParse(params) {
438
+ return this._http.post('/connect/test-parse', params);
439
+ }
440
+
441
+ /**
442
+ * Create a connector source.
443
+ * @param {object} params - { name, description?, format?, config? }
444
+ */
445
+ createSource(params) {
446
+ return this._http.post('/connect/sources', params);
447
+ }
448
+
449
+ /** List all connector sources. */
450
+ listSources(query = {}) {
451
+ const qs = new URLSearchParams();
452
+ for (const [k, v] of Object.entries(query)) {
453
+ if (v !== undefined && v !== null) qs.set(k, String(v));
454
+ }
455
+ const qstr = qs.toString();
456
+ return this._http.get(`/connect/sources${qstr ? '?' + qstr : ''}`);
457
+ }
458
+
459
+ /** Get a connector source by ID. */
460
+ getSource(sourceId) {
461
+ return this._http.get(`/connect/sources/${sourceId}`);
462
+ }
463
+
464
+ /** Update a connector source. */
465
+ updateSource(sourceId, params) {
466
+ return this._http.put(`/connect/sources/${sourceId}`, params);
467
+ }
468
+
469
+ /** Delete (deactivate) a connector source. */
470
+ deleteSource(sourceId) {
471
+ return this._http.del(`/connect/sources/${sourceId}`);
472
+ }
473
+
474
+ /** Get records from a connector source. */
475
+ listRecords(sourceId, query = {}) {
476
+ const qs = new URLSearchParams();
477
+ for (const [k, v] of Object.entries(query)) {
478
+ if (v !== undefined && v !== null) qs.set(k, String(v));
479
+ }
480
+ const qstr = qs.toString();
481
+ return this._http.get(`/connect/sources/${sourceId}/records${qstr ? '?' + qstr : ''}`);
482
+ }
483
+
484
+ /** Get discovered schema for a connector source. */
485
+ getSchema(sourceId) {
486
+ return this._http.get(`/connect/sources/${sourceId}/schema`);
487
+ }
488
+
489
+ /** Get field mappings for a connector source. */
490
+ getMappings(sourceId) {
491
+ return this._http.get(`/connect/sources/${sourceId}/mappings`);
492
+ }
493
+
494
+ /** Set field→label mappings. */
495
+ updateMappings(sourceId, mappings) {
496
+ return this._http.put(`/connect/sources/${sourceId}/mappings`, { mappings });
497
+ }
498
+
499
+ /**
500
+ * Print labels from connector data.
501
+ * @param {string} sourceId
502
+ * @param {object} params - { printer_id, design_id, record_ids?, ingest_id?, copies?, field_mappings? }
503
+ */
504
+ print(sourceId, params) {
505
+ return this._http.post(`/connect/sources/${sourceId}/print`, params);
506
+ }
507
+ }
508
+
509
+ class LabelInn {
510
+ /**
511
+ * Create a LabelInn API client.
512
+ *
513
+ * @param {string} apiKey - Your API key (sk_live_xxx or sk_test_xxx)
514
+ * @param {object} [opts]
515
+ * @param {string} [opts.baseUrl] - Override API base URL
516
+ * @param {number} [opts.timeout] - Request timeout in ms (default: 30000)
517
+ */
518
+ constructor(apiKey, opts = {}) {
519
+ if (!apiKey || typeof apiKey !== 'string') {
520
+ throw new Error('LabelInn: apiKey is required. Get one at https://labelinn.com → Settings → API Keys');
521
+ }
522
+ if (!apiKey.startsWith('sk_live_') && !apiKey.startsWith('sk_test_')) {
523
+ throw new Error('LabelInn: apiKey must start with sk_live_ or sk_test_');
524
+ }
525
+
526
+ this._http = new HttpClient(apiKey, opts);
527
+
528
+ /** @type {PrintResource} Print job operations */
529
+ this.print = new PrintResource(this._http);
530
+
531
+ /** @type {FleetResource} Printer fleet management */
532
+ this.fleet = new FleetResource(this._http);
533
+
534
+ /** @type {DesignsResource} Design/template management */
535
+ this.designs = new DesignsResource(this._http);
536
+
537
+ /** @type {WebhooksResource} Webhook subscriptions */
538
+ this.webhooks = new WebhooksResource(this._http);
539
+
540
+ /** @type {ConnectResource} Data Connect — enterprise data ingestion */
541
+ this.connect = new ConnectResource(this._http);
542
+ }
543
+
544
+ /** Returns true if using a test key */
545
+ get isTestMode() {
546
+ return this._http.apiKey.startsWith('sk_test_');
547
+ }
548
+ }
549
+
550
+ // ─── Exports ─────────────────────────────────────────────────
551
+
552
+ module.exports = LabelInn;
553
+ module.exports.LabelInn = LabelInn;
554
+ module.exports.LabelInnError = LabelInnError;
555
+ module.exports.LabelInnRateLimitError = LabelInnRateLimitError;
556
+ module.exports.default = LabelInn;
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "labelinn",
3
+ "version": "1.1.0",
4
+ "description": "Official Node.js SDK for the LabelInn Cloud Print API. Send labels to Zebra, TSC, and Honeywell thermal printers from any system.",
5
+ "keywords": [
6
+ "labelinn",
7
+ "label",
8
+ "printer",
9
+ "zebra",
10
+ "tsc",
11
+ "honeywell",
12
+ "zpl",
13
+ "barcode",
14
+ "shipping",
15
+ "thermal",
16
+ "thermal-printer",
17
+ "cloud-print",
18
+ "print-api",
19
+ "warehouse",
20
+ "logistics",
21
+ "ecommerce"
22
+ ],
23
+ "license": "MIT",
24
+ "author": "LabelInn <support@labelinn.com> (https://labelinn.com)",
25
+ "homepage": "https://labelinn.com/developers",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/labelcraft31/labelinn-node.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/labelcraft31/labelinn-node/issues"
32
+ },
33
+ "funding": {
34
+ "url": "https://labelinn.com"
35
+ },
36
+ "main": "lib/index.js",
37
+ "types": "lib/index.d.ts",
38
+ "exports": {
39
+ ".": {
40
+ "require": "./lib/index.js",
41
+ "types": "./lib/index.d.ts"
42
+ }
43
+ },
44
+ "bin": {
45
+ "labelinn": "bin/cli.js"
46
+ },
47
+ "files": [
48
+ "lib/",
49
+ "bin/",
50
+ "LICENSE",
51
+ "README.md",
52
+ "CHANGELOG.md"
53
+ ],
54
+ "engines": {
55
+ "node": ">=18.0.0"
56
+ },
57
+ "scripts": {
58
+ "test": "node test/basic.js",
59
+ "prepublishOnly": "node -e \"require('./lib/index.js')\""
60
+ }
61
+ }