lightspeed-retail-sdk 3.4.0 → 3.4.2

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
@@ -2,14 +2,16 @@
2
2
 
3
3
  A modern JavaScript SDK for interacting with the Lightspeed Retail API. This SDK provides a convenient, secure, and flexible way to access Lightspeed Retail's features—including customer, item, and order management.
4
4
 
5
- **Current Version: 3.4.0** — Enhanced CLI with interactive token injection and production environment support.
5
+ **Current Version: 3.4.2** — Auto-discovery system for seamless cron job and production deployment.
6
6
 
7
7
  ## **🆕 Recent Updates (v3.4.0)**
8
8
 
9
- - **🔧 Enhanced Token Injection**: Interactive `inject-tokens` command with prompts for access/refresh tokens, expiry settings, and storage backend selection
10
- - **🏭 Production Environment Support**: Login command now supports headless environments with `--no-browser` option and automatic detection
9
+ - **🤖 Auto-Discovery System**: SDK automatically discovers storage configuration after CLI setup - perfect for cron jobs and automated scripts
10
+ - **🔧 Enhanced Token Injection**: Interactive `inject-tokens` command with prompts for access/refresh tokens, expiry settings, and storage backend selection
11
+ - **🏭 Production Environment Support**: Login command supports headless environments with `--no-browser` option
12
+ - **📁 Storage Configuration Management**: Automatic saving and updating of storage configurations during CLI operations
13
+ - **🔄 Migration Transparency**: Storage migrations automatically update all scripts to use new storage
11
14
  - **💡 Improved CLI UX**: Better error messages, token validation, and clear instructions for manual OAuth flows
12
- - **📖 Enhanced Documentation**: Updated README and CLI help with production deployment guidance
13
15
 
14
16
  ## **Previous Updates (v3.3.5)**
15
17
 
@@ -27,12 +29,14 @@ A modern JavaScript SDK for interacting with the Lightspeed Retail API. This SDK
27
29
 
28
30
  ## 🚀 Key Features
29
31
 
30
- - **Modern API**: Object-based parameters with full backward compatibility
31
- - **Timestamp Filtering**: Get only records updated since a specific time
32
- - **Robust Error Handling**: Clean, silent error handling with consistent return types
33
- - **Enhanced CLI**: Browser selection, default scopes, and improved authentication
34
- - **Multiple Storage Options**: File, encrypted, database, and in-memory token storage
35
- - **Comprehensive Coverage**: 20+ API methods with consistent interfaces
32
+ - **🤖 Auto-Discovery**: Zero-configuration after CLI setup - perfect for cron jobs and production
33
+ - **🔄 Modern API**: Object-based parameters with full backward compatibility
34
+ - **🕒 Timestamp Filtering**: Get only records updated since a specific time
35
+ - **🛡️ Robust Error Handling**: Clean, silent error handling with consistent return types
36
+ - **🎯 Enhanced CLI**: Interactive setup with storage selection and headless support
37
+ - **🔒 Multiple Storage Options**: File, encrypted, database, and in-memory token storage
38
+ - **📊 Comprehensive Coverage**: 20+ API methods with consistent interfaces
39
+ - **⚡ Seamless Token Management**: Automatic token refresh with failure notifications
36
40
 
37
41
  ## 🔄 Migrating from 3.1.x
38
42
 
@@ -73,7 +77,8 @@ const items = await sdk.getItems({
73
77
  ## Table of Contents
74
78
 
75
79
  - [Another Unofficial Lightspeed Retail V3 API SDK](#another-unofficial-lightspeed-retail-v3-api-sdk)
76
- - [**🆕 Recent Updates (v3.3.5)**](#-recent-updates-v335)
80
+ - [**🆕 Recent Updates (v3.4.0)**](#-recent-updates-v340)
81
+ - [**Previous Updates (v3.3.5)**](#previous-updates-v335)
77
82
  - [🚀 Key Features](#-key-features)
78
83
  - [🔄 Migrating from 3.1.x](#-migrating-from-31x)
79
84
  - [Backward Compatibility](#backward-compatibility)
@@ -112,7 +117,9 @@ const items = await sdk.getItems({
112
117
  - [Quick Start](#quick-start)
113
118
  - [Modern CLI-First Approach (Recommended)](#modern-cli-first-approach-recommended)
114
119
  - [Alternative: Local Installation](#alternative-local-installation)
115
- - [Basic Usage (In-Memory Storage)](#basic-usage-in-memory-storage)
120
+ - [Recommended Usage (Auto-Discovery)](#recommended-usage-auto-discovery)
121
+ - [Explicit Storage (Advanced)](#explicit-storage-advanced)
122
+ - [Production \& Cron Job Setup](#production--cron-job-setup)
116
123
  - [Manual Token Management (Advanced)](#manual-token-management-advanced)
117
124
  - [Option 1: Interactive Token Injection (Easiest)](#option-1-interactive-token-injection-easiest)
118
125
  - [Option 2: Programmatic Token Storage](#option-2-programmatic-token-storage)
@@ -643,50 +650,94 @@ npx lightspeed-retail-sdk login
643
650
  Then in your code:
644
651
 
645
652
  ```javascript
646
- import LightspeedRetailSDK, {
647
- FileTokenStorage,
648
- EncryptedTokenStorage,
649
- } from "lightspeed-retail-sdk";
650
- import dotenv from "dotenv";
651
- dotenv.config();
652
-
653
- // Use the same storage configuration as your CLI
654
- const fileStorage = new FileTokenStorage(
655
- process.env.LIGHTSPEED_TOKEN_FILE || "./tokens/encrypted-tokens.json"
656
- );
657
- const tokenStorage = process.env.LIGHTSPEED_ENCRYPTION_KEY
658
- ? new EncryptedTokenStorage(
659
- fileStorage,
660
- process.env.LIGHTSPEED_ENCRYPTION_KEY
661
- )
662
- : fileStorage;
653
+ import LightspeedRetailSDK from "lightspeed-retail-sdk";
663
654
 
655
+ // After CLI setup, your code is this simple:
664
656
  const api = new LightspeedRetailSDK({
665
- accountID: process.env.LIGHTSPEED_ACCOUNT_ID,
666
657
  clientID: process.env.LIGHTSPEED_CLIENT_ID,
667
658
  clientSecret: process.env.LIGHTSPEED_CLIENT_SECRET,
668
- tokenStorage,
659
+ accountID: process.env.LIGHTSPEED_ACCOUNT_ID,
660
+ // Storage automatically discovered from CLI setup!
669
661
  });
670
662
 
671
- // The SDK will automatically use stored tokens and refresh as needed
663
+ // The SDK automatically finds your tokens and refreshes them as needed
664
+ const items = await api.getItems();
672
665
  export default api;
673
666
  ```
674
667
 
675
- ### Basic Usage (In-Memory Storage)
668
+ ### Recommended Usage (Auto-Discovery)
669
+
670
+ **Best for most users**: After one-time CLI setup, no configuration needed:
676
671
 
677
672
  ```javascript
678
673
  import LightspeedRetailSDK from "lightspeed-retail-sdk";
679
674
 
675
+ // After running: npm run cli login (one time setup)
680
676
  const api = new LightspeedRetailSDK({
681
- accountID: "Your Account No.",
682
- clientID: "Your client ID.",
683
- clientSecret: "Your client secret.",
684
- refreshToken: "Your initial refresh token.",
685
- // No tokenStorage = uses InMemoryTokenStorage by default
677
+ clientID: process.env.LIGHTSPEED_CLIENT_ID,
678
+ clientSecret: process.env.LIGHTSPEED_CLIENT_SECRET,
679
+ accountID: process.env.LIGHTSPEED_ACCOUNT_ID,
680
+ // No tokenStorage needed - automatically discovered!
681
+ });
682
+
683
+ // Works immediately, handles token refresh automatically
684
+ const items = await api.getItems();
685
+ ```
686
+
687
+ ### Explicit Storage (Advanced)
688
+
689
+ For advanced use cases where you want to specify storage manually:
690
+
691
+ ```javascript
692
+ import LightspeedRetailSDK, {
693
+ FileTokenStorage,
694
+ EncryptedTokenStorage
695
+ } from "lightspeed-retail-sdk";
696
+
697
+ const fileStorage = new FileTokenStorage('./lightspeed-tokens.json');
698
+ const tokenStorage = process.env.LIGHTSPEED_ENCRYPTION_KEY
699
+ ? new EncryptedTokenStorage(fileStorage, process.env.LIGHTSPEED_ENCRYPTION_KEY)
700
+ : fileStorage;
701
+
702
+ const api = new LightspeedRetailSDK({
703
+ clientID: process.env.LIGHTSPEED_CLIENT_ID,
704
+ clientSecret: process.env.LIGHTSPEED_CLIENT_SECRET,
705
+ accountID: process.env.LIGHTSPEED_ACCOUNT_ID,
706
+ tokenStorage: tokenStorage, // Explicit storage choice
686
707
  });
687
708
  ```
688
709
 
689
- ⚠️ **Warning**: Basic usage stores tokens in memory only. Tokens will be lost on application restart, which may cause issues with Lightspeed's new token rotation system.
710
+ **Auto-Discovery**: The SDK automatically discovers your storage configuration after initial CLI setup. Perfect for cron jobs and automated scripts.
711
+
712
+ ✅ **Explicit Storage**: You can still explicitly provide tokenStorage for advanced use cases. Choose from FileTokenStorage, EncryptedTokenStorage (recommended), DatabaseTokenStorage, or InMemoryTokenStorage (not recommended).
713
+
714
+ ### Production & Cron Job Setup
715
+
716
+ The auto-discovery system is perfect for production environments and automated scripts:
717
+
718
+ 1. **Initial Setup** (run once):
719
+
720
+ ```bash
721
+ # Interactive setup with storage selection
722
+ npm run cli login
723
+
724
+ # Or for headless environments:
725
+ npm run cli login --no-browser
726
+ ```
727
+
728
+ 2. **Your Scripts Work Automatically**:
729
+ - Cron jobs find storage configuration automatically
730
+ - Token refresh happens seamlessly in background
731
+ - Storage migrations update all scripts automatically
732
+ - No configuration management needed
733
+
734
+ 3. **Environment Variables** (recommended):
735
+
736
+ ```bash
737
+ export LIGHTSPEED_CLIENT_ID="your-client-id"
738
+ export LIGHTSPEED_CLIENT_SECRET="your-client-secret"
739
+ export LIGHTSPEED_ACCOUNT_ID="your-account-id"
740
+ ```
690
741
 
691
742
  ### Manual Token Management (Advanced)
692
743
 
@@ -5,7 +5,9 @@ import dotenv from "dotenv";
5
5
  import {
6
6
  FileTokenStorage,
7
7
  EncryptedTokenStorage,
8
+ DatabaseTokenStorage,
8
9
  } from "../storage/TokenStorage.mjs";
10
+ import { StorageConfig } from "../storage/StorageConfig.mjs";
9
11
  import LightspeedRetailSDK from "../../index.mjs";
10
12
  import inquirer from "inquirer";
11
13
  import { createInterface } from "readline";
@@ -197,7 +199,7 @@ program
197
199
  ).toISOString();
198
200
 
199
201
  // 5. Store tokens using storage backend
200
- storageBackend = await selectStorageBackend();
202
+ storageBackend = await selectStorageBackend(); // Save config for initial setup
201
203
  await storageBackend.setTokens({
202
204
  access_token: tokenData.access_token,
203
205
  refresh_token: tokenData.refresh_token,
@@ -228,7 +230,7 @@ program
228
230
  .action(async () => {
229
231
  let storageBackend = null;
230
232
  try {
231
- storageBackend = await selectStorageBackend();
233
+ storageBackend = await selectStorageBackend(false); // Read-only, don't save config
232
234
  const tokens = await storageBackend.getTokens();
233
235
  if (!tokens || !tokens.access_token) {
234
236
  console.log("\n⚠️ No tokens found in storage.");
@@ -328,6 +330,10 @@ program
328
330
  err.message
329
331
  );
330
332
  }
333
+ // Remove storage config file
334
+ const storageConfig = new StorageConfig();
335
+ await storageConfig.removeConfig();
336
+ console.log("🗑️ Storage configuration cleared");
331
337
  return;
332
338
  }
333
339
  case "postgres": {
@@ -363,6 +369,10 @@ program
363
369
  err.message
364
370
  );
365
371
  }
372
+ // Remove storage config file
373
+ const storageConfig = new StorageConfig();
374
+ await storageConfig.removeConfig();
375
+ console.log("🗑️ Storage configuration cleared");
366
376
  return;
367
377
  }
368
378
  case "mongodb": {
@@ -400,6 +410,10 @@ program
400
410
  err.message
401
411
  );
402
412
  }
413
+ // Remove storage config file
414
+ const storageConfig = new StorageConfig();
415
+ await storageConfig.removeConfig();
416
+ console.log("🗑️ Storage configuration cleared");
403
417
  return;
404
418
  }
405
419
  default:
@@ -437,6 +451,11 @@ program
437
451
  await fileStorage.setTokens({});
438
452
  console.log(`\n✅ Token file cleared: ${tokenFile}`);
439
453
  }
454
+
455
+ // Remove storage config file
456
+ const storageConfig = new StorageConfig();
457
+ await storageConfig.removeConfig();
458
+ console.log("🗑️ Storage configuration cleared");
440
459
  });
441
460
 
442
461
  program
@@ -550,7 +569,7 @@ program
550
569
  }
551
570
 
552
571
  console.log("\n📁 Token Storage Configuration");
553
- storageBackend = await selectStorageBackend();
572
+ storageBackend = await selectStorageBackend(); // Save config for inject-tokens setup
554
573
 
555
574
  // Validate tokens format (basic check)
556
575
  if (accessToken.length < 10) {
@@ -599,7 +618,7 @@ program
599
618
  console.log("\n🔄 Token Storage Migration Wizard\n");
600
619
  // Prompt for source backend
601
620
  console.log("Select SOURCE storage backend:");
602
- sourceBackend = await selectStorageBackend();
621
+ sourceBackend = await selectStorageBackend(false); // Don't save config for source
603
622
  const sourceTokens = await sourceBackend.getTokens();
604
623
  if (!sourceTokens || !sourceTokens.access_token) {
605
624
  console.error("\n❌ No tokens found in source storage. Aborting.");
@@ -610,7 +629,7 @@ program
610
629
 
611
630
  // Prompt for destination backend
612
631
  console.log("\nSelect DESTINATION storage backend:");
613
- destBackend = await selectStorageBackend();
632
+ destBackend = await selectStorageBackend(false); // Don't save config yet - will save after successful migration
614
633
 
615
634
  // Attempt to create table/collection/file if it doesn't exist (best effort)
616
635
  switch (destBackend.constructor.name) {
@@ -726,6 +745,16 @@ program
726
745
  }
727
746
  await destBackend.setTokens(sourceTokens);
728
747
  console.log("\n🎉 Tokens migrated successfully!");
748
+
749
+ // Update storage configuration to point to the new destination
750
+ try {
751
+ const storageConfig = new StorageConfig();
752
+ const config = await getStorageConfig(destBackend);
753
+ await storageConfig.saveConfig(config);
754
+ console.log("✅ Storage configuration updated for auto-discovery");
755
+ } catch (configError) {
756
+ console.warn(`Warning: Could not update storage config: ${configError.message}`);
757
+ }
729
758
  } catch (error) {
730
759
  console.error("\n❌ Migration failed:", error.message);
731
760
  } finally {
@@ -741,7 +770,7 @@ program
741
770
  .action(async () => {
742
771
  let storageBackend = null;
743
772
  try {
744
- storageBackend = await selectStorageBackend();
773
+ storageBackend = await selectStorageBackend(false); // Read-only, don't save config
745
774
  const tokens = await storageBackend.getTokens();
746
775
  if (!tokens || !tokens.access_token) {
747
776
  console.log("\n⚠️ No tokens found in storage. Please login first.");
@@ -1045,7 +1074,80 @@ async function sendTestTokenRefreshFailureEmail(error, accountID) {
1045
1074
  }
1046
1075
  }
1047
1076
 
1048
- async function selectStorageBackend() {
1077
+ /**
1078
+ * Extract configuration from a storage instance for saving
1079
+ * @param {TokenStorage} storage - Storage instance
1080
+ * @returns {Object} Configuration object
1081
+ */
1082
+ async function getStorageConfig(storage) {
1083
+ // Handle EncryptedTokenStorage wrapper
1084
+ if (storage.adapter) {
1085
+ const innerConfig = await getStorageConfig(storage.adapter);
1086
+ return {
1087
+ type: innerConfig.type === "file" ? "encrypted-file" : "database",
1088
+ settings: {
1089
+ ...innerConfig.settings,
1090
+ encrypted: true,
1091
+ encryptionKey: process.env.LIGHTSPEED_ENCRYPTION_KEY || "[provided]"
1092
+ }
1093
+ };
1094
+ }
1095
+
1096
+ // Handle FileTokenStorage
1097
+ if (storage.filePath) {
1098
+ return {
1099
+ type: "file",
1100
+ settings: {
1101
+ filePath: storage.filePath
1102
+ }
1103
+ };
1104
+ }
1105
+
1106
+ // Handle DatabaseTokenStorage
1107
+ if (storage.dbConnectionString) {
1108
+ return {
1109
+ type: "database",
1110
+ settings: {
1111
+ connectionString: storage.dbConnectionString,
1112
+ dbType: storage.dbType,
1113
+ tableName: storage.tableName,
1114
+ appId: storage.appId
1115
+ }
1116
+ };
1117
+ }
1118
+
1119
+ throw new Error("Unknown storage type for configuration");
1120
+ }
1121
+
1122
+ async function selectStorageBackend(saveConfig = true) {
1123
+ const storageConfig = new StorageConfig();
1124
+
1125
+ // Try auto-discovery first if not saving config (read-only operations)
1126
+ if (!saveConfig) {
1127
+ const { autoDiscoverStorage } = await import("../storage/StorageConfig.mjs");
1128
+ const autoStorage = await autoDiscoverStorage();
1129
+ if (autoStorage) {
1130
+ return autoStorage;
1131
+ }
1132
+ }
1133
+
1134
+ const storage = await selectStorageBackendInternal();
1135
+
1136
+ // Save configuration for auto-discovery if requested
1137
+ if (saveConfig) {
1138
+ try {
1139
+ const config = await getStorageConfig(storage);
1140
+ await storageConfig.saveConfig(config);
1141
+ console.log("✅ Storage configuration saved for auto-discovery");
1142
+ } catch (error) {
1143
+ console.warn(`Warning: Could not save storage config: ${error.message}`);
1144
+ }
1145
+ }
1146
+
1147
+ return storage;
1148
+ }
1149
+
1150
+ async function selectStorageBackendInternal() {
1049
1151
  const { storageType } = await inquirer.prompt([
1050
1152
  {
1051
1153
  type: "list",
@@ -1148,10 +1250,6 @@ async function selectStorageBackend() {
1148
1250
  default:
1149
1251
  throw new Error("Unsupported database type");
1150
1252
  }
1151
- // Dynamically import DatabaseTokenStorage
1152
- const { DatabaseTokenStorage } = await import(
1153
- "../storage/TokenStorage.mjs"
1154
- );
1155
1253
  const dbStorage = new DatabaseTokenStorage(dbConnectionString, {
1156
1254
  dbType,
1157
1255
  tableName,
@@ -1239,7 +1337,7 @@ program
1239
1337
  console.log("🔄 Refreshing stored access token...\n");
1240
1338
 
1241
1339
  // Get storage backend
1242
- storageBackend = await selectStorageBackend();
1340
+ storageBackend = await selectStorageBackend(false); // Read-only, don't save config
1243
1341
 
1244
1342
  // Get existing tokens
1245
1343
  const tokens = await storageBackend.getTokens();
@@ -187,6 +187,27 @@ async function sendTokenRefreshFailureEmail(error, accountID) {
187
187
  }
188
188
  }
189
189
  class LightspeedSDKCore {
190
+ /**
191
+ * Initialize token storage through auto-discovery if not explicitly provided
192
+ * @private
193
+ */ async _initializeStorage() {
194
+ if (this._storageInitialized) {
195
+ return;
196
+ }
197
+ if (!this.tokenStorage) {
198
+ try {
199
+ const { autoDiscoverStorage } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../storage/StorageConfig.cjs")));
200
+ this.tokenStorage = await autoDiscoverStorage();
201
+ if (!this.tokenStorage) {
202
+ throw new Error("No token storage found. Please either:\n" + "• Run the CLI to set up storage: npm run cli login\n" + "• Or explicitly provide tokenStorage:\n" + " - FileTokenStorage('./tokens.json')\n" + " - EncryptedTokenStorage(fileStorage, key) [recommended]\n" + " - DatabaseTokenStorage(connection, options)\n" + " - InMemoryTokenStorage() [NOT RECOMMENDED]\n\n" + "See documentation: https://github.com/darrylmorley/lightspeed-retail-sdk#token-storage");
203
+ }
204
+ } catch (error) {
205
+ // If auto-discovery fails, throw helpful error
206
+ throw new Error("Failed to auto-discover token storage. " + error.message);
207
+ }
208
+ }
209
+ this._storageInitialized = true;
210
+ }
190
211
  // Core error handling
191
212
  handleError(context, err, shouldThrow = true) {
192
213
  const errorMessage = (err === null || err === void 0 ? void 0 : err.message) || "Unknown error occurred";
@@ -206,6 +227,8 @@ class LightspeedSDKCore {
206
227
  }
207
228
  // Token management
208
229
  async getToken() {
230
+ // Ensure storage is initialized
231
+ await this._initializeStorage();
209
232
  const now = new Date();
210
233
  const bufferTime = 5 * 60 * 1000; // 5-minute buffer
211
234
  const storedTokens = await this.tokenStorage.getTokens();
@@ -479,6 +502,8 @@ class LightspeedSDKCore {
479
502
  }
480
503
  }
481
504
  async getTokenInfo() {
505
+ // Ensure storage is initialized
506
+ await this._initializeStorage();
482
507
  const storedTokens = await this.tokenStorage.getTokens();
483
508
  return {
484
509
  hasAccessToken: !!storedTokens.access_token,
@@ -526,8 +551,14 @@ class LightspeedSDKCore {
526
551
  this.token = null;
527
552
  this.tokenExpiry = null;
528
553
  this.refreshInProgress = false;
529
- // Token storage interface - defaults to in-memory if not provided
530
- this.tokenStorage = tokenStorage || new InMemoryTokenStorage();
554
+ // Token storage interface - can be explicitly provided or auto-discovered
555
+ if (tokenStorage) {
556
+ this.tokenStorage = tokenStorage;
557
+ } else {
558
+ // Auto-discovery will be handled by async initialization
559
+ this.tokenStorage = null;
560
+ this._storageInitialized = false;
561
+ }
531
562
  }
532
563
  }
533
564
  _define_property(LightspeedSDKCore, "BASE_URL", "https://api.lightspeedapp.com/API/V3/Account");
@@ -146,8 +146,51 @@ export class LightspeedSDKCore {
146
146
  this.tokenExpiry = null;
147
147
  this.refreshInProgress = false;
148
148
 
149
- // Token storage interface - defaults to in-memory if not provided
150
- this.tokenStorage = tokenStorage || new InMemoryTokenStorage();
149
+ // Token storage interface - can be explicitly provided or auto-discovered
150
+ if (tokenStorage) {
151
+ this.tokenStorage = tokenStorage;
152
+ } else {
153
+ // Auto-discovery will be handled by async initialization
154
+ this.tokenStorage = null;
155
+ this._storageInitialized = false;
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Initialize token storage through auto-discovery if not explicitly provided
161
+ * @private
162
+ */
163
+ async _initializeStorage() {
164
+ if (this._storageInitialized) {
165
+ return;
166
+ }
167
+
168
+ if (!this.tokenStorage) {
169
+ try {
170
+ const { autoDiscoverStorage } = await import("../storage/StorageConfig.mjs");
171
+ this.tokenStorage = await autoDiscoverStorage();
172
+
173
+ if (!this.tokenStorage) {
174
+ throw new Error(
175
+ "No token storage found. Please either:\n" +
176
+ "• Run the CLI to set up storage: npm run cli login\n" +
177
+ "• Or explicitly provide tokenStorage:\n" +
178
+ " - FileTokenStorage('./tokens.json')\n" +
179
+ " - EncryptedTokenStorage(fileStorage, key) [recommended]\n" +
180
+ " - DatabaseTokenStorage(connection, options)\n" +
181
+ " - InMemoryTokenStorage() [NOT RECOMMENDED]\n\n" +
182
+ "See documentation: https://github.com/darrylmorley/lightspeed-retail-sdk#token-storage"
183
+ );
184
+ }
185
+ } catch (error) {
186
+ // If auto-discovery fails, throw helpful error
187
+ throw new Error(
188
+ "Failed to auto-discover token storage. " + error.message
189
+ );
190
+ }
191
+ }
192
+
193
+ this._storageInitialized = true;
151
194
  }
152
195
 
153
196
  // Core error handling
@@ -216,6 +259,9 @@ export class LightspeedSDKCore {
216
259
 
217
260
  // Token management
218
261
  async getToken() {
262
+ // Ensure storage is initialized
263
+ await this._initializeStorage();
264
+
219
265
  const now = new Date();
220
266
  const bufferTime = 5 * 60 * 1000; // 5-minute buffer
221
267
 
@@ -536,6 +582,9 @@ export class LightspeedSDKCore {
536
582
  }
537
583
 
538
584
  async getTokenInfo() {
585
+ // Ensure storage is initialized
586
+ await this._initializeStorage();
587
+
539
588
  const storedTokens = await this.tokenStorage.getTokens();
540
589
  return {
541
590
  hasAccessToken: !!storedTokens.access_token,
@@ -0,0 +1,175 @@
1
+ import { promises as fs } from "fs";
2
+ import path from "path";
3
+
4
+ /**
5
+ * Storage configuration manager that tracks which storage type and settings
6
+ * are being used, enabling automatic storage discovery.
7
+ */
8
+ export class StorageConfig {
9
+ constructor(configPath = ".lightspeed-storage-config.json") {
10
+ this.configPath = path.resolve(configPath);
11
+ }
12
+
13
+ /**
14
+ * Save storage configuration for future auto-discovery
15
+ * @param {Object} config - Storage configuration
16
+ * @param {string} config.type - Storage type: 'file', 'encrypted-file', 'database'
17
+ * @param {Object} config.settings - Storage-specific settings
18
+ */
19
+ async saveConfig(config) {
20
+ try {
21
+ const configData = {
22
+ type: config.type,
23
+ settings: config.settings,
24
+ lastUpdated: new Date().toISOString(),
25
+ version: "1.0"
26
+ };
27
+
28
+ await fs.writeFile(
29
+ this.configPath,
30
+ JSON.stringify(configData, null, 2),
31
+ "utf8"
32
+ );
33
+ } catch (error) {
34
+ console.warn(`Warning: Could not save storage config: ${error.message}`);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Load existing storage configuration
40
+ * @returns {Object|null} Configuration object or null if not found
41
+ */
42
+ async loadConfig() {
43
+ try {
44
+ const data = await fs.readFile(this.configPath, "utf8");
45
+ return JSON.parse(data);
46
+ } catch (error) {
47
+ if (error.code === "ENOENT") {
48
+ return null; // Config file doesn't exist
49
+ }
50
+ console.warn(`Warning: Could not load storage config: ${error.message}`);
51
+ return null;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Check if storage configuration exists
57
+ * @returns {boolean}
58
+ */
59
+ async hasConfig() {
60
+ try {
61
+ await fs.access(this.configPath);
62
+ return true;
63
+ } catch {
64
+ return false;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Remove storage configuration
70
+ */
71
+ async removeConfig() {
72
+ try {
73
+ await fs.unlink(this.configPath);
74
+ } catch (error) {
75
+ if (error.code !== "ENOENT") {
76
+ console.warn(`Warning: Could not remove storage config: ${error.message}`);
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Create a token storage instance based on saved configuration
84
+ * @param {Object} config - Storage configuration from StorageConfig
85
+ * @returns {TokenStorage} Configured storage instance
86
+ */
87
+ export async function createStorageFromConfig(config) {
88
+ const {
89
+ FileTokenStorage,
90
+ EncryptedTokenStorage,
91
+ DatabaseTokenStorage
92
+ } = await import("./TokenStorage.mjs");
93
+
94
+ switch (config.type) {
95
+ case "file": {
96
+ return new FileTokenStorage(config.settings.filePath);
97
+ }
98
+
99
+ case "encrypted-file": {
100
+ const fileStorage = new FileTokenStorage(config.settings.filePath);
101
+ return new EncryptedTokenStorage(fileStorage, config.settings.encryptionKey);
102
+ }
103
+
104
+ case "database": {
105
+ const dbStorage = new DatabaseTokenStorage(
106
+ config.settings.connectionString,
107
+ {
108
+ dbType: config.settings.dbType,
109
+ tableName: config.settings.tableName,
110
+ appId: config.settings.appId
111
+ }
112
+ );
113
+
114
+ if (config.settings.encrypted) {
115
+ return new EncryptedTokenStorage(dbStorage, config.settings.encryptionKey);
116
+ }
117
+
118
+ return dbStorage;
119
+ }
120
+
121
+ default:
122
+ throw new Error(`Unknown storage type: ${config.type}`);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Auto-discover and create the appropriate token storage based on
128
+ * previously saved configuration, environment variables, or defaults.
129
+ * @param {string} configPath - Optional path to config file
130
+ * @returns {TokenStorage|null} Storage instance or null if none found
131
+ */
132
+ export async function autoDiscoverStorage(configPath) {
133
+ const storageConfig = new StorageConfig(configPath);
134
+
135
+ // Try to load saved configuration first
136
+ const config = await storageConfig.loadConfig();
137
+ if (config) {
138
+ try {
139
+ return await createStorageFromConfig(config);
140
+ } catch (error) {
141
+ console.warn(`Warning: Could not create storage from saved config: ${error.message}`);
142
+ }
143
+ }
144
+
145
+ // Fallback: Check for environment variables for common setups
146
+ if (process.env.LIGHTSPEED_TOKEN_FILE) {
147
+ const { FileTokenStorage, EncryptedTokenStorage } = await import("./TokenStorage.mjs");
148
+ const fileStorage = new FileTokenStorage(process.env.LIGHTSPEED_TOKEN_FILE);
149
+
150
+ if (process.env.LIGHTSPEED_ENCRYPTION_KEY) {
151
+ return new EncryptedTokenStorage(fileStorage, process.env.LIGHTSPEED_ENCRYPTION_KEY);
152
+ }
153
+
154
+ return fileStorage;
155
+ }
156
+
157
+ // Check for default token file locations
158
+ const defaultPaths = [
159
+ ".lightspeed-tokens.json",
160
+ "./tokens/encrypted-tokens.json",
161
+ "./lightspeed-tokens.json"
162
+ ];
163
+
164
+ for (const defaultPath of defaultPaths) {
165
+ try {
166
+ await fs.access(defaultPath);
167
+ const { FileTokenStorage } = await import("./TokenStorage.mjs");
168
+ return new FileTokenStorage(defaultPath);
169
+ } catch {
170
+ // File doesn't exist, continue to next
171
+ }
172
+ }
173
+
174
+ return null;
175
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightspeed-retail-sdk",
3
- "version": "3.4.0",
3
+ "version": "3.4.2",
4
4
  "description": "Another unofficial Lightspeed Retail API SDK for Node.js",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",