n8n-nodes-lemonsqueezy 0.5.0 → 0.6.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/README.md +23 -0
- package/dist/nodes/LemonSqueezy/LemonSqueezy.node.js +22 -0
- package/dist/nodes/LemonSqueezy/LemonSqueezyTrigger.node.js +46 -13
- package/dist/nodes/LemonSqueezy/constants.d.ts +2 -0
- package/dist/nodes/LemonSqueezy/constants.js +3 -1
- package/dist/nodes/LemonSqueezy/helpers.d.ts +7 -2
- package/dist/nodes/LemonSqueezy/helpers.js +17 -5
- package/dist/nodes/LemonSqueezy/resources/shared.d.ts +35 -0
- package/dist/nodes/LemonSqueezy/resources/shared.js +94 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -311,6 +311,29 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
311
311
|
|
|
312
312
|
## Changelog
|
|
313
313
|
|
|
314
|
+
### v0.6.0
|
|
315
|
+
|
|
316
|
+
**Reliability & Error Handling:**
|
|
317
|
+
- Improved webhook management error handling with proper 404 vs other error distinction
|
|
318
|
+
- Added detailed logging for webhook lifecycle (create, check, delete operations)
|
|
319
|
+
- Added logging for filtered/unsubscribed webhook events to aid debugging
|
|
320
|
+
- Added rate limit visibility logging with wait time information
|
|
321
|
+
- Added retry attempt logging with exponential backoff details
|
|
322
|
+
|
|
323
|
+
**Input Validation:**
|
|
324
|
+
- Added pre-API validation for email fields (customer create/update, checkout)
|
|
325
|
+
- Added pre-API validation for URL fields (webhook URL, redirect URLs, receipt link URLs)
|
|
326
|
+
- Added webhook secret minimum length validation (16 characters) for security
|
|
327
|
+
- Validation errors now fail fast before making API requests
|
|
328
|
+
|
|
329
|
+
**Performance:**
|
|
330
|
+
- Added configurable request timeout (default: 30 seconds) for all API requests
|
|
331
|
+
- Timeout prevents hanging requests and improves workflow reliability
|
|
332
|
+
|
|
333
|
+
**Code Quality:**
|
|
334
|
+
- Added common filter field generators to reduce code duplication
|
|
335
|
+
- Added createFiltersField, createStatusFilter factory functions
|
|
336
|
+
|
|
314
337
|
### v0.5.0
|
|
315
338
|
|
|
316
339
|
**Security & Stability Improvements:**
|
|
@@ -10,6 +10,8 @@ async function handleCreate(ctx, resource, itemIndex) {
|
|
|
10
10
|
const name = ctx.getNodeParameter('customerName', itemIndex);
|
|
11
11
|
const email = ctx.getNodeParameter('customerEmail', itemIndex);
|
|
12
12
|
const additionalFields = ctx.getNodeParameter('additionalFields', itemIndex);
|
|
13
|
+
// Validate required fields before API call
|
|
14
|
+
(0, helpers_1.validateField)('email', email, 'email');
|
|
13
15
|
const body = (0, helpers_1.buildJsonApiBody)('customers', { name, email, ...additionalFields }, { store: { type: 'stores', id: storeId } });
|
|
14
16
|
return await helpers_1.lemonSqueezyApiRequest.call(ctx, 'POST', '/customers', body);
|
|
15
17
|
}
|
|
@@ -63,6 +65,8 @@ async function handleCreate(ctx, resource, itemIndex) {
|
|
|
63
65
|
attributes.custom_price = additionalOptions.customPrice;
|
|
64
66
|
}
|
|
65
67
|
if (additionalOptions.email) {
|
|
68
|
+
// Validate email before API call
|
|
69
|
+
(0, helpers_1.validateField)('email', additionalOptions.email, 'email');
|
|
66
70
|
checkoutData.email = additionalOptions.email;
|
|
67
71
|
}
|
|
68
72
|
if (additionalOptions.name) {
|
|
@@ -72,12 +76,16 @@ async function handleCreate(ctx, resource, itemIndex) {
|
|
|
72
76
|
checkoutData.discount_code = additionalOptions.discountCode;
|
|
73
77
|
}
|
|
74
78
|
if (additionalOptions.redirectUrl) {
|
|
79
|
+
// Validate URL before API call
|
|
80
|
+
(0, helpers_1.validateField)('redirectUrl', additionalOptions.redirectUrl, 'url');
|
|
75
81
|
productOptions.redirect_url = additionalOptions.redirectUrl;
|
|
76
82
|
}
|
|
77
83
|
if (additionalOptions.receiptButtonText) {
|
|
78
84
|
productOptions.receipt_button_text = additionalOptions.receiptButtonText;
|
|
79
85
|
}
|
|
80
86
|
if (additionalOptions.receiptLinkUrl) {
|
|
87
|
+
// Validate URL before API call
|
|
88
|
+
(0, helpers_1.validateField)('receiptLinkUrl', additionalOptions.receiptLinkUrl, 'url');
|
|
81
89
|
productOptions.receipt_link_url = additionalOptions.receiptLinkUrl;
|
|
82
90
|
}
|
|
83
91
|
if (additionalOptions.receiptThankYouNote) {
|
|
@@ -154,6 +162,12 @@ async function handleCreate(ctx, resource, itemIndex) {
|
|
|
154
162
|
const events = ctx.getNodeParameter('webhookEvents', itemIndex);
|
|
155
163
|
const secret = ctx.getNodeParameter('webhookSecret', itemIndex);
|
|
156
164
|
const additionalOptions = ctx.getNodeParameter('additionalOptions', itemIndex, {});
|
|
165
|
+
// Validate URL before API call
|
|
166
|
+
(0, helpers_1.validateField)('url', url, 'url');
|
|
167
|
+
// Validate webhook secret minimum length for security
|
|
168
|
+
if (secret.length < 16) {
|
|
169
|
+
throw new Error('Webhook secret must be at least 16 characters for security');
|
|
170
|
+
}
|
|
157
171
|
const attributes = { url, events, secret };
|
|
158
172
|
if (additionalOptions.testMode !== undefined) {
|
|
159
173
|
attributes.test_mode = additionalOptions.testMode;
|
|
@@ -202,6 +216,8 @@ async function handleUpdate(ctx, resource, itemIndex) {
|
|
|
202
216
|
attributes.name = updateFields.name;
|
|
203
217
|
}
|
|
204
218
|
if (updateFields.email) {
|
|
219
|
+
// Validate email before API call
|
|
220
|
+
(0, helpers_1.validateField)('email', updateFields.email, 'email');
|
|
205
221
|
attributes.email = updateFields.email;
|
|
206
222
|
}
|
|
207
223
|
if (updateFields.city) {
|
|
@@ -240,12 +256,18 @@ async function handleUpdate(ctx, resource, itemIndex) {
|
|
|
240
256
|
const updateFields = ctx.getNodeParameter('updateFields', itemIndex);
|
|
241
257
|
const attributes = {};
|
|
242
258
|
if (updateFields.url) {
|
|
259
|
+
// Validate URL before API call
|
|
260
|
+
(0, helpers_1.validateField)('url', updateFields.url, 'url');
|
|
243
261
|
attributes.url = updateFields.url;
|
|
244
262
|
}
|
|
245
263
|
if (updateFields.events) {
|
|
246
264
|
attributes.events = updateFields.events;
|
|
247
265
|
}
|
|
248
266
|
if (updateFields.secret) {
|
|
267
|
+
// Validate webhook secret minimum length for security
|
|
268
|
+
if (updateFields.secret.length < 16) {
|
|
269
|
+
throw new Error('Webhook secret must be at least 16 characters for security');
|
|
270
|
+
}
|
|
249
271
|
attributes.secret = updateFields.secret;
|
|
250
272
|
}
|
|
251
273
|
const body = (0, helpers_1.buildJsonApiBody)('webhooks', attributes, undefined, webhookId);
|
|
@@ -90,6 +90,14 @@ class LemonSqueezyTrigger {
|
|
|
90
90
|
const webhookUrl = this.getNodeWebhookUrl('default');
|
|
91
91
|
const storeId = this.getNodeParameter('storeId');
|
|
92
92
|
const webhookData = this.getWorkflowStaticData('node');
|
|
93
|
+
// Helper to check if error is a 404 (webhook not found)
|
|
94
|
+
const isNotFoundError = (error) => {
|
|
95
|
+
if (error && typeof error === 'object') {
|
|
96
|
+
const err = error;
|
|
97
|
+
return err.statusCode === 404 || err.httpCode === 404 || err.code === 404;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
};
|
|
93
101
|
// Check if we have stored webhook data
|
|
94
102
|
if (webhookData.webhookId) {
|
|
95
103
|
try {
|
|
@@ -98,10 +106,17 @@ class LemonSqueezyTrigger {
|
|
|
98
106
|
return true;
|
|
99
107
|
}
|
|
100
108
|
catch (error) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
109
|
+
const webhookId = String(webhookData.webhookId);
|
|
110
|
+
if (isNotFoundError(error)) {
|
|
111
|
+
// Webhook was deleted externally - this is expected, recreate it
|
|
112
|
+
// eslint-disable-next-line no-console
|
|
113
|
+
console.log(`[LemonSqueezy] Webhook ${webhookId} not found (404) - will recreate`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// Unexpected error - could be auth, network, or server issue
|
|
117
|
+
// eslint-disable-next-line no-console
|
|
118
|
+
console.warn(`[LemonSqueezy] Webhook ${webhookId} check failed with unexpected error: ${error instanceof Error ? error.message : 'Unknown error'}. Will attempt to recreate.`);
|
|
119
|
+
}
|
|
105
120
|
delete webhookData.webhookId;
|
|
106
121
|
return false;
|
|
107
122
|
}
|
|
@@ -117,15 +132,16 @@ class LemonSqueezyTrigger {
|
|
|
117
132
|
const existingWebhook = webhooks.find((webhook) => { var _a; return ((_a = webhook.attributes) === null || _a === void 0 ? void 0 : _a.url) === webhookUrl; });
|
|
118
133
|
if (existingWebhook) {
|
|
119
134
|
webhookData.webhookId = existingWebhook.id;
|
|
135
|
+
// eslint-disable-next-line no-console
|
|
136
|
+
console.log(`[LemonSqueezy] Found existing webhook ${String(existingWebhook.id)} for URL ${webhookUrl}`);
|
|
120
137
|
return true;
|
|
121
138
|
}
|
|
122
139
|
}
|
|
123
140
|
}
|
|
124
141
|
catch (error) {
|
|
125
|
-
// Error checking webhooks -
|
|
126
|
-
// This could indicate API issues, but we'll try to create a new webhook
|
|
142
|
+
// Error checking webhooks - this is more serious as it could indicate API/auth issues
|
|
127
143
|
// eslint-disable-next-line no-console
|
|
128
|
-
console.
|
|
144
|
+
console.error(`[LemonSqueezy] Error checking existing webhooks for store ${storeId}: ${error instanceof Error ? error.message : 'Unknown error'}. Will attempt to create new webhook.`);
|
|
129
145
|
}
|
|
130
146
|
return false;
|
|
131
147
|
},
|
|
@@ -167,14 +183,28 @@ class LemonSqueezyTrigger {
|
|
|
167
183
|
async delete() {
|
|
168
184
|
const webhookData = this.getWorkflowStaticData('node');
|
|
169
185
|
if (webhookData.webhookId) {
|
|
186
|
+
const webhookId = String(webhookData.webhookId);
|
|
170
187
|
try {
|
|
171
|
-
await helpers_1.lemonSqueezyApiRequest.call(this, 'DELETE', `/webhooks/${
|
|
188
|
+
await helpers_1.lemonSqueezyApiRequest.call(this, 'DELETE', `/webhooks/${webhookId}`);
|
|
189
|
+
// eslint-disable-next-line no-console
|
|
190
|
+
console.log(`[LemonSqueezy] Webhook ${webhookId} deleted successfully`);
|
|
172
191
|
}
|
|
173
192
|
catch (error) {
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
193
|
+
// Check if it's a 404 (already deleted) vs other errors
|
|
194
|
+
const is404 = error &&
|
|
195
|
+
typeof error === 'object' &&
|
|
196
|
+
(error.statusCode === 404 ||
|
|
197
|
+
error.httpCode === 404);
|
|
198
|
+
if (is404) {
|
|
199
|
+
// Webhook was already deleted - this is fine
|
|
200
|
+
// eslint-disable-next-line no-console
|
|
201
|
+
console.log(`[LemonSqueezy] Webhook ${webhookId} already deleted (404)`);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// Unexpected error during deletion - log as warning
|
|
205
|
+
// eslint-disable-next-line no-console
|
|
206
|
+
console.warn(`[LemonSqueezy] Webhook ${webhookId} deletion failed: ${error instanceof Error ? error.message : 'Unknown error'}. Continuing cleanup.`);
|
|
207
|
+
}
|
|
178
208
|
}
|
|
179
209
|
delete webhookData.webhookId;
|
|
180
210
|
}
|
|
@@ -252,10 +282,13 @@ class LemonSqueezyTrigger {
|
|
|
252
282
|
const subscribedEvents = this.getNodeParameter('events');
|
|
253
283
|
if (!eventName || !subscribedEvents.includes(eventName)) {
|
|
254
284
|
// Event not subscribed, acknowledge but don't trigger workflow
|
|
285
|
+
// Log for debugging - helps identify misconfigured webhooks
|
|
286
|
+
// eslint-disable-next-line no-console
|
|
287
|
+
console.log(`[LemonSqueezy] Received event '${eventName || 'unknown'}' but not in subscribed events [${subscribedEvents.join(', ')}]. Acknowledged but not processed.`);
|
|
255
288
|
return {
|
|
256
289
|
webhookResponse: {
|
|
257
290
|
status: 200,
|
|
258
|
-
body: { received: true, processed: false },
|
|
291
|
+
body: { received: true, processed: false, event: eventName },
|
|
259
292
|
},
|
|
260
293
|
};
|
|
261
294
|
}
|
|
@@ -6,6 +6,8 @@ export declare const DEFAULT_PAGE_SIZE = 100;
|
|
|
6
6
|
export declare const MAX_RETRIES = 3;
|
|
7
7
|
export declare const RETRY_DELAY_MS = 1000;
|
|
8
8
|
export declare const RATE_LIMIT_DELAY_MS = 60000;
|
|
9
|
+
/** Default timeout for API requests in milliseconds (30 seconds) */
|
|
10
|
+
export declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
9
11
|
/**
|
|
10
12
|
* Resource to API endpoint mapping
|
|
11
13
|
*/
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
* Lemon Squeezy API constants
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.INTERVAL_TYPES = exports.PRODUCT_STATUSES = exports.PAUSE_MODES = exports.DISCOUNT_DURATION_TYPES = exports.DISCOUNT_AMOUNT_TYPES = exports.LICENSE_KEY_STATUSES = exports.CUSTOMER_STATUSES = exports.ORDER_STATUSES = exports.SUBSCRIPTION_STATUSES = exports.WEBHOOK_EVENTS = exports.RESOURCE_ID_PARAMS = exports.RESOURCE_ENDPOINTS = exports.RATE_LIMIT_DELAY_MS = exports.RETRY_DELAY_MS = exports.MAX_RETRIES = exports.DEFAULT_PAGE_SIZE = exports.API_BASE_URL = void 0;
|
|
6
|
+
exports.INTERVAL_TYPES = exports.PRODUCT_STATUSES = exports.PAUSE_MODES = exports.DISCOUNT_DURATION_TYPES = exports.DISCOUNT_AMOUNT_TYPES = exports.LICENSE_KEY_STATUSES = exports.CUSTOMER_STATUSES = exports.ORDER_STATUSES = exports.SUBSCRIPTION_STATUSES = exports.WEBHOOK_EVENTS = exports.RESOURCE_ID_PARAMS = exports.RESOURCE_ENDPOINTS = exports.DEFAULT_REQUEST_TIMEOUT_MS = exports.RATE_LIMIT_DELAY_MS = exports.RETRY_DELAY_MS = exports.MAX_RETRIES = exports.DEFAULT_PAGE_SIZE = exports.API_BASE_URL = void 0;
|
|
7
7
|
exports.API_BASE_URL = 'https://api.lemonsqueezy.com/v1';
|
|
8
8
|
exports.DEFAULT_PAGE_SIZE = 100;
|
|
9
9
|
exports.MAX_RETRIES = 3;
|
|
10
10
|
exports.RETRY_DELAY_MS = 1000;
|
|
11
11
|
exports.RATE_LIMIT_DELAY_MS = 60000;
|
|
12
|
+
/** Default timeout for API requests in milliseconds (30 seconds) */
|
|
13
|
+
exports.DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
12
14
|
/**
|
|
13
15
|
* Resource to API endpoint mapping
|
|
14
16
|
*/
|
|
@@ -167,6 +167,7 @@ export declare function isRetryableError(error: unknown): boolean;
|
|
|
167
167
|
* - Automatic authentication using stored credentials
|
|
168
168
|
* - Rate limit handling with automatic retry after delay
|
|
169
169
|
* - Exponential backoff for server errors (5xx)
|
|
170
|
+
* - Configurable request timeout (default: 30 seconds)
|
|
170
171
|
* - Detailed error messages using NodeApiError
|
|
171
172
|
*
|
|
172
173
|
* @param this - The n8n execution context
|
|
@@ -174,8 +175,9 @@ export declare function isRetryableError(error: unknown): boolean;
|
|
|
174
175
|
* @param endpoint - API endpoint path (e.g., '/v1/products')
|
|
175
176
|
* @param body - Optional request body for POST/PATCH requests
|
|
176
177
|
* @param qs - Optional query string parameters
|
|
178
|
+
* @param timeout - Request timeout in milliseconds (default: 30000)
|
|
177
179
|
* @returns The API response data
|
|
178
|
-
* @throws NodeApiError if the request fails after all retries
|
|
180
|
+
* @throws NodeApiError if the request fails after all retries or times out
|
|
179
181
|
*
|
|
180
182
|
* @example
|
|
181
183
|
* // GET request
|
|
@@ -185,8 +187,11 @@ export declare function isRetryableError(error: unknown): boolean;
|
|
|
185
187
|
* const checkout = await lemonSqueezyApiRequest.call(this, 'POST', '/v1/checkouts', {
|
|
186
188
|
* data: { type: 'checkouts', attributes: { ... } }
|
|
187
189
|
* })
|
|
190
|
+
*
|
|
191
|
+
* // Request with custom timeout (60 seconds)
|
|
192
|
+
* const data = await lemonSqueezyApiRequest.call(this, 'GET', '/v1/orders', undefined, {}, 60000)
|
|
188
193
|
*/
|
|
189
|
-
export declare function lemonSqueezyApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions, method: IHttpRequestMethods, endpoint: string, body?: IDataObject, qs?: Record<string, string | number
|
|
194
|
+
export declare function lemonSqueezyApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions, method: IHttpRequestMethods, endpoint: string, body?: IDataObject, qs?: Record<string, string | number>, timeout?: number): Promise<IDataObject>;
|
|
190
195
|
/**
|
|
191
196
|
* Makes paginated requests to fetch all items from a Lemon Squeezy API endpoint.
|
|
192
197
|
*
|
|
@@ -341,6 +341,7 @@ function isRetryableError(error) {
|
|
|
341
341
|
* - Automatic authentication using stored credentials
|
|
342
342
|
* - Rate limit handling with automatic retry after delay
|
|
343
343
|
* - Exponential backoff for server errors (5xx)
|
|
344
|
+
* - Configurable request timeout (default: 30 seconds)
|
|
344
345
|
* - Detailed error messages using NodeApiError
|
|
345
346
|
*
|
|
346
347
|
* @param this - The n8n execution context
|
|
@@ -348,8 +349,9 @@ function isRetryableError(error) {
|
|
|
348
349
|
* @param endpoint - API endpoint path (e.g., '/v1/products')
|
|
349
350
|
* @param body - Optional request body for POST/PATCH requests
|
|
350
351
|
* @param qs - Optional query string parameters
|
|
352
|
+
* @param timeout - Request timeout in milliseconds (default: 30000)
|
|
351
353
|
* @returns The API response data
|
|
352
|
-
* @throws NodeApiError if the request fails after all retries
|
|
354
|
+
* @throws NodeApiError if the request fails after all retries or times out
|
|
353
355
|
*
|
|
354
356
|
* @example
|
|
355
357
|
* // GET request
|
|
@@ -359,13 +361,17 @@ function isRetryableError(error) {
|
|
|
359
361
|
* const checkout = await lemonSqueezyApiRequest.call(this, 'POST', '/v1/checkouts', {
|
|
360
362
|
* data: { type: 'checkouts', attributes: { ... } }
|
|
361
363
|
* })
|
|
364
|
+
*
|
|
365
|
+
* // Request with custom timeout (60 seconds)
|
|
366
|
+
* const data = await lemonSqueezyApiRequest.call(this, 'GET', '/v1/orders', undefined, {}, 60000)
|
|
362
367
|
*/
|
|
363
|
-
async function lemonSqueezyApiRequest(method, endpoint, body, qs = {}) {
|
|
368
|
+
async function lemonSqueezyApiRequest(method, endpoint, body, qs = {}, timeout = constants_1.DEFAULT_REQUEST_TIMEOUT_MS) {
|
|
364
369
|
const options = {
|
|
365
370
|
method,
|
|
366
371
|
url: `${constants_1.API_BASE_URL}${endpoint}`,
|
|
367
372
|
qs,
|
|
368
373
|
json: true,
|
|
374
|
+
timeout,
|
|
369
375
|
};
|
|
370
376
|
if (body) {
|
|
371
377
|
options.body = body;
|
|
@@ -378,13 +384,17 @@ async function lemonSqueezyApiRequest(method, endpoint, body, qs = {}) {
|
|
|
378
384
|
catch (error) {
|
|
379
385
|
lastError = error;
|
|
380
386
|
if (isRateLimitError(error)) {
|
|
381
|
-
//
|
|
387
|
+
// Log rate limit for visibility
|
|
388
|
+
// eslint-disable-next-line no-console
|
|
389
|
+
console.warn(`[LemonSqueezy] Rate limited (429) on ${method} ${endpoint}. Waiting ${constants_1.RATE_LIMIT_DELAY_MS / 1000}s before retry...`);
|
|
382
390
|
await sleep(constants_1.RATE_LIMIT_DELAY_MS);
|
|
383
391
|
continue;
|
|
384
392
|
}
|
|
385
393
|
if (isRetryableError(error) && attempt < constants_1.MAX_RETRIES - 1) {
|
|
386
|
-
|
|
387
|
-
|
|
394
|
+
const delayMs = constants_1.RETRY_DELAY_MS * Math.pow(2, attempt);
|
|
395
|
+
// eslint-disable-next-line no-console
|
|
396
|
+
console.warn(`[LemonSqueezy] Retryable error on ${method} ${endpoint} (attempt ${attempt + 1}/${constants_1.MAX_RETRIES}). Retrying in ${delayMs}ms...`);
|
|
397
|
+
await sleep(delayMs);
|
|
388
398
|
continue;
|
|
389
399
|
}
|
|
390
400
|
// Non-retryable error, throw immediately
|
|
@@ -454,6 +464,8 @@ async function lemonSqueezyApiRequestAllItems(method, endpoint, qs = {}, paginat
|
|
|
454
464
|
}
|
|
455
465
|
catch (error) {
|
|
456
466
|
if (isRateLimitError(error)) {
|
|
467
|
+
// eslint-disable-next-line no-console
|
|
468
|
+
console.warn(`[LemonSqueezy] Rate limited during pagination (${returnData.length} items fetched). Waiting ${constants_1.RATE_LIMIT_DELAY_MS / 1000}s...`);
|
|
457
469
|
await sleep(constants_1.RATE_LIMIT_DELAY_MS);
|
|
458
470
|
continue;
|
|
459
471
|
}
|
|
@@ -47,8 +47,43 @@ export declare const RESOURCE_INCLUDES: Record<string, Array<{
|
|
|
47
47
|
name: string;
|
|
48
48
|
value: string;
|
|
49
49
|
}>>;
|
|
50
|
+
/**
|
|
51
|
+
* Creates a filters collection field for a resource
|
|
52
|
+
* @param resource - The resource name
|
|
53
|
+
* @param filterOptions - Array of filter field definitions
|
|
54
|
+
* @param operations - Operations where this field applies
|
|
55
|
+
*/
|
|
56
|
+
export declare function createFiltersField(resource: string, filterOptions: INodeProperties['options'], operations?: string[]): INodeProperties;
|
|
57
|
+
/**
|
|
58
|
+
* Common filter field: Store ID
|
|
59
|
+
*/
|
|
60
|
+
export declare const storeIdFilter: INodeProperties;
|
|
61
|
+
/**
|
|
62
|
+
* Common filter field: Status (generic)
|
|
63
|
+
*/
|
|
64
|
+
export declare function createStatusFilter(statusOptions: Array<{
|
|
65
|
+
name: string;
|
|
66
|
+
value: string;
|
|
67
|
+
}>): INodeProperties;
|
|
68
|
+
/**
|
|
69
|
+
* Common filter field: Email
|
|
70
|
+
*/
|
|
71
|
+
export declare const emailFilter: INodeProperties;
|
|
72
|
+
/**
|
|
73
|
+
* Common filter field: Product ID
|
|
74
|
+
*/
|
|
75
|
+
export declare const productIdFilter: INodeProperties;
|
|
76
|
+
/**
|
|
77
|
+
* Common filter field: Variant ID
|
|
78
|
+
*/
|
|
79
|
+
export declare const variantIdFilter: INodeProperties;
|
|
80
|
+
/**
|
|
81
|
+
* Common filter field: Order ID
|
|
82
|
+
*/
|
|
83
|
+
export declare const orderIdFilter: INodeProperties;
|
|
50
84
|
/**
|
|
51
85
|
* Generate advanced options field for a specific resource
|
|
86
|
+
* Includes sorting, relationship expansion, and pagination timeout
|
|
52
87
|
*/
|
|
53
88
|
export declare function createAdvancedOptionsField(resource: string, operations?: string[]): INodeProperties;
|
|
54
89
|
/**
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.discountAdvancedOptions = exports.checkoutAdvancedOptions = exports.variantAdvancedOptions = exports.productAdvancedOptions = exports.licenseKeyAdvancedOptions = exports.customerAdvancedOptions = exports.subscriptionAdvancedOptions = exports.orderAdvancedOptions = exports.RESOURCE_INCLUDES = exports.COMMON_SORT_FIELDS = exports.SORT_DIRECTIONS = void 0;
|
|
3
|
+
exports.discountAdvancedOptions = exports.checkoutAdvancedOptions = exports.variantAdvancedOptions = exports.productAdvancedOptions = exports.licenseKeyAdvancedOptions = exports.customerAdvancedOptions = exports.subscriptionAdvancedOptions = exports.orderAdvancedOptions = exports.orderIdFilter = exports.variantIdFilter = exports.productIdFilter = exports.emailFilter = exports.storeIdFilter = exports.RESOURCE_INCLUDES = exports.COMMON_SORT_FIELDS = exports.SORT_DIRECTIONS = void 0;
|
|
4
4
|
exports.createReturnAllField = createReturnAllField;
|
|
5
5
|
exports.createLimitField = createLimitField;
|
|
6
6
|
exports.createIdField = createIdField;
|
|
7
|
+
exports.createFiltersField = createFiltersField;
|
|
8
|
+
exports.createStatusFilter = createStatusFilter;
|
|
7
9
|
exports.createAdvancedOptionsField = createAdvancedOptionsField;
|
|
8
10
|
/**
|
|
9
11
|
* Shared field definitions for advanced query options
|
|
@@ -138,8 +140,91 @@ exports.RESOURCE_INCLUDES = {
|
|
|
138
140
|
{ name: 'Discount Redemptions', value: 'discount-redemptions' },
|
|
139
141
|
],
|
|
140
142
|
};
|
|
143
|
+
/**
|
|
144
|
+
* Creates a filters collection field for a resource
|
|
145
|
+
* @param resource - The resource name
|
|
146
|
+
* @param filterOptions - Array of filter field definitions
|
|
147
|
+
* @param operations - Operations where this field applies
|
|
148
|
+
*/
|
|
149
|
+
function createFiltersField(resource, filterOptions, operations = ['getAll']) {
|
|
150
|
+
return {
|
|
151
|
+
displayName: 'Filters',
|
|
152
|
+
name: 'filters',
|
|
153
|
+
type: 'collection',
|
|
154
|
+
placeholder: 'Add Filter',
|
|
155
|
+
default: {},
|
|
156
|
+
displayOptions: {
|
|
157
|
+
show: { resource: [resource], operation: operations },
|
|
158
|
+
},
|
|
159
|
+
options: filterOptions,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Common filter field: Store ID
|
|
164
|
+
*/
|
|
165
|
+
exports.storeIdFilter = {
|
|
166
|
+
displayName: 'Store ID',
|
|
167
|
+
name: 'storeId',
|
|
168
|
+
type: 'string',
|
|
169
|
+
default: '',
|
|
170
|
+
description: 'Filter by store ID',
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* Common filter field: Status (generic)
|
|
174
|
+
*/
|
|
175
|
+
function createStatusFilter(statusOptions) {
|
|
176
|
+
return {
|
|
177
|
+
displayName: 'Status',
|
|
178
|
+
name: 'status',
|
|
179
|
+
type: 'options',
|
|
180
|
+
options: statusOptions,
|
|
181
|
+
default: '',
|
|
182
|
+
description: 'Filter by status',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Common filter field: Email
|
|
187
|
+
*/
|
|
188
|
+
exports.emailFilter = {
|
|
189
|
+
displayName: 'Email',
|
|
190
|
+
name: 'email',
|
|
191
|
+
type: 'string',
|
|
192
|
+
default: '',
|
|
193
|
+
description: 'Filter by email address',
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Common filter field: Product ID
|
|
197
|
+
*/
|
|
198
|
+
exports.productIdFilter = {
|
|
199
|
+
displayName: 'Product ID',
|
|
200
|
+
name: 'productId',
|
|
201
|
+
type: 'string',
|
|
202
|
+
default: '',
|
|
203
|
+
description: 'Filter by product ID',
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* Common filter field: Variant ID
|
|
207
|
+
*/
|
|
208
|
+
exports.variantIdFilter = {
|
|
209
|
+
displayName: 'Variant ID',
|
|
210
|
+
name: 'variantId',
|
|
211
|
+
type: 'string',
|
|
212
|
+
default: '',
|
|
213
|
+
description: 'Filter by variant ID',
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* Common filter field: Order ID
|
|
217
|
+
*/
|
|
218
|
+
exports.orderIdFilter = {
|
|
219
|
+
displayName: 'Order ID',
|
|
220
|
+
name: 'orderId',
|
|
221
|
+
type: 'string',
|
|
222
|
+
default: '',
|
|
223
|
+
description: 'Filter by order ID',
|
|
224
|
+
};
|
|
141
225
|
/**
|
|
142
226
|
* Generate advanced options field for a specific resource
|
|
227
|
+
* Includes sorting, relationship expansion, and pagination timeout
|
|
143
228
|
*/
|
|
144
229
|
function createAdvancedOptionsField(resource, operations = ['getAll']) {
|
|
145
230
|
const includes = exports.RESOURCE_INCLUDES[resource] || [];
|
|
@@ -160,6 +245,14 @@ function createAdvancedOptionsField(resource, operations = ['getAll']) {
|
|
|
160
245
|
default: 'desc',
|
|
161
246
|
description: 'Direction to sort results',
|
|
162
247
|
},
|
|
248
|
+
{
|
|
249
|
+
displayName: 'Pagination Timeout (Seconds)',
|
|
250
|
+
name: 'paginationTimeout',
|
|
251
|
+
type: 'number',
|
|
252
|
+
default: 300,
|
|
253
|
+
description: 'Maximum time in seconds to wait for paginated results when using Return All. Set to 0 for no timeout.',
|
|
254
|
+
typeOptions: { minValue: 0, maxValue: 600 },
|
|
255
|
+
},
|
|
163
256
|
];
|
|
164
257
|
if (includes.length > 0) {
|
|
165
258
|
options.push({
|