n8n-nodes-lemonsqueezy 0.4.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.
@@ -1,4 +1,16 @@
1
1
  "use strict";
2
+ /**
3
+ * Lemon Squeezy API Helper Functions
4
+ *
5
+ * This module provides utility functions for:
6
+ * - Input validation (email, URL, date, etc.)
7
+ * - API request handling with retry logic
8
+ * - Webhook signature verification
9
+ * - JSON:API body construction
10
+ * - Query parameter building
11
+ *
12
+ * @module helpers
13
+ */
2
14
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
15
  if (k2 === undefined) k2 = k;
4
16
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -39,6 +51,9 @@ exports.isValidIsoDate = isValidIsoDate;
39
51
  exports.isPositiveInteger = isPositiveInteger;
40
52
  exports.validateField = validateField;
41
53
  exports.safeJsonParse = safeJsonParse;
54
+ exports.sleep = sleep;
55
+ exports.isRateLimitError = isRateLimitError;
56
+ exports.isRetryableError = isRetryableError;
42
57
  exports.lemonSqueezyApiRequest = lemonSqueezyApiRequest;
43
58
  exports.lemonSqueezyApiRequestAllItems = lemonSqueezyApiRequestAllItems;
44
59
  exports.validateRequiredFields = validateRequiredFields;
@@ -56,39 +71,146 @@ const constants_1 = require("./constants");
56
71
  // Validation Helpers
57
72
  // ============================================================================
58
73
  /**
59
- * Validate email format
74
+ * Validates email format using RFC 5322 compliant regex.
75
+ *
76
+ * The validation checks for:
77
+ * - Valid local part characters (alphanumeric and special chars)
78
+ * - Single @ symbol
79
+ * - Valid domain with proper TLD (at least one dot)
80
+ *
81
+ * @param email - The email address to validate
82
+ * @returns True if the email format is valid, false otherwise
83
+ *
84
+ * @example
85
+ * isValidEmail('user@example.com') // true
86
+ * isValidEmail('invalid') // false
87
+ * isValidEmail('user@localhost') // false (no TLD)
60
88
  */
61
89
  function isValidEmail(email) {
62
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
90
+ const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;
63
91
  return emailRegex.test(email);
64
92
  }
65
93
  /**
66
- * Validate URL format
94
+ * Validates URL format and ensures it's a safe external URL.
95
+ *
96
+ * Security features:
97
+ * - Only allows http:// and https:// protocols
98
+ * - Blocks localhost and loopback addresses (127.0.0.1, ::1, [::1])
99
+ * - Blocks private network ranges (10.x, 172.16-31.x, 192.168.x)
100
+ * - Blocks link-local addresses (169.254.x - AWS metadata endpoint)
101
+ *
102
+ * This prevents Server-Side Request Forgery (SSRF) attacks.
103
+ *
104
+ * @param url - The URL to validate
105
+ * @returns True if the URL is valid and safe, false otherwise
106
+ *
107
+ * @example
108
+ * isValidUrl('https://example.com') // true
109
+ * isValidUrl('http://localhost:3000') // false (internal)
110
+ * isValidUrl('ftp://files.example.com') // false (non-http protocol)
111
+ * isValidUrl('http://169.254.169.254') // false (AWS metadata)
67
112
  */
68
113
  function isValidUrl(url) {
69
114
  try {
70
- new URL(url);
115
+ const parsedUrl = new URL(url);
116
+ // Only allow http and https protocols (security: prevent file://, javascript:, etc.)
117
+ if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
118
+ return false;
119
+ }
120
+ // Block internal/private network URLs for SSRF protection
121
+ const hostname = parsedUrl.hostname.toLowerCase();
122
+ const blockedPatterns = [
123
+ 'localhost', // Loopback hostname
124
+ '127.0.0.1', // IPv4 loopback
125
+ '0.0.0.0', // All interfaces
126
+ '::1', // IPv6 loopback
127
+ '[::1]', // IPv6 loopback (bracketed form)
128
+ '10.', // Private Class A (10.0.0.0/8)
129
+ '172.16.', // Private Class B start (172.16.0.0/12)
130
+ '172.17.',
131
+ '172.18.',
132
+ '172.19.',
133
+ '172.20.',
134
+ '172.21.',
135
+ '172.22.',
136
+ '172.23.',
137
+ '172.24.',
138
+ '172.25.',
139
+ '172.26.',
140
+ '172.27.',
141
+ '172.28.',
142
+ '172.29.',
143
+ '172.30.',
144
+ '172.31.', // Private Class B end
145
+ '192.168.', // Private Class C (192.168.0.0/16)
146
+ '169.254.', // Link-local / APIPA (includes AWS metadata endpoint)
147
+ ];
148
+ for (const pattern of blockedPatterns) {
149
+ if (hostname === pattern || hostname.startsWith(pattern)) {
150
+ return false;
151
+ }
152
+ }
71
153
  return true;
72
154
  }
73
155
  catch {
156
+ // URL parsing failed - invalid URL
74
157
  return false;
75
158
  }
76
159
  }
77
160
  /**
78
- * Validate ISO 8601 date format
161
+ * Validates ISO 8601 date format.
162
+ *
163
+ * Accepts dates in formats like:
164
+ * - 2024-01-15
165
+ * - 2024-01-15T10:30:00Z
166
+ * - 2024-01-15T10:30:00.000Z
167
+ *
168
+ * @param dateString - The date string to validate
169
+ * @returns True if the date is valid ISO 8601 format, false otherwise
170
+ *
171
+ * @example
172
+ * isValidIsoDate('2024-01-15T10:30:00Z') // true
173
+ * isValidIsoDate('invalid') // false
174
+ * isValidIsoDate('01/15/2024') // false (no dash separator)
79
175
  */
80
176
  function isValidIsoDate(dateString) {
81
177
  const date = new Date(dateString);
82
178
  return !isNaN(date.getTime()) && dateString.includes('-');
83
179
  }
84
180
  /**
85
- * Validate that a value is a positive integer
181
+ * Validates that a value is a positive integer (greater than 0).
182
+ *
183
+ * @param value - The value to validate
184
+ * @returns True if the value is a positive integer, false otherwise
185
+ *
186
+ * @example
187
+ * isPositiveInteger(5) // true
188
+ * isPositiveInteger(0) // false
189
+ * isPositiveInteger(-1) // false
190
+ * isPositiveInteger(3.14) // false
191
+ * isPositiveInteger('5') // false (string, not number)
86
192
  */
87
193
  function isPositiveInteger(value) {
88
194
  return typeof value === 'number' && Number.isInteger(value) && value > 0;
89
195
  }
90
196
  /**
91
- * Validate field with specific type and throw descriptive error
197
+ * Validates a field value and throws a descriptive error if invalid.
198
+ *
199
+ * Supports multiple validation types:
200
+ * - 'required': Ensures value is not empty/null/undefined
201
+ * - 'email': RFC 5322 compliant email validation
202
+ * - 'url': Safe URL validation with SSRF protection
203
+ * - 'date': ISO 8601 date format validation
204
+ * - 'positiveInteger': Positive integer validation
205
+ *
206
+ * @param fieldName - The name of the field (used in error messages)
207
+ * @param value - The value to validate
208
+ * @param validationType - The type of validation to perform
209
+ * @throws Error with descriptive message if validation fails
210
+ *
211
+ * @example
212
+ * validateField('email', 'user@example.com', 'email') // passes
213
+ * validateField('email', 'invalid', 'email') // throws "email must be a valid email address"
92
214
  */
93
215
  function validateField(fieldName, value, validationType) {
94
216
  if (validationType === 'required') {
@@ -125,7 +247,20 @@ function validateField(fieldName, value, validationType) {
125
247
  }
126
248
  }
127
249
  /**
128
- * Safely parse JSON with error handling
250
+ * Safely parses a JSON string with descriptive error handling.
251
+ *
252
+ * @template T - The expected type of the parsed JSON
253
+ * @param jsonString - The JSON string to parse
254
+ * @param fieldName - The name of the field (used in error messages)
255
+ * @returns The parsed JSON object
256
+ * @throws Error if the JSON is invalid
257
+ *
258
+ * @example
259
+ * const data = safeJsonParse<{name: string}>('{"name": "test"}', 'config')
260
+ * // Returns: {name: "test"}
261
+ *
262
+ * safeJsonParse('invalid json', 'config')
263
+ * // Throws: "config contains invalid JSON"
129
264
  */
130
265
  function safeJsonParse(jsonString, fieldName) {
131
266
  try {
@@ -136,13 +271,31 @@ function safeJsonParse(jsonString, fieldName) {
136
271
  }
137
272
  }
138
273
  /**
139
- * Sleep for a specified number of milliseconds
274
+ * Pauses execution for a specified duration.
275
+ *
276
+ * Used for implementing retry delays and rate limit backoff.
277
+ *
278
+ * @param ms - The number of milliseconds to sleep
279
+ * @returns A promise that resolves after the specified duration
280
+ *
281
+ * @example
282
+ * await sleep(1000) // Wait 1 second
140
283
  */
141
284
  function sleep(ms) {
142
285
  return new Promise((resolve) => setTimeout(resolve, ms));
143
286
  }
144
287
  /**
145
- * Check if error is a rate limit error
288
+ * Checks if an error is a rate limit error (HTTP 429).
289
+ *
290
+ * Handles both direct statusCode and nested response.statusCode patterns.
291
+ *
292
+ * @param error - The error object to check
293
+ * @returns True if the error is a rate limit error, false otherwise
294
+ *
295
+ * @example
296
+ * isRateLimitError({ statusCode: 429 }) // true
297
+ * isRateLimitError({ response: { statusCode: 429 } }) // true
298
+ * isRateLimitError({ statusCode: 500 }) // false
146
299
  */
147
300
  function isRateLimitError(error) {
148
301
  var _a;
@@ -153,7 +306,19 @@ function isRateLimitError(error) {
153
306
  return false;
154
307
  }
155
308
  /**
156
- * Check if error is retryable (5xx errors or network errors)
309
+ * Checks if an error is retryable (5xx server errors or network errors).
310
+ *
311
+ * Retryable conditions:
312
+ * - HTTP 5xx status codes (500-599)
313
+ * - Network errors: ECONNRESET, ETIMEDOUT, ECONNREFUSED
314
+ *
315
+ * @param error - The error object to check
316
+ * @returns True if the error is retryable, false otherwise
317
+ *
318
+ * @example
319
+ * isRetryableError({ statusCode: 503 }) // true (server error)
320
+ * isRetryableError({ code: 'ECONNRESET' }) // true (network error)
321
+ * isRetryableError({ statusCode: 404 }) // false (client error)
157
322
  */
158
323
  function isRetryableError(error) {
159
324
  var _a;
@@ -170,14 +335,43 @@ function isRetryableError(error) {
170
335
  return false;
171
336
  }
172
337
  /**
173
- * Make an authenticated request to the Lemon Squeezy API with retry logic
338
+ * Makes an authenticated request to the Lemon Squeezy API with retry logic.
339
+ *
340
+ * Features:
341
+ * - Automatic authentication using stored credentials
342
+ * - Rate limit handling with automatic retry after delay
343
+ * - Exponential backoff for server errors (5xx)
344
+ * - Configurable request timeout (default: 30 seconds)
345
+ * - Detailed error messages using NodeApiError
346
+ *
347
+ * @param this - The n8n execution context
348
+ * @param method - HTTP method (GET, POST, PATCH, DELETE)
349
+ * @param endpoint - API endpoint path (e.g., '/v1/products')
350
+ * @param body - Optional request body for POST/PATCH requests
351
+ * @param qs - Optional query string parameters
352
+ * @param timeout - Request timeout in milliseconds (default: 30000)
353
+ * @returns The API response data
354
+ * @throws NodeApiError if the request fails after all retries or times out
355
+ *
356
+ * @example
357
+ * // GET request
358
+ * const product = await lemonSqueezyApiRequest.call(this, 'GET', '/v1/products/123')
359
+ *
360
+ * // POST request with body
361
+ * const checkout = await lemonSqueezyApiRequest.call(this, 'POST', '/v1/checkouts', {
362
+ * data: { type: 'checkouts', attributes: { ... } }
363
+ * })
364
+ *
365
+ * // Request with custom timeout (60 seconds)
366
+ * const data = await lemonSqueezyApiRequest.call(this, 'GET', '/v1/orders', undefined, {}, 60000)
174
367
  */
175
- async function lemonSqueezyApiRequest(method, endpoint, body, qs = {}) {
368
+ async function lemonSqueezyApiRequest(method, endpoint, body, qs = {}, timeout = constants_1.DEFAULT_REQUEST_TIMEOUT_MS) {
176
369
  const options = {
177
370
  method,
178
371
  url: `${constants_1.API_BASE_URL}${endpoint}`,
179
372
  qs,
180
373
  json: true,
374
+ timeout,
181
375
  };
182
376
  if (body) {
183
377
  options.body = body;
@@ -190,13 +384,17 @@ async function lemonSqueezyApiRequest(method, endpoint, body, qs = {}) {
190
384
  catch (error) {
191
385
  lastError = error;
192
386
  if (isRateLimitError(error)) {
193
- // Wait for rate limit to reset (usually 60 seconds)
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...`);
194
390
  await sleep(constants_1.RATE_LIMIT_DELAY_MS);
195
391
  continue;
196
392
  }
197
393
  if (isRetryableError(error) && attempt < constants_1.MAX_RETRIES - 1) {
198
- // Exponential backoff for retryable errors
199
- await sleep(constants_1.RETRY_DELAY_MS * Math.pow(2, attempt));
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);
200
398
  continue;
201
399
  }
202
400
  // Non-retryable error, throw immediately
@@ -211,14 +409,49 @@ async function lemonSqueezyApiRequest(method, endpoint, body, qs = {}) {
211
409
  });
212
410
  }
213
411
  /**
214
- * Make paginated requests to fetch all items
412
+ * Makes paginated requests to fetch all items from a Lemon Squeezy API endpoint.
413
+ *
414
+ * Automatically handles pagination by following 'next' links until all items
415
+ * are retrieved. Includes rate limit handling for long-running fetches.
416
+ *
417
+ * Features:
418
+ * - Optional maxItems limit to prevent memory issues with large datasets
419
+ * - Optional timeout to prevent long-running requests
420
+ * - Rate limit handling with automatic retry
421
+ *
422
+ * @param this - The n8n execution context
423
+ * @param method - HTTP method (typically 'GET')
424
+ * @param endpoint - API endpoint path (e.g., '/v1/products')
425
+ * @param qs - Optional query string parameters (filters, sorting, etc.)
426
+ * @param paginationOptions - Optional pagination configuration
427
+ * @returns Array of all items from all pages (up to maxItems if specified)
428
+ * @throws NodeApiError if any request fails or timeout is exceeded
429
+ *
430
+ * @example
431
+ * // Fetch all products with filtering
432
+ * const products = await lemonSqueezyApiRequestAllItems.call(
433
+ * this, 'GET', '/v1/products', { 'filter[store_id]': 123 }
434
+ * )
435
+ *
436
+ * // Fetch with limits
437
+ * const products = await lemonSqueezyApiRequestAllItems.call(
438
+ * this, 'GET', '/v1/products', {}, { maxItems: 1000, timeout: 60000 }
439
+ * )
215
440
  */
216
- async function lemonSqueezyApiRequestAllItems(method, endpoint, qs = {}) {
441
+ async function lemonSqueezyApiRequestAllItems(method, endpoint, qs = {}, paginationOptions = {}) {
217
442
  var _a;
218
443
  const returnData = [];
219
444
  let nextPageUrl = `${constants_1.API_BASE_URL}${endpoint}`;
220
- qs['page[size]'] = constants_1.DEFAULT_PAGE_SIZE;
445
+ const { maxItems, timeout = 300000, pageSize = constants_1.DEFAULT_PAGE_SIZE } = paginationOptions;
446
+ const startTime = Date.now();
447
+ qs['page[size]'] = pageSize;
221
448
  do {
449
+ // Check timeout
450
+ if (Date.now() - startTime > timeout) {
451
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), {}, {
452
+ message: `Pagination timeout exceeded (${timeout}ms). Retrieved ${returnData.length} items before timeout.`,
453
+ });
454
+ }
222
455
  const options = {
223
456
  method,
224
457
  url: nextPageUrl,
@@ -231,6 +464,8 @@ async function lemonSqueezyApiRequestAllItems(method, endpoint, qs = {}) {
231
464
  }
232
465
  catch (error) {
233
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...`);
234
469
  await sleep(constants_1.RATE_LIMIT_DELAY_MS);
235
470
  continue;
236
471
  }
@@ -238,7 +473,12 @@ async function lemonSqueezyApiRequestAllItems(method, endpoint, qs = {}) {
238
473
  message: getErrorMessage(error),
239
474
  });
240
475
  }
241
- returnData.push(...responseData.data);
476
+ const pageData = responseData.data;
477
+ returnData.push(...pageData);
478
+ // Check maxItems limit
479
+ if (maxItems && returnData.length >= maxItems) {
480
+ return returnData.slice(0, maxItems);
481
+ }
242
482
  nextPageUrl = ((_a = responseData.links) === null || _a === void 0 ? void 0 : _a.next) || null;
243
483
  } while (nextPageUrl);
244
484
  return returnData;
@@ -293,7 +533,15 @@ function getErrorMessage(error) {
293
533
  return 'An unknown error occurred';
294
534
  }
295
535
  /**
296
- * Validate required fields before making API request
536
+ * Validates that all required fields are present and non-empty.
537
+ *
538
+ * @param fields - Object containing field values to validate
539
+ * @param requiredFields - Array of field names that are required
540
+ * @throws Error listing all missing fields if any are empty
541
+ *
542
+ * @example
543
+ * validateRequiredFields({ name: 'Test', email: '' }, ['name', 'email'])
544
+ * // Throws: "Missing required fields: email"
297
545
  */
298
546
  function validateRequiredFields(fields, requiredFields) {
299
547
  const missingFields = [];
@@ -307,7 +555,17 @@ function validateRequiredFields(fields, requiredFields) {
307
555
  }
308
556
  }
309
557
  /**
310
- * Build filter query string parameters
558
+ * Builds filter query string parameters for Lemon Squeezy API.
559
+ *
560
+ * Converts camelCase field names to snake_case and wraps them in
561
+ * filter[] syntax as required by the API.
562
+ *
563
+ * @param filters - Object containing filter key-value pairs
564
+ * @returns Query string parameters object for API request
565
+ *
566
+ * @example
567
+ * buildFilterParams({ storeId: 123, status: 'active' })
568
+ * // Returns: { 'filter[store_id]': 123, 'filter[status]': 'active' }
311
569
  */
312
570
  function buildFilterParams(filters) {
313
571
  const qs = {};
@@ -321,7 +579,24 @@ function buildFilterParams(filters) {
321
579
  return qs;
322
580
  }
323
581
  /**
324
- * Build JSON:API request body
582
+ * Builds a JSON:API compliant request body for create/update operations.
583
+ *
584
+ * Constructs the proper structure expected by Lemon Squeezy API:
585
+ * - data.type: Resource type (e.g., 'checkouts', 'customers')
586
+ * - data.attributes: Resource attributes
587
+ * - data.relationships: Optional related resource references
588
+ * - data.id: Optional resource ID (for updates)
589
+ *
590
+ * @param type - The JSON:API resource type
591
+ * @param attributes - Resource attributes to include
592
+ * @param relationships - Optional relationships to other resources
593
+ * @param id - Optional resource ID (required for updates)
594
+ * @returns Properly structured JSON:API request body
595
+ *
596
+ * @example
597
+ * buildJsonApiBody('customers', { name: 'John', email: 'john@example.com' },
598
+ * { store: { type: 'stores', id: '123' } })
599
+ * // Returns: { data: { type: 'customers', attributes: {...}, relationships: {...} } }
325
600
  */
326
601
  function buildJsonApiBody(type, attributes, relationships, id) {
327
602
  const body = {
@@ -348,7 +623,21 @@ function buildJsonApiBody(type, attributes, relationships, id) {
348
623
  return body;
349
624
  }
350
625
  /**
351
- * Parse webhook signature for validation
626
+ * Verifies a webhook signature using HMAC-SHA256.
627
+ *
628
+ * Uses timing-safe comparison to prevent timing attacks.
629
+ *
630
+ * @param payload - The raw webhook payload string
631
+ * @param signature - The signature from X-Signature header
632
+ * @param secret - The webhook signing secret
633
+ * @returns True if signature is valid, false otherwise
634
+ *
635
+ * @example
636
+ * const isValid = verifyWebhookSignature(
637
+ * '{"data": {...}}',
638
+ * 'abc123signature',
639
+ * 'webhook_secret_key'
640
+ * )
352
641
  */
353
642
  function verifyWebhookSignature(payload, signature, secret) {
354
643
  const hmac = crypto.createHmac('sha256', secret);
@@ -364,7 +653,20 @@ function verifyWebhookSignature(payload, signature, secret) {
364
653
  // Advanced Query Helpers
365
654
  // ============================================================================
366
655
  /**
367
- * Build query params with relationship expansion (include)
656
+ * Builds query parameters for including related resources in API responses.
657
+ *
658
+ * Uses the JSON:API include parameter to fetch related resources in a single
659
+ * request, reducing the number of API calls needed.
660
+ *
661
+ * @param includes - Array of relationship names to include
662
+ * @returns Query string parameters object with 'include' key
663
+ *
664
+ * @example
665
+ * buildIncludeParams(['store', 'customer', 'order-items'])
666
+ * // Returns: { include: 'store,customer,order-items' }
667
+ *
668
+ * buildIncludeParams([])
669
+ * // Returns: {}
368
670
  */
369
671
  function buildIncludeParams(includes) {
370
672
  if (includes.length === 0) {
@@ -373,7 +675,27 @@ function buildIncludeParams(includes) {
373
675
  return { include: includes.join(',') };
374
676
  }
375
677
  /**
376
- * Build advanced filter params with date range support
678
+ * Builds advanced filter parameters with support for date ranges and sorting.
679
+ *
680
+ * Features:
681
+ * - Converts camelCase to snake_case for API compatibility
682
+ * - Handles date range filters with _after/_before suffixes
683
+ * - Adds sorting with ascending/descending direction
684
+ *
685
+ * @param filters - Object containing filter key-value pairs
686
+ * @param options - Optional configuration for date fields and sorting
687
+ * @param options.dateFields - Array of field names that are date ranges
688
+ * @param options.sortField - Field name to sort by
689
+ * @param options.sortDirection - Sort direction ('asc' or 'desc')
690
+ * @returns Query string parameters object for API request
691
+ *
692
+ * @example
693
+ * buildAdvancedFilterParams(
694
+ * { status: 'active', createdAt: { from: '2024-01-01', to: '2024-12-31' } },
695
+ * { dateFields: ['createdAt'], sortField: 'created_at', sortDirection: 'desc' }
696
+ * )
697
+ * // Returns: { 'filter[status]': 'active', 'filter[created_at_after]': '2024-01-01',
698
+ * // 'filter[created_at_before]': '2024-12-31', sort: '-created_at' }
377
699
  */
378
700
  function buildAdvancedFilterParams(filters, options) {
379
701
  var _a;
@@ -412,7 +734,19 @@ function buildAdvancedFilterParams(filters, options) {
412
734
  return qs;
413
735
  }
414
736
  /**
415
- * Extract data from JSON:API response with proper typing
737
+ * Extracts the 'data' field from a JSON:API response with proper typing.
738
+ *
739
+ * JSON:API responses wrap the actual resource data in a 'data' field.
740
+ * This helper extracts it while preserving type information.
741
+ *
742
+ * @template T - The expected type of the extracted data
743
+ * @param response - The full JSON:API response object
744
+ * @returns The extracted data, or undefined if not present
745
+ *
746
+ * @example
747
+ * const response = { data: { id: '1', type: 'products', attributes: {...} } }
748
+ * const product = extractResponseData<Product>(response)
749
+ * // Returns: { id: '1', type: 'products', attributes: {...} }
416
750
  */
417
751
  function extractResponseData(response) {
418
752
  if (!response || typeof response !== 'object') {
@@ -421,7 +755,21 @@ function extractResponseData(response) {
421
755
  return response.data;
422
756
  }
423
757
  /**
424
- * Extract included resources from JSON:API response
758
+ * Extracts included related resources from a JSON:API response.
759
+ *
760
+ * When using the 'include' query parameter, related resources are returned
761
+ * in the 'included' array of the response. This helper extracts them.
762
+ *
763
+ * @param response - The full JSON:API response object
764
+ * @returns Array of included resources, or empty array if none
765
+ *
766
+ * @example
767
+ * const response = {
768
+ * data: {...},
769
+ * included: [{ id: '1', type: 'stores', attributes: {...} }]
770
+ * }
771
+ * const stores = extractIncludedResources(response)
772
+ * // Returns: [{ id: '1', type: 'stores', attributes: {...} }]
425
773
  */
426
774
  function extractIncludedResources(response) {
427
775
  if (!response || typeof response !== 'object') {
@@ -49,6 +49,7 @@ Object.defineProperty(exports, "usageRecordFields", { enumerable: true, get: fun
49
49
  const user_1 = require("./user");
50
50
  Object.defineProperty(exports, "userOperations", { enumerable: true, get: function () { return user_1.userOperations; } });
51
51
  Object.defineProperty(exports, "userFields", { enumerable: true, get: function () { return user_1.userFields; } });
52
+ const shared_1 = require("./shared");
52
53
  exports.resourceProperty = {
53
54
  displayName: 'Resource',
54
55
  name: 'resource',
@@ -94,18 +95,26 @@ exports.allOperations = [
94
95
  ];
95
96
  exports.allFields = [
96
97
  ...product_1.productFields,
98
+ shared_1.productAdvancedOptions,
97
99
  ...order_1.orderFields,
100
+ shared_1.orderAdvancedOptions,
98
101
  ...orderItem_1.orderItemFields,
99
102
  ...subscription_1.subscriptionFields,
103
+ shared_1.subscriptionAdvancedOptions,
100
104
  ...subscriptionInvoice_1.subscriptionInvoiceFields,
101
105
  ...customer_1.customerFields,
106
+ shared_1.customerAdvancedOptions,
102
107
  ...licenseKey_1.licenseKeyFields,
108
+ shared_1.licenseKeyAdvancedOptions,
103
109
  ...licenseKeyInstance_1.licenseKeyInstanceFields,
104
110
  ...discount_1.discountFields,
111
+ shared_1.discountAdvancedOptions,
105
112
  ...discountRedemption_1.discountRedemptionFields,
106
113
  ...store_1.storeFields,
107
114
  ...variant_1.variantFields,
115
+ shared_1.variantAdvancedOptions,
108
116
  ...checkout_1.checkoutFields,
117
+ shared_1.checkoutAdvancedOptions,
109
118
  ...webhook_1.webhookFields,
110
119
  ...usageRecord_1.usageRecordFields,
111
120
  ...user_1.userFields,