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,61 +1,335 @@
1
+ /**
2
+ * Lemon Squeezy API Helper Functions
3
+ *
4
+ * This module provides utility functions for:
5
+ * - Input validation (email, URL, date, etc.)
6
+ * - API request handling with retry logic
7
+ * - Webhook signature verification
8
+ * - JSON:API body construction
9
+ * - Query parameter building
10
+ *
11
+ * @module helpers
12
+ */
1
13
  import type { IExecuteFunctions, IWebhookFunctions, IHookFunctions, IHttpRequestMethods, IDataObject } from 'n8n-workflow';
14
+ import type { PaginationOptions } from './types';
2
15
  /**
3
- * Validate email format
16
+ * Validates email format using RFC 5322 compliant regex.
17
+ *
18
+ * The validation checks for:
19
+ * - Valid local part characters (alphanumeric and special chars)
20
+ * - Single @ symbol
21
+ * - Valid domain with proper TLD (at least one dot)
22
+ *
23
+ * @param email - The email address to validate
24
+ * @returns True if the email format is valid, false otherwise
25
+ *
26
+ * @example
27
+ * isValidEmail('user@example.com') // true
28
+ * isValidEmail('invalid') // false
29
+ * isValidEmail('user@localhost') // false (no TLD)
4
30
  */
5
31
  export declare function isValidEmail(email: string): boolean;
6
32
  /**
7
- * Validate URL format
33
+ * Validates URL format and ensures it's a safe external URL.
34
+ *
35
+ * Security features:
36
+ * - Only allows http:// and https:// protocols
37
+ * - Blocks localhost and loopback addresses (127.0.0.1, ::1, [::1])
38
+ * - Blocks private network ranges (10.x, 172.16-31.x, 192.168.x)
39
+ * - Blocks link-local addresses (169.254.x - AWS metadata endpoint)
40
+ *
41
+ * This prevents Server-Side Request Forgery (SSRF) attacks.
42
+ *
43
+ * @param url - The URL to validate
44
+ * @returns True if the URL is valid and safe, false otherwise
45
+ *
46
+ * @example
47
+ * isValidUrl('https://example.com') // true
48
+ * isValidUrl('http://localhost:3000') // false (internal)
49
+ * isValidUrl('ftp://files.example.com') // false (non-http protocol)
50
+ * isValidUrl('http://169.254.169.254') // false (AWS metadata)
8
51
  */
9
52
  export declare function isValidUrl(url: string): boolean;
10
53
  /**
11
- * Validate ISO 8601 date format
54
+ * Validates ISO 8601 date format.
55
+ *
56
+ * Accepts dates in formats like:
57
+ * - 2024-01-15
58
+ * - 2024-01-15T10:30:00Z
59
+ * - 2024-01-15T10:30:00.000Z
60
+ *
61
+ * @param dateString - The date string to validate
62
+ * @returns True if the date is valid ISO 8601 format, false otherwise
63
+ *
64
+ * @example
65
+ * isValidIsoDate('2024-01-15T10:30:00Z') // true
66
+ * isValidIsoDate('invalid') // false
67
+ * isValidIsoDate('01/15/2024') // false (no dash separator)
12
68
  */
13
69
  export declare function isValidIsoDate(dateString: string): boolean;
14
70
  /**
15
- * Validate that a value is a positive integer
71
+ * Validates that a value is a positive integer (greater than 0).
72
+ *
73
+ * @param value - The value to validate
74
+ * @returns True if the value is a positive integer, false otherwise
75
+ *
76
+ * @example
77
+ * isPositiveInteger(5) // true
78
+ * isPositiveInteger(0) // false
79
+ * isPositiveInteger(-1) // false
80
+ * isPositiveInteger(3.14) // false
81
+ * isPositiveInteger('5') // false (string, not number)
16
82
  */
17
83
  export declare function isPositiveInteger(value: unknown): boolean;
18
84
  /**
19
- * Validate field with specific type and throw descriptive error
85
+ * Validates a field value and throws a descriptive error if invalid.
86
+ *
87
+ * Supports multiple validation types:
88
+ * - 'required': Ensures value is not empty/null/undefined
89
+ * - 'email': RFC 5322 compliant email validation
90
+ * - 'url': Safe URL validation with SSRF protection
91
+ * - 'date': ISO 8601 date format validation
92
+ * - 'positiveInteger': Positive integer validation
93
+ *
94
+ * @param fieldName - The name of the field (used in error messages)
95
+ * @param value - The value to validate
96
+ * @param validationType - The type of validation to perform
97
+ * @throws Error with descriptive message if validation fails
98
+ *
99
+ * @example
100
+ * validateField('email', 'user@example.com', 'email') // passes
101
+ * validateField('email', 'invalid', 'email') // throws "email must be a valid email address"
20
102
  */
21
103
  export declare function validateField(fieldName: string, value: unknown, validationType: 'email' | 'url' | 'date' | 'positiveInteger' | 'required'): void;
22
104
  /**
23
- * Safely parse JSON with error handling
105
+ * Safely parses a JSON string with descriptive error handling.
106
+ *
107
+ * @template T - The expected type of the parsed JSON
108
+ * @param jsonString - The JSON string to parse
109
+ * @param fieldName - The name of the field (used in error messages)
110
+ * @returns The parsed JSON object
111
+ * @throws Error if the JSON is invalid
112
+ *
113
+ * @example
114
+ * const data = safeJsonParse<{name: string}>('{"name": "test"}', 'config')
115
+ * // Returns: {name: "test"}
116
+ *
117
+ * safeJsonParse('invalid json', 'config')
118
+ * // Throws: "config contains invalid JSON"
24
119
  */
25
120
  export declare function safeJsonParse<T = unknown>(jsonString: string, fieldName: string): T;
26
121
  /**
27
- * Make an authenticated request to the Lemon Squeezy API with retry logic
122
+ * Pauses execution for a specified duration.
123
+ *
124
+ * Used for implementing retry delays and rate limit backoff.
125
+ *
126
+ * @param ms - The number of milliseconds to sleep
127
+ * @returns A promise that resolves after the specified duration
128
+ *
129
+ * @example
130
+ * await sleep(1000) // Wait 1 second
131
+ */
132
+ export declare function sleep(ms: number): Promise<void>;
133
+ /**
134
+ * Checks if an error is a rate limit error (HTTP 429).
135
+ *
136
+ * Handles both direct statusCode and nested response.statusCode patterns.
137
+ *
138
+ * @param error - The error object to check
139
+ * @returns True if the error is a rate limit error, false otherwise
140
+ *
141
+ * @example
142
+ * isRateLimitError({ statusCode: 429 }) // true
143
+ * isRateLimitError({ response: { statusCode: 429 } }) // true
144
+ * isRateLimitError({ statusCode: 500 }) // false
145
+ */
146
+ export declare function isRateLimitError(error: unknown): boolean;
147
+ /**
148
+ * Checks if an error is retryable (5xx server errors or network errors).
149
+ *
150
+ * Retryable conditions:
151
+ * - HTTP 5xx status codes (500-599)
152
+ * - Network errors: ECONNRESET, ETIMEDOUT, ECONNREFUSED
153
+ *
154
+ * @param error - The error object to check
155
+ * @returns True if the error is retryable, false otherwise
156
+ *
157
+ * @example
158
+ * isRetryableError({ statusCode: 503 }) // true (server error)
159
+ * isRetryableError({ code: 'ECONNRESET' }) // true (network error)
160
+ * isRetryableError({ statusCode: 404 }) // false (client error)
161
+ */
162
+ export declare function isRetryableError(error: unknown): boolean;
163
+ /**
164
+ * Makes an authenticated request to the Lemon Squeezy API with retry logic.
165
+ *
166
+ * Features:
167
+ * - Automatic authentication using stored credentials
168
+ * - Rate limit handling with automatic retry after delay
169
+ * - Exponential backoff for server errors (5xx)
170
+ * - Configurable request timeout (default: 30 seconds)
171
+ * - Detailed error messages using NodeApiError
172
+ *
173
+ * @param this - The n8n execution context
174
+ * @param method - HTTP method (GET, POST, PATCH, DELETE)
175
+ * @param endpoint - API endpoint path (e.g., '/v1/products')
176
+ * @param body - Optional request body for POST/PATCH requests
177
+ * @param qs - Optional query string parameters
178
+ * @param timeout - Request timeout in milliseconds (default: 30000)
179
+ * @returns The API response data
180
+ * @throws NodeApiError if the request fails after all retries or times out
181
+ *
182
+ * @example
183
+ * // GET request
184
+ * const product = await lemonSqueezyApiRequest.call(this, 'GET', '/v1/products/123')
185
+ *
186
+ * // POST request with body
187
+ * const checkout = await lemonSqueezyApiRequest.call(this, 'POST', '/v1/checkouts', {
188
+ * data: { type: 'checkouts', attributes: { ... } }
189
+ * })
190
+ *
191
+ * // Request with custom timeout (60 seconds)
192
+ * const data = await lemonSqueezyApiRequest.call(this, 'GET', '/v1/orders', undefined, {}, 60000)
28
193
  */
29
- export declare function lemonSqueezyApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions, method: IHttpRequestMethods, endpoint: string, body?: IDataObject, qs?: Record<string, string | number>): Promise<IDataObject>;
194
+ export declare function lemonSqueezyApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions, method: IHttpRequestMethods, endpoint: string, body?: IDataObject, qs?: Record<string, string | number>, timeout?: number): Promise<IDataObject>;
30
195
  /**
31
- * Make paginated requests to fetch all items
196
+ * Makes paginated requests to fetch all items from a Lemon Squeezy API endpoint.
197
+ *
198
+ * Automatically handles pagination by following 'next' links until all items
199
+ * are retrieved. Includes rate limit handling for long-running fetches.
200
+ *
201
+ * Features:
202
+ * - Optional maxItems limit to prevent memory issues with large datasets
203
+ * - Optional timeout to prevent long-running requests
204
+ * - Rate limit handling with automatic retry
205
+ *
206
+ * @param this - The n8n execution context
207
+ * @param method - HTTP method (typically 'GET')
208
+ * @param endpoint - API endpoint path (e.g., '/v1/products')
209
+ * @param qs - Optional query string parameters (filters, sorting, etc.)
210
+ * @param paginationOptions - Optional pagination configuration
211
+ * @returns Array of all items from all pages (up to maxItems if specified)
212
+ * @throws NodeApiError if any request fails or timeout is exceeded
213
+ *
214
+ * @example
215
+ * // Fetch all products with filtering
216
+ * const products = await lemonSqueezyApiRequestAllItems.call(
217
+ * this, 'GET', '/v1/products', { 'filter[store_id]': 123 }
218
+ * )
219
+ *
220
+ * // Fetch with limits
221
+ * const products = await lemonSqueezyApiRequestAllItems.call(
222
+ * this, 'GET', '/v1/products', {}, { maxItems: 1000, timeout: 60000 }
223
+ * )
32
224
  */
33
- export declare function lemonSqueezyApiRequestAllItems(this: IExecuteFunctions, method: IHttpRequestMethods, endpoint: string, qs?: Record<string, string | number>): Promise<IDataObject[]>;
225
+ export declare function lemonSqueezyApiRequestAllItems(this: IExecuteFunctions, method: IHttpRequestMethods, endpoint: string, qs?: Record<string, string | number>, paginationOptions?: PaginationOptions): Promise<IDataObject[]>;
34
226
  /**
35
- * Validate required fields before making API request
227
+ * Validates that all required fields are present and non-empty.
228
+ *
229
+ * @param fields - Object containing field values to validate
230
+ * @param requiredFields - Array of field names that are required
231
+ * @throws Error listing all missing fields if any are empty
232
+ *
233
+ * @example
234
+ * validateRequiredFields({ name: 'Test', email: '' }, ['name', 'email'])
235
+ * // Throws: "Missing required fields: email"
36
236
  */
37
237
  export declare function validateRequiredFields(fields: Record<string, unknown>, requiredFields: string[]): void;
38
238
  /**
39
- * Build filter query string parameters
239
+ * Builds filter query string parameters for Lemon Squeezy API.
240
+ *
241
+ * Converts camelCase field names to snake_case and wraps them in
242
+ * filter[] syntax as required by the API.
243
+ *
244
+ * @param filters - Object containing filter key-value pairs
245
+ * @returns Query string parameters object for API request
246
+ *
247
+ * @example
248
+ * buildFilterParams({ storeId: 123, status: 'active' })
249
+ * // Returns: { 'filter[store_id]': 123, 'filter[status]': 'active' }
40
250
  */
41
251
  export declare function buildFilterParams(filters: IDataObject): Record<string, string | number>;
42
252
  /**
43
- * Build JSON:API request body
253
+ * Builds a JSON:API compliant request body for create/update operations.
254
+ *
255
+ * Constructs the proper structure expected by Lemon Squeezy API:
256
+ * - data.type: Resource type (e.g., 'checkouts', 'customers')
257
+ * - data.attributes: Resource attributes
258
+ * - data.relationships: Optional related resource references
259
+ * - data.id: Optional resource ID (for updates)
260
+ *
261
+ * @param type - The JSON:API resource type
262
+ * @param attributes - Resource attributes to include
263
+ * @param relationships - Optional relationships to other resources
264
+ * @param id - Optional resource ID (required for updates)
265
+ * @returns Properly structured JSON:API request body
266
+ *
267
+ * @example
268
+ * buildJsonApiBody('customers', { name: 'John', email: 'john@example.com' },
269
+ * { store: { type: 'stores', id: '123' } })
270
+ * // Returns: { data: { type: 'customers', attributes: {...}, relationships: {...} } }
44
271
  */
45
272
  export declare function buildJsonApiBody(type: string, attributes: IDataObject, relationships?: Record<string, {
46
273
  type: string;
47
274
  id: string;
48
275
  }>, id?: string): IDataObject;
49
276
  /**
50
- * Parse webhook signature for validation
277
+ * Verifies a webhook signature using HMAC-SHA256.
278
+ *
279
+ * Uses timing-safe comparison to prevent timing attacks.
280
+ *
281
+ * @param payload - The raw webhook payload string
282
+ * @param signature - The signature from X-Signature header
283
+ * @param secret - The webhook signing secret
284
+ * @returns True if signature is valid, false otherwise
285
+ *
286
+ * @example
287
+ * const isValid = verifyWebhookSignature(
288
+ * '{"data": {...}}',
289
+ * 'abc123signature',
290
+ * 'webhook_secret_key'
291
+ * )
51
292
  */
52
293
  export declare function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean;
53
294
  /**
54
- * Build query params with relationship expansion (include)
295
+ * Builds query parameters for including related resources in API responses.
296
+ *
297
+ * Uses the JSON:API include parameter to fetch related resources in a single
298
+ * request, reducing the number of API calls needed.
299
+ *
300
+ * @param includes - Array of relationship names to include
301
+ * @returns Query string parameters object with 'include' key
302
+ *
303
+ * @example
304
+ * buildIncludeParams(['store', 'customer', 'order-items'])
305
+ * // Returns: { include: 'store,customer,order-items' }
306
+ *
307
+ * buildIncludeParams([])
308
+ * // Returns: {}
55
309
  */
56
310
  export declare function buildIncludeParams(includes: string[]): Record<string, string>;
57
311
  /**
58
- * Build advanced filter params with date range support
312
+ * Builds advanced filter parameters with support for date ranges and sorting.
313
+ *
314
+ * Features:
315
+ * - Converts camelCase to snake_case for API compatibility
316
+ * - Handles date range filters with _after/_before suffixes
317
+ * - Adds sorting with ascending/descending direction
318
+ *
319
+ * @param filters - Object containing filter key-value pairs
320
+ * @param options - Optional configuration for date fields and sorting
321
+ * @param options.dateFields - Array of field names that are date ranges
322
+ * @param options.sortField - Field name to sort by
323
+ * @param options.sortDirection - Sort direction ('asc' or 'desc')
324
+ * @returns Query string parameters object for API request
325
+ *
326
+ * @example
327
+ * buildAdvancedFilterParams(
328
+ * { status: 'active', createdAt: { from: '2024-01-01', to: '2024-12-31' } },
329
+ * { dateFields: ['createdAt'], sortField: 'created_at', sortDirection: 'desc' }
330
+ * )
331
+ * // Returns: { 'filter[status]': 'active', 'filter[created_at_after]': '2024-01-01',
332
+ * // 'filter[created_at_before]': '2024-12-31', sort: '-created_at' }
59
333
  */
60
334
  export declare function buildAdvancedFilterParams(filters: IDataObject, options?: {
61
335
  dateFields?: string[];
@@ -63,10 +337,36 @@ export declare function buildAdvancedFilterParams(filters: IDataObject, options?
63
337
  sortDirection?: 'asc' | 'desc';
64
338
  }): Record<string, string | number>;
65
339
  /**
66
- * Extract data from JSON:API response with proper typing
340
+ * Extracts the 'data' field from a JSON:API response with proper typing.
341
+ *
342
+ * JSON:API responses wrap the actual resource data in a 'data' field.
343
+ * This helper extracts it while preserving type information.
344
+ *
345
+ * @template T - The expected type of the extracted data
346
+ * @param response - The full JSON:API response object
347
+ * @returns The extracted data, or undefined if not present
348
+ *
349
+ * @example
350
+ * const response = { data: { id: '1', type: 'products', attributes: {...} } }
351
+ * const product = extractResponseData<Product>(response)
352
+ * // Returns: { id: '1', type: 'products', attributes: {...} }
67
353
  */
68
354
  export declare function extractResponseData<T = IDataObject>(response: IDataObject): T | T[] | undefined;
69
355
  /**
70
- * Extract included resources from JSON:API response
356
+ * Extracts included related resources from a JSON:API response.
357
+ *
358
+ * When using the 'include' query parameter, related resources are returned
359
+ * in the 'included' array of the response. This helper extracts them.
360
+ *
361
+ * @param response - The full JSON:API response object
362
+ * @returns Array of included resources, or empty array if none
363
+ *
364
+ * @example
365
+ * const response = {
366
+ * data: {...},
367
+ * included: [{ id: '1', type: 'stores', attributes: {...} }]
368
+ * }
369
+ * const stores = extractIncludedResources(response)
370
+ * // Returns: [{ id: '1', type: 'stores', attributes: {...} }]
71
371
  */
72
372
  export declare function extractIncludedResources(response: IDataObject): IDataObject[];