@profplum700/etsy-v3-api-client 1.0.1 → 2.3.1

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/dist/node.cjs CHANGED
@@ -3,11 +3,22 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  class EtsyApiError extends Error {
6
- constructor(message, _statusCode, _response) {
6
+ constructor(message, _statusCode, _response, _retryAfter) {
7
7
  super(message);
8
8
  this._statusCode = _statusCode;
9
9
  this._response = _response;
10
+ this._retryAfter = _retryAfter;
10
11
  this.name = 'EtsyApiError';
12
+ this.details = {
13
+ statusCode: _statusCode || 0,
14
+ retryAfter: _retryAfter
15
+ };
16
+ if (_response && typeof _response === 'object') {
17
+ const resp = _response;
18
+ this.details.errorCode = (resp.error_code || resp.code);
19
+ this.details.field = resp.field;
20
+ this.details.suggestion = (resp.suggestion || resp.message);
21
+ }
11
22
  }
12
23
  get statusCode() {
13
24
  return this._statusCode;
@@ -15,6 +26,47 @@ class EtsyApiError extends Error {
15
26
  get response() {
16
27
  return this._response;
17
28
  }
29
+ isRetryable() {
30
+ if (!this._statusCode) {
31
+ return false;
32
+ }
33
+ if (this._statusCode === 429) {
34
+ return true;
35
+ }
36
+ if (this._statusCode >= 500 && this._statusCode < 600) {
37
+ return true;
38
+ }
39
+ const retryableClientErrors = [408, 409, 423, 425];
40
+ if (retryableClientErrors.includes(this._statusCode)) {
41
+ return true;
42
+ }
43
+ return false;
44
+ }
45
+ getRetryAfter() {
46
+ return this._retryAfter || null;
47
+ }
48
+ getRateLimitReset() {
49
+ if (this._statusCode !== 429 || !this._retryAfter) {
50
+ return null;
51
+ }
52
+ return new Date(Date.now() + this._retryAfter * 1000);
53
+ }
54
+ getUserFriendlyMessage() {
55
+ let message = this.message;
56
+ if (this.details.suggestion) {
57
+ message += `\nSuggestion: ${this.details.suggestion}`;
58
+ }
59
+ if (this.isRetryable()) {
60
+ const retryAfter = this.getRetryAfter();
61
+ if (retryAfter) {
62
+ message += `\nRetry after ${retryAfter} seconds.`;
63
+ }
64
+ else {
65
+ message += '\nThis error can be retried.';
66
+ }
67
+ }
68
+ return message;
69
+ }
18
70
  }
19
71
  class EtsyAuthError extends Error {
20
72
  constructor(message, _code) {
@@ -106,11 +158,12 @@ class MemoryTokenStorage {
106
158
  }
107
159
  }
108
160
  class TokenManager {
109
- constructor(config, storage) {
161
+ constructor(config, storage, rotationConfig) {
110
162
  this.currentTokens = null;
111
163
  this.keystring = config.keystring;
112
164
  this.refreshCallback = config.refreshSave;
113
165
  this.storage = storage;
166
+ this.rotationConfig = rotationConfig;
114
167
  this.currentTokens = {
115
168
  access_token: config.accessToken,
116
169
  refresh_token: config.refreshToken,
@@ -118,6 +171,9 @@ class TokenManager {
118
171
  token_type: 'Bearer',
119
172
  scope: ''
120
173
  };
174
+ if (this.rotationConfig?.enabled && this.rotationConfig?.autoSchedule) {
175
+ this.startRotationScheduler();
176
+ }
121
177
  }
122
178
  async getAccessToken() {
123
179
  if (!this.currentTokens) {
@@ -178,8 +234,7 @@ class TokenManager {
178
234
  body: body.toString()
179
235
  });
180
236
  if (!response.ok) {
181
- const errorText = await response.text();
182
- throw new EtsyAuthError(`Token refresh failed: ${response.status} ${response.statusText} - ${errorText}`, 'TOKEN_REFRESH_FAILED');
237
+ throw new EtsyAuthError(`Token refresh failed: ${response.status} ${response.statusText}`, 'TOKEN_REFRESH_FAILED');
183
238
  }
184
239
  const tokenResponse = await response.json();
185
240
  return {
@@ -234,6 +289,73 @@ class TokenManager {
234
289
  const expiresAt = new Date(this.currentTokens.expires_at);
235
290
  return expiresAt.getTime() - now.getTime();
236
291
  }
292
+ needsProactiveRotation() {
293
+ if (!this.rotationConfig?.enabled || !this.currentTokens) {
294
+ return false;
295
+ }
296
+ const timeUntilExpiration = this.getTimeUntilExpiration();
297
+ if (timeUntilExpiration === null) {
298
+ return false;
299
+ }
300
+ const rotateBeforeExpiry = this.rotationConfig.rotateBeforeExpiry || 15 * 60 * 1000;
301
+ return timeUntilExpiration <= rotateBeforeExpiry;
302
+ }
303
+ async rotateToken() {
304
+ if (!this.currentTokens) {
305
+ throw new EtsyAuthError('No tokens available to rotate', 'NO_TOKENS');
306
+ }
307
+ const oldTokens = { ...this.currentTokens };
308
+ const newTokens = await this.refreshToken();
309
+ if (this.rotationConfig?.onRotation) {
310
+ try {
311
+ await Promise.resolve(this.rotationConfig.onRotation(oldTokens, newTokens));
312
+ }
313
+ catch (error) {
314
+ console.error('Token rotation callback failed:', error);
315
+ }
316
+ }
317
+ return newTokens;
318
+ }
319
+ startRotationScheduler() {
320
+ if (!this.rotationConfig?.enabled) {
321
+ throw new Error('Token rotation is not enabled');
322
+ }
323
+ this.stopRotationScheduler();
324
+ const checkInterval = this.rotationConfig.checkInterval || 60 * 1000;
325
+ this.rotationTimer = setInterval(async () => {
326
+ try {
327
+ if (this.needsProactiveRotation()) {
328
+ console.log('Proactively rotating token before expiration');
329
+ await this.rotateToken();
330
+ }
331
+ }
332
+ catch (error) {
333
+ console.error('Failed to proactively rotate token:', error);
334
+ }
335
+ }, checkInterval);
336
+ if (this.rotationTimer.unref) {
337
+ this.rotationTimer.unref();
338
+ }
339
+ }
340
+ stopRotationScheduler() {
341
+ if (this.rotationTimer) {
342
+ clearInterval(this.rotationTimer);
343
+ this.rotationTimer = undefined;
344
+ }
345
+ }
346
+ updateRotationConfig(config) {
347
+ const wasAutoScheduleEnabled = this.rotationConfig?.autoSchedule;
348
+ this.rotationConfig = config;
349
+ if (config.enabled && config.autoSchedule && !wasAutoScheduleEnabled) {
350
+ this.startRotationScheduler();
351
+ }
352
+ else if (!config.enabled || !config.autoSchedule) {
353
+ this.stopRotationScheduler();
354
+ }
355
+ }
356
+ getRotationConfig() {
357
+ return this.rotationConfig ? { ...this.rotationConfig } : undefined;
358
+ }
237
359
  async fetch(url, options) {
238
360
  assertFetchSupport();
239
361
  return fetch(url, options);
@@ -330,6 +452,7 @@ class FileTokenStorage {
330
452
  try {
331
453
  const data = JSON.stringify(tokens, null, 2);
332
454
  await this._writeFile(this.filePath, data);
455
+ await this._setFilePermissions(this.filePath, 0o600);
333
456
  }
334
457
  catch {
335
458
  throw new Error('Failed to save tokens to file');
@@ -367,6 +490,16 @@ class FileTokenStorage {
367
490
  const fs = await import('fs');
368
491
  await fs.promises.writeFile(filePath, data, 'utf8');
369
492
  }
493
+ async _setFilePermissions(filePath, mode) {
494
+ if (typeof process === 'undefined')
495
+ return;
496
+ try {
497
+ const fs = await import('fs');
498
+ await fs.promises.chmod(filePath, mode);
499
+ }
500
+ catch {
501
+ }
502
+ }
370
503
  async _readFile(filePath) {
371
504
  if (typeof process === 'undefined')
372
505
  throw new Error('Not available');
@@ -464,6 +597,407 @@ class EtsyRateLimiter {
464
597
  }
465
598
  const defaultRateLimiter = new EtsyRateLimiter();
466
599
 
600
+ class BulkOperationManager {
601
+ constructor(config = {}) {
602
+ this.concurrency = config.concurrency ?? 5;
603
+ this.stopOnError = config.stopOnError ?? false;
604
+ this.onProgress = config.onProgress;
605
+ this.onItemComplete = config.onItemComplete;
606
+ this.onItemError = config.onItemError;
607
+ }
608
+ async executeBulk(items, operation, getId) {
609
+ const startTime = Date.now();
610
+ const results = [];
611
+ const errors = [];
612
+ let completed = 0;
613
+ let successful = 0;
614
+ let failed = 0;
615
+ const queue = items.map((item, index) => ({
616
+ item,
617
+ index,
618
+ id: getId ? getId(item, index) : index
619
+ }));
620
+ const workers = [];
621
+ for (let i = 0; i < Math.min(this.concurrency, items.length); i++) {
622
+ workers.push(this.worker(queue, operation, results, errors, () => {
623
+ completed++;
624
+ const lastResult = results[results.length - 1];
625
+ if (lastResult?.success) {
626
+ successful++;
627
+ }
628
+ else {
629
+ failed++;
630
+ }
631
+ if (this.onProgress) {
632
+ this.onProgress(completed, items.length, lastResult);
633
+ }
634
+ }));
635
+ }
636
+ await Promise.all(workers);
637
+ const duration = Date.now() - startTime;
638
+ return {
639
+ total: items.length,
640
+ successful,
641
+ failed,
642
+ results,
643
+ errors,
644
+ duration
645
+ };
646
+ }
647
+ async worker(queue, operation, results, errors, onComplete) {
648
+ while (queue.length > 0) {
649
+ const work = queue.shift();
650
+ if (!work)
651
+ break;
652
+ const { item, index, id } = work;
653
+ try {
654
+ const data = await operation(item, index);
655
+ const result = {
656
+ success: true,
657
+ data,
658
+ id,
659
+ index
660
+ };
661
+ results.push(result);
662
+ if (this.onItemComplete) {
663
+ this.onItemComplete(result);
664
+ }
665
+ onComplete();
666
+ }
667
+ catch (error) {
668
+ const err = error instanceof Error ? error : new Error(String(error));
669
+ const result = {
670
+ success: false,
671
+ error: err,
672
+ id,
673
+ index
674
+ };
675
+ results.push(result);
676
+ const bulkError = {
677
+ id,
678
+ index,
679
+ error: err
680
+ };
681
+ errors.push(bulkError);
682
+ if (this.onItemError) {
683
+ this.onItemError(bulkError);
684
+ }
685
+ onComplete();
686
+ if (this.stopOnError) {
687
+ queue.length = 0;
688
+ break;
689
+ }
690
+ }
691
+ }
692
+ }
693
+ setConcurrency(concurrency) {
694
+ this.concurrency = Math.max(1, concurrency);
695
+ }
696
+ getConcurrency() {
697
+ return this.concurrency;
698
+ }
699
+ }
700
+ function createBulkOperationManager(config) {
701
+ return new BulkOperationManager(config);
702
+ }
703
+ async function executeBulkOperation(items, operation, config) {
704
+ const manager = new BulkOperationManager(config);
705
+ return manager.executeBulk(items, operation);
706
+ }
707
+
708
+ class Validator {
709
+ constructor() {
710
+ this.validators = [];
711
+ }
712
+ rule(validator) {
713
+ this.validators.push(validator);
714
+ return this;
715
+ }
716
+ validate(data) {
717
+ const errors = [];
718
+ for (const validator of this.validators) {
719
+ const error = validator(data);
720
+ if (error) {
721
+ errors.push(error);
722
+ }
723
+ }
724
+ return {
725
+ valid: errors.length === 0,
726
+ errors
727
+ };
728
+ }
729
+ }
730
+ class FieldValidator {
731
+ constructor(field) {
732
+ this.field = field;
733
+ }
734
+ required(message) {
735
+ return (data) => {
736
+ if (typeof data !== 'object' || data === null)
737
+ return null;
738
+ const value = data[this.field];
739
+ if (value === undefined || value === null || value === '') {
740
+ return {
741
+ field: this.field,
742
+ message: message || `${this.field} is required`,
743
+ value
744
+ };
745
+ }
746
+ return null;
747
+ };
748
+ }
749
+ string(options) {
750
+ return (data) => {
751
+ if (typeof data !== 'object' || data === null)
752
+ return null;
753
+ const value = data[this.field];
754
+ if (value === undefined || value === null)
755
+ return null;
756
+ if (typeof value !== 'string') {
757
+ return {
758
+ field: this.field,
759
+ message: options.message || `${this.field} must be a string`,
760
+ value
761
+ };
762
+ }
763
+ if (options.min !== undefined && value.length < options.min) {
764
+ return {
765
+ field: this.field,
766
+ message: options.message || `${this.field} must be at least ${options.min} characters`,
767
+ value
768
+ };
769
+ }
770
+ if (options.max !== undefined && value.length > options.max) {
771
+ return {
772
+ field: this.field,
773
+ message: options.message || `${this.field} must be at most ${options.max} characters`,
774
+ value
775
+ };
776
+ }
777
+ if (options.pattern && !options.pattern.test(value)) {
778
+ return {
779
+ field: this.field,
780
+ message: options.message || `${this.field} has invalid format`,
781
+ value
782
+ };
783
+ }
784
+ return null;
785
+ };
786
+ }
787
+ number(options) {
788
+ return (data) => {
789
+ if (typeof data !== 'object' || data === null)
790
+ return null;
791
+ const value = data[this.field];
792
+ if (value === undefined || value === null)
793
+ return null;
794
+ if (typeof value !== 'number' || isNaN(value)) {
795
+ return {
796
+ field: this.field,
797
+ message: options.message || `${this.field} must be a number`,
798
+ value
799
+ };
800
+ }
801
+ if (options.integer && !Number.isInteger(value)) {
802
+ return {
803
+ field: this.field,
804
+ message: options.message || `${this.field} must be an integer`,
805
+ value
806
+ };
807
+ }
808
+ if (options.positive && value <= 0) {
809
+ return {
810
+ field: this.field,
811
+ message: options.message || `${this.field} must be positive`,
812
+ value
813
+ };
814
+ }
815
+ if (options.min !== undefined && value < options.min) {
816
+ return {
817
+ field: this.field,
818
+ message: options.message || `${this.field} must be at least ${options.min}`,
819
+ value
820
+ };
821
+ }
822
+ if (options.max !== undefined && value > options.max) {
823
+ return {
824
+ field: this.field,
825
+ message: options.message || `${this.field} must be at most ${options.max}`,
826
+ value
827
+ };
828
+ }
829
+ return null;
830
+ };
831
+ }
832
+ enum(allowedValues, message) {
833
+ return (data) => {
834
+ if (typeof data !== 'object' || data === null)
835
+ return null;
836
+ const value = data[this.field];
837
+ if (value === undefined || value === null)
838
+ return null;
839
+ if (!allowedValues.includes(value)) {
840
+ return {
841
+ field: this.field,
842
+ message: message || `${this.field} must be one of: ${allowedValues.join(', ')}`,
843
+ value
844
+ };
845
+ }
846
+ return null;
847
+ };
848
+ }
849
+ array(options) {
850
+ return (data) => {
851
+ if (typeof data !== 'object' || data === null)
852
+ return null;
853
+ const value = data[this.field];
854
+ if (value === undefined || value === null)
855
+ return null;
856
+ if (!Array.isArray(value)) {
857
+ return {
858
+ field: this.field,
859
+ message: options.message || `${this.field} must be an array`,
860
+ value
861
+ };
862
+ }
863
+ if (options.min !== undefined && value.length < options.min) {
864
+ return {
865
+ field: this.field,
866
+ message: options.message || `${this.field} must have at least ${options.min} items`,
867
+ value
868
+ };
869
+ }
870
+ if (options.max !== undefined && value.length > options.max) {
871
+ return {
872
+ field: this.field,
873
+ message: options.message || `${this.field} must have at most ${options.max} items`,
874
+ value
875
+ };
876
+ }
877
+ if (options.itemValidator) {
878
+ for (let i = 0; i < value.length; i++) {
879
+ if (!options.itemValidator(value[i])) {
880
+ return {
881
+ field: this.field,
882
+ message: options.message || `${this.field}[${i}] has invalid value`,
883
+ value: value[i]
884
+ };
885
+ }
886
+ }
887
+ }
888
+ return null;
889
+ };
890
+ }
891
+ }
892
+ function field(fieldName) {
893
+ return new FieldValidator(fieldName);
894
+ }
895
+ const CreateListingSchema = new Validator()
896
+ .rule(field('quantity').required())
897
+ .rule(field('quantity').number({ min: 1, integer: true, message: 'quantity must be a positive integer' }))
898
+ .rule(field('title').required())
899
+ .rule(field('title').string({ min: 1, max: 140, message: 'title must be 1-140 characters' }))
900
+ .rule(field('description').string({ max: 65535, message: 'description must be less than 65535 characters' }))
901
+ .rule(field('price').required())
902
+ .rule(field('price').number({ min: 0.2, max: 50000, message: 'price must be between 0.20 and 50000.00' }))
903
+ .rule(field('who_made').enum(['i_did', 'someone_else', 'collective'], 'who_made must be one of: i_did, someone_else, collective'))
904
+ .rule(field('when_made').enum([
905
+ 'made_to_order',
906
+ '2020_2024',
907
+ '2010_2019',
908
+ '2005_2009',
909
+ '2000_2004',
910
+ '1990s',
911
+ '1980s',
912
+ '1970s',
913
+ '1960s',
914
+ '1950s',
915
+ '1940s',
916
+ '1930s',
917
+ '1920s',
918
+ '1910s',
919
+ '1900s',
920
+ '1800s',
921
+ '1700s',
922
+ 'before_1700'
923
+ ]))
924
+ .rule(field('taxonomy_id').required())
925
+ .rule(field('taxonomy_id').number({ integer: true, positive: true, message: 'taxonomy_id must be a positive integer' }));
926
+ const UpdateListingSchema = new Validator()
927
+ .rule((data) => {
928
+ if (typeof data === 'object' && data !== null && 'title' in data && data.title !== undefined) {
929
+ return field('title').string({ min: 1, max: 140, message: 'title must be 1-140 characters' })(data);
930
+ }
931
+ return null;
932
+ })
933
+ .rule((data) => {
934
+ if (typeof data === 'object' && data !== null && 'description' in data && data.description !== undefined) {
935
+ return field('description').string({ max: 65535, message: 'description must be less than 65535 characters' })(data);
936
+ }
937
+ return null;
938
+ })
939
+ .rule((data) => {
940
+ if (data.tags !== undefined) {
941
+ return field('tags').array({ max: 13, message: 'tags must have at most 13 items' })(data);
942
+ }
943
+ return null;
944
+ })
945
+ .rule((data) => {
946
+ if (data.materials !== undefined) {
947
+ return field('materials').array({ max: 13, message: 'materials must have at most 13 items' })(data);
948
+ }
949
+ return null;
950
+ });
951
+ const UpdateShopSchema = new Validator()
952
+ .rule((data) => {
953
+ if (typeof data === 'object' && data !== null && 'title' in data && data.title !== undefined) {
954
+ return field('title').string({ min: 1, max: 55, message: 'shop title must be 1-55 characters' })(data);
955
+ }
956
+ return null;
957
+ })
958
+ .rule((data) => {
959
+ if (typeof data === 'object' && data !== null && 'announcement' in data && data.announcement !== undefined) {
960
+ return field('announcement').string({ max: 5000, message: 'announcement must be less than 5000 characters' })(data);
961
+ }
962
+ return null;
963
+ });
964
+ class ValidationException extends Error {
965
+ constructor(message, errors) {
966
+ super(message);
967
+ this.name = 'ValidationException';
968
+ this.errors = errors;
969
+ }
970
+ }
971
+ function validateOrThrow(data, schema, errorMessage = 'Validation failed') {
972
+ const result = schema.validate(data);
973
+ if (!result.valid) {
974
+ throw new ValidationException(errorMessage, result.errors);
975
+ }
976
+ }
977
+ function validate(data, schema) {
978
+ return schema.validate(data);
979
+ }
980
+ function createValidator() {
981
+ return new Validator();
982
+ }
983
+ function combineValidators(...validators) {
984
+ return {
985
+ validate(data) {
986
+ const allErrors = [];
987
+ for (const validator of validators) {
988
+ const result = validator.validate(data);
989
+ if (!result.valid) {
990
+ allErrors.push(...result.errors);
991
+ }
992
+ }
993
+ return {
994
+ valid: allErrors.length === 0,
995
+ errors: allErrors
996
+ };
997
+ }
998
+ };
999
+ }
1000
+
467
1001
  class DefaultLogger {
468
1002
  debug(message, ...args) {
469
1003
  const isDevelopment = isNode$1
@@ -528,6 +1062,10 @@ class EtsyClient {
528
1062
  this.cache = config.caching?.storage || new MemoryCache();
529
1063
  this.cacheTtl = config.caching?.ttl || 3600;
530
1064
  }
1065
+ this.bulkOperationManager = new BulkOperationManager({
1066
+ concurrency: 5,
1067
+ stopOnError: false
1068
+ });
531
1069
  }
532
1070
  async makeRequest(endpoint, options = {}, useCache = true) {
533
1071
  const url = `${this.baseUrl}${endpoint}`;
@@ -560,6 +1098,13 @@ class EtsyClient {
560
1098
  const errorText = await response.text();
561
1099
  throw new EtsyApiError(`Etsy API error: ${response.status} ${response.statusText}`, response.status, errorText);
562
1100
  }
1101
+ if (response.status === 204) {
1102
+ return undefined;
1103
+ }
1104
+ const contentLength = response.headers?.get?.('content-length');
1105
+ if (contentLength === '0') {
1106
+ return undefined;
1107
+ }
563
1108
  const data = await response.json();
564
1109
  if (useCache && this.cache && requestOptions.method === 'GET') {
565
1110
  await this.cache.set(cacheKey, JSON.stringify(data), this.cacheTtl);
@@ -670,6 +1215,278 @@ class EtsyClient {
670
1215
  async getListingInventory(listingId) {
671
1216
  return this.makeRequest(`/listings/${listingId}/inventory`);
672
1217
  }
1218
+ async getSellerTaxonomyNodes() {
1219
+ const response = await this.makeRequest('/seller-taxonomy/nodes');
1220
+ return response.results;
1221
+ }
1222
+ async getUserShops() {
1223
+ const response = await this.makeRequest('/users/me/shops');
1224
+ return response.results || [];
1225
+ }
1226
+ async updateShop(shopId, params, options) {
1227
+ if (options?.validate) {
1228
+ const schema = options.validateSchema || UpdateShopSchema;
1229
+ const throwOnError = options.throwOnValidationError !== false;
1230
+ if (throwOnError) {
1231
+ validateOrThrow(params, schema, 'Invalid shop update parameters');
1232
+ }
1233
+ else {
1234
+ const result = schema.validate(params);
1235
+ if (!result.valid) {
1236
+ this.logger.warn('Shop update validation failed', result.errors);
1237
+ }
1238
+ }
1239
+ }
1240
+ return this.makeRequest(`/shops/${shopId}`, {
1241
+ method: 'PUT',
1242
+ body: JSON.stringify(params)
1243
+ }, false);
1244
+ }
1245
+ async createShopSection(shopId, params) {
1246
+ return this.makeRequest(`/shops/${shopId}/sections`, {
1247
+ method: 'POST',
1248
+ body: JSON.stringify(params)
1249
+ }, false);
1250
+ }
1251
+ async updateShopSection(shopId, sectionId, params) {
1252
+ return this.makeRequest(`/shops/${shopId}/sections/${sectionId}`, {
1253
+ method: 'PUT',
1254
+ body: JSON.stringify(params)
1255
+ }, false);
1256
+ }
1257
+ async deleteShopSection(shopId, sectionId) {
1258
+ await this.makeRequest(`/shops/${shopId}/sections/${sectionId}`, { method: 'DELETE' }, false);
1259
+ }
1260
+ async createDraftListing(shopId, params, options) {
1261
+ if (options?.validate) {
1262
+ const schema = options.validateSchema || CreateListingSchema;
1263
+ const throwOnError = options.throwOnValidationError !== false;
1264
+ if (throwOnError) {
1265
+ validateOrThrow(params, schema, 'Invalid listing parameters');
1266
+ }
1267
+ else {
1268
+ const result = schema.validate(params);
1269
+ if (!result.valid) {
1270
+ this.logger.warn('Listing validation failed', result.errors);
1271
+ }
1272
+ }
1273
+ }
1274
+ return this.makeRequest(`/shops/${shopId}/listings`, {
1275
+ method: 'POST',
1276
+ body: JSON.stringify(params)
1277
+ }, false);
1278
+ }
1279
+ async updateListing(shopId, listingId, params, options) {
1280
+ if (options?.validate) {
1281
+ const schema = options.validateSchema || UpdateListingSchema;
1282
+ const throwOnError = options.throwOnValidationError !== false;
1283
+ if (throwOnError) {
1284
+ validateOrThrow(params, schema, 'Invalid listing update parameters');
1285
+ }
1286
+ else {
1287
+ const result = schema.validate(params);
1288
+ if (!result.valid) {
1289
+ this.logger.warn('Listing update validation failed', result.errors);
1290
+ }
1291
+ }
1292
+ }
1293
+ return this.makeRequest(`/shops/${shopId}/listings/${listingId}`, {
1294
+ method: 'PATCH',
1295
+ body: JSON.stringify(params)
1296
+ }, false);
1297
+ }
1298
+ async deleteListing(listingId) {
1299
+ await this.makeRequest(`/listings/${listingId}`, { method: 'DELETE' }, false);
1300
+ }
1301
+ async updateListingInventory(listingId, params) {
1302
+ return this.makeRequest(`/listings/${listingId}/inventory`, {
1303
+ method: 'PUT',
1304
+ body: JSON.stringify(params)
1305
+ }, false);
1306
+ }
1307
+ async uploadListingImage(shopId, listingId, imageData, params) {
1308
+ const formData = new FormData();
1309
+ formData.append('image', imageData);
1310
+ if (params?.rank !== undefined)
1311
+ formData.append('rank', params.rank.toString());
1312
+ if (params?.overwrite !== undefined)
1313
+ formData.append('overwrite', params.overwrite.toString());
1314
+ if (params?.is_watermarked !== undefined)
1315
+ formData.append('is_watermarked', params.is_watermarked.toString());
1316
+ if (params?.alt_text)
1317
+ formData.append('alt_text', params.alt_text);
1318
+ const url = `${this.baseUrl}/shops/${shopId}/listings/${listingId}/images`;
1319
+ await this.rateLimiter.waitForRateLimit();
1320
+ const accessToken = await this.tokenManager.getAccessToken();
1321
+ const response = await this.fetch(url, {
1322
+ method: 'POST',
1323
+ headers: {
1324
+ 'Authorization': `Bearer ${accessToken}`,
1325
+ 'x-api-key': this.getApiKey(),
1326
+ },
1327
+ body: formData
1328
+ });
1329
+ if (!response.ok) {
1330
+ const errorText = await response.text();
1331
+ throw new EtsyApiError(`Failed to upload image: ${response.status} ${response.statusText}`, response.status, errorText);
1332
+ }
1333
+ return response.json();
1334
+ }
1335
+ async getListingImage(shopId, listingId, imageId) {
1336
+ return this.makeRequest(`/shops/${shopId}/listings/${listingId}/images/${imageId}`);
1337
+ }
1338
+ async deleteListingImage(shopId, listingId, imageId) {
1339
+ await this.makeRequest(`/shops/${shopId}/listings/${listingId}/images/${imageId}`, { method: 'DELETE' }, false);
1340
+ }
1341
+ async bulkUpdateListings(shopId, operations, config) {
1342
+ const manager = config ? new BulkOperationManager(config) : this.bulkOperationManager;
1343
+ return manager.executeBulk(operations, async (operation) => {
1344
+ return this.updateListing(shopId, operation.listingId.toString(), operation.updates);
1345
+ }, (operation) => operation.listingId);
1346
+ }
1347
+ async bulkUploadImages(shopId, listingId, images, config) {
1348
+ const manager = config ? new BulkOperationManager(config) : this.bulkOperationManager;
1349
+ return manager.executeBulk(images, async (image) => {
1350
+ return this.uploadListingImage(shopId, listingId, image.file, {
1351
+ rank: image.rank,
1352
+ alt_text: image.altText
1353
+ });
1354
+ });
1355
+ }
1356
+ setBulkOperationConcurrency(concurrency) {
1357
+ this.bulkOperationManager.setConcurrency(Math.max(1, Math.min(10, concurrency)));
1358
+ }
1359
+ async getShopReceipts(shopId, params) {
1360
+ const searchParams = new URLSearchParams();
1361
+ if (params?.limit !== undefined)
1362
+ searchParams.set('limit', params.limit.toString());
1363
+ if (params?.offset !== undefined)
1364
+ searchParams.set('offset', params.offset.toString());
1365
+ if (params?.sort_on)
1366
+ searchParams.set('sort_on', params.sort_on);
1367
+ if (params?.sort_order)
1368
+ searchParams.set('sort_order', params.sort_order);
1369
+ if (params?.min_created !== undefined)
1370
+ searchParams.set('min_created', params.min_created.toString());
1371
+ if (params?.max_created !== undefined)
1372
+ searchParams.set('max_created', params.max_created.toString());
1373
+ if (params?.min_last_modified !== undefined)
1374
+ searchParams.set('min_last_modified', params.min_last_modified.toString());
1375
+ if (params?.max_last_modified !== undefined)
1376
+ searchParams.set('max_last_modified', params.max_last_modified.toString());
1377
+ if (params?.was_paid !== undefined)
1378
+ searchParams.set('was_paid', params.was_paid.toString());
1379
+ if (params?.was_shipped !== undefined)
1380
+ searchParams.set('was_shipped', params.was_shipped.toString());
1381
+ if (params?.was_delivered !== undefined)
1382
+ searchParams.set('was_delivered', params.was_delivered.toString());
1383
+ const response = await this.makeRequest(`/shops/${shopId}/receipts?${searchParams.toString()}`);
1384
+ return response.results;
1385
+ }
1386
+ async getShopReceipt(shopId, receiptId) {
1387
+ return this.makeRequest(`/shops/${shopId}/receipts/${receiptId}`);
1388
+ }
1389
+ async updateShopReceipt(shopId, receiptId, params) {
1390
+ return this.makeRequest(`/shops/${shopId}/receipts/${receiptId}`, {
1391
+ method: 'PUT',
1392
+ body: JSON.stringify(params)
1393
+ }, false);
1394
+ }
1395
+ async getShopReceiptTransactions(shopId, receiptId) {
1396
+ const response = await this.makeRequest(`/shops/${shopId}/receipts/${receiptId}/transactions`);
1397
+ return response.results;
1398
+ }
1399
+ async getShopTransaction(shopId, transactionId) {
1400
+ return this.makeRequest(`/shops/${shopId}/transactions/${transactionId}`);
1401
+ }
1402
+ async getShopShippingProfiles(shopId) {
1403
+ const response = await this.makeRequest(`/shops/${shopId}/shipping-profiles`);
1404
+ return response.results;
1405
+ }
1406
+ async createShopShippingProfile(shopId, params) {
1407
+ return this.makeRequest(`/shops/${shopId}/shipping-profiles`, {
1408
+ method: 'POST',
1409
+ body: JSON.stringify(params)
1410
+ }, false);
1411
+ }
1412
+ async getShopShippingProfile(shopId, profileId) {
1413
+ return this.makeRequest(`/shops/${shopId}/shipping-profiles/${profileId}`);
1414
+ }
1415
+ async updateShopShippingProfile(shopId, profileId, params) {
1416
+ return this.makeRequest(`/shops/${shopId}/shipping-profiles/${profileId}`, {
1417
+ method: 'PUT',
1418
+ body: JSON.stringify(params)
1419
+ }, false);
1420
+ }
1421
+ async deleteShopShippingProfile(shopId, profileId) {
1422
+ await this.makeRequest(`/shops/${shopId}/shipping-profiles/${profileId}`, { method: 'DELETE' }, false);
1423
+ }
1424
+ async getShopShippingProfileDestinations(shopId, profileId) {
1425
+ const response = await this.makeRequest(`/shops/${shopId}/shipping-profiles/${profileId}/destinations`);
1426
+ return response.results;
1427
+ }
1428
+ async createShopShippingProfileDestination(shopId, profileId, params) {
1429
+ return this.makeRequest(`/shops/${shopId}/shipping-profiles/${profileId}/destinations`, {
1430
+ method: 'POST',
1431
+ body: JSON.stringify(params)
1432
+ }, false);
1433
+ }
1434
+ async updateShopShippingProfileDestination(shopId, profileId, destinationId, params) {
1435
+ return this.makeRequest(`/shops/${shopId}/shipping-profiles/${profileId}/destinations/${destinationId}`, {
1436
+ method: 'PUT',
1437
+ body: JSON.stringify(params)
1438
+ }, false);
1439
+ }
1440
+ async deleteShopShippingProfileDestination(shopId, profileId, destinationId) {
1441
+ await this.makeRequest(`/shops/${shopId}/shipping-profiles/${profileId}/destinations/${destinationId}`, { method: 'DELETE' }, false);
1442
+ }
1443
+ async getShopShippingProfileUpgrades(shopId, profileId) {
1444
+ const response = await this.makeRequest(`/shops/${shopId}/shipping-profiles/${profileId}/upgrades`);
1445
+ return response.results;
1446
+ }
1447
+ async createReceiptShipment(shopId, receiptId, params) {
1448
+ return this.makeRequest(`/shops/${shopId}/receipts/${receiptId}/tracking`, {
1449
+ method: 'POST',
1450
+ body: JSON.stringify(params)
1451
+ }, false);
1452
+ }
1453
+ async getShopReceiptShipments(shopId, receiptId) {
1454
+ const response = await this.makeRequest(`/shops/${shopId}/receipts/${receiptId}/shipments`);
1455
+ return response.results;
1456
+ }
1457
+ async getShopPaymentAccountLedgerEntries(shopId, params) {
1458
+ const searchParams = new URLSearchParams();
1459
+ searchParams.set('min_created', params.min_created.toString());
1460
+ searchParams.set('max_created', params.max_created.toString());
1461
+ if (params.limit !== undefined)
1462
+ searchParams.set('limit', params.limit.toString());
1463
+ if (params.offset !== undefined)
1464
+ searchParams.set('offset', params.offset.toString());
1465
+ const response = await this.makeRequest(`/shops/${shopId}/payment-account/ledger-entries?${searchParams.toString()}`);
1466
+ return response.results;
1467
+ }
1468
+ async getShopPaymentAccountLedgerEntry(shopId, entryId) {
1469
+ return this.makeRequest(`/shops/${shopId}/payment-account/ledger-entries/${entryId}`);
1470
+ }
1471
+ async getShopPayment(shopId, paymentId) {
1472
+ return this.makeRequest(`/shops/${shopId}/payment-account/payments/${paymentId}`);
1473
+ }
1474
+ async getBuyerTaxonomyNodes() {
1475
+ const response = await this.makeRequest('/buyer-taxonomy/nodes');
1476
+ return response.results;
1477
+ }
1478
+ async getPropertiesByTaxonomyId(taxonomyId) {
1479
+ const response = await this.makeRequest(`/seller-taxonomy/nodes/${taxonomyId}/properties`);
1480
+ return response.results;
1481
+ }
1482
+ async getListingProperties(shopId, listingId) {
1483
+ const response = await this.makeRequest(`/shops/${shopId}/listings/${listingId}/properties`);
1484
+ return response.results;
1485
+ }
1486
+ async getShopProductionPartners(shopId) {
1487
+ const response = await this.makeRequest(`/shops/${shopId}/production-partners`);
1488
+ return response.results;
1489
+ }
673
1490
  getRemainingRequests() {
674
1491
  return this.rateLimiter.getRemainingRequests();
675
1492
  }
@@ -917,6 +1734,1075 @@ const COMMON_SCOPE_COMBINATIONS = {
917
1734
  ]
918
1735
  };
919
1736
 
1737
+ class PaginatedResults {
1738
+ constructor(fetcher, options = {}) {
1739
+ this.currentPage = [];
1740
+ this.totalCount = null;
1741
+ this.hasMore = true;
1742
+ this.fetcher = fetcher;
1743
+ this.options = {
1744
+ limit: options.limit || 25,
1745
+ offset: options.offset || 0,
1746
+ maxPages: options.maxPages || Infinity,
1747
+ maxItems: options.maxItems || Infinity
1748
+ };
1749
+ this.currentOffset = this.options.offset;
1750
+ }
1751
+ async *[Symbol.asyncIterator]() {
1752
+ let pageCount = 0;
1753
+ let itemCount = 0;
1754
+ while (this.hasMore && pageCount < this.options.maxPages && itemCount < this.options.maxItems) {
1755
+ const page = await this.fetchPage();
1756
+ pageCount++;
1757
+ for (const item of page) {
1758
+ if (itemCount >= this.options.maxItems) {
1759
+ return;
1760
+ }
1761
+ yield item;
1762
+ itemCount++;
1763
+ }
1764
+ if (!this.hasMore) {
1765
+ break;
1766
+ }
1767
+ }
1768
+ }
1769
+ async getAll() {
1770
+ const items = [];
1771
+ for await (const item of this) {
1772
+ items.push(item);
1773
+ }
1774
+ return items;
1775
+ }
1776
+ getCurrentPage() {
1777
+ return this.currentPage;
1778
+ }
1779
+ hasNextPage() {
1780
+ return this.hasMore;
1781
+ }
1782
+ async getNextPage() {
1783
+ if (!this.hasMore) {
1784
+ return [];
1785
+ }
1786
+ return this.fetchPage();
1787
+ }
1788
+ getTotalCount() {
1789
+ return this.totalCount;
1790
+ }
1791
+ reset() {
1792
+ this.currentOffset = this.options.offset;
1793
+ this.currentPage = [];
1794
+ this.totalCount = null;
1795
+ this.hasMore = true;
1796
+ }
1797
+ async fetchPage() {
1798
+ const response = await this.fetcher(this.options.limit, this.currentOffset);
1799
+ this.currentPage = response.results;
1800
+ this.totalCount = response.count;
1801
+ if (response.pagination) {
1802
+ if (response.pagination.next_offset !== undefined) {
1803
+ this.hasMore = true;
1804
+ this.currentOffset = response.pagination.next_offset;
1805
+ }
1806
+ else {
1807
+ this.hasMore = false;
1808
+ }
1809
+ }
1810
+ else {
1811
+ this.hasMore = response.results.length === this.options.limit;
1812
+ this.currentOffset += this.options.limit;
1813
+ }
1814
+ return this.currentPage;
1815
+ }
1816
+ }
1817
+ function createPaginatedResults(fetcher, options) {
1818
+ return new PaginatedResults(fetcher, options);
1819
+ }
1820
+
1821
+ const DEFAULT_RETRY_CONFIG = {
1822
+ maxRetries: 3,
1823
+ retryDelay: 1000,
1824
+ exponentialBackoff: true,
1825
+ retryableStatusCodes: [429, 500, 502, 503, 504],
1826
+ maxRetryDelay: 30000,
1827
+ jitter: 0.1
1828
+ };
1829
+ function calculateDelay(attempt, config) {
1830
+ let delay;
1831
+ if (config.exponentialBackoff) {
1832
+ delay = config.retryDelay * Math.pow(2, attempt - 1);
1833
+ }
1834
+ else {
1835
+ delay = config.retryDelay * attempt;
1836
+ }
1837
+ if (config.maxRetryDelay) {
1838
+ delay = Math.min(delay, config.maxRetryDelay);
1839
+ }
1840
+ if (config.jitter && config.jitter > 0) {
1841
+ const jitterAmount = delay * config.jitter;
1842
+ const randomJitter = Math.random() * jitterAmount * 2 - jitterAmount;
1843
+ delay += randomJitter;
1844
+ }
1845
+ return Math.max(0, Math.floor(delay));
1846
+ }
1847
+ function isRetryableError(error, config) {
1848
+ if (error instanceof EtsyApiError) {
1849
+ const statusCode = error.statusCode;
1850
+ if (statusCode && config.retryableStatusCodes.includes(statusCode)) {
1851
+ return true;
1852
+ }
1853
+ }
1854
+ if (error instanceof TypeError && error.message.includes('fetch')) {
1855
+ return true;
1856
+ }
1857
+ return false;
1858
+ }
1859
+ function getRetryAfterDelay(error) {
1860
+ if (error instanceof EtsyApiError && error.statusCode === 429) {
1861
+ const retryAfter = error.getRetryAfter();
1862
+ if (typeof retryAfter === 'number') {
1863
+ return retryAfter * 1000;
1864
+ }
1865
+ }
1866
+ return null;
1867
+ }
1868
+ function sleep(ms) {
1869
+ return new Promise(resolve => setTimeout(resolve, ms));
1870
+ }
1871
+ async function withRetry(operation, options = {}) {
1872
+ const config = {
1873
+ ...DEFAULT_RETRY_CONFIG,
1874
+ ...options
1875
+ };
1876
+ let lastError = new Error('Unknown error');
1877
+ let attempt = 0;
1878
+ while (attempt <= config.maxRetries) {
1879
+ try {
1880
+ if (options.signal?.aborted) {
1881
+ throw new Error('Operation aborted');
1882
+ }
1883
+ const result = await operation();
1884
+ return result;
1885
+ }
1886
+ catch (error) {
1887
+ lastError = error;
1888
+ attempt++;
1889
+ if (attempt > config.maxRetries) {
1890
+ throw lastError;
1891
+ }
1892
+ if (!isRetryableError(error, config)) {
1893
+ throw lastError;
1894
+ }
1895
+ let delay = calculateDelay(attempt, config);
1896
+ const retryAfterDelay = getRetryAfterDelay(error);
1897
+ if (retryAfterDelay !== null) {
1898
+ delay = retryAfterDelay;
1899
+ }
1900
+ if (config.onRetry) {
1901
+ config.onRetry(attempt, lastError);
1902
+ }
1903
+ await sleep(delay);
1904
+ }
1905
+ }
1906
+ throw lastError;
1907
+ }
1908
+ class RetryManager {
1909
+ constructor(config = {}) {
1910
+ this.config = {
1911
+ ...DEFAULT_RETRY_CONFIG,
1912
+ ...config
1913
+ };
1914
+ }
1915
+ async execute(operation, options) {
1916
+ return withRetry(operation, {
1917
+ ...this.config,
1918
+ ...options
1919
+ });
1920
+ }
1921
+ updateConfig(config) {
1922
+ this.config = {
1923
+ ...this.config,
1924
+ ...config
1925
+ };
1926
+ }
1927
+ getConfig() {
1928
+ return { ...this.config };
1929
+ }
1930
+ }
1931
+
1932
+ class EtsyWebhookHandler {
1933
+ constructor(config) {
1934
+ this.config = {
1935
+ algorithm: 'sha256',
1936
+ verifySignatures: true,
1937
+ ...config
1938
+ };
1939
+ this.handlers = new Map();
1940
+ if (isNode$1) {
1941
+ try {
1942
+ this.crypto = require('crypto');
1943
+ }
1944
+ catch {
1945
+ console.warn('crypto module not available, signature verification will not work');
1946
+ }
1947
+ }
1948
+ }
1949
+ verifySignature(payload, signature) {
1950
+ if (!this.config.verifySignatures) {
1951
+ return true;
1952
+ }
1953
+ if (!this.crypto) {
1954
+ throw new Error('Signature verification requires Node.js crypto module');
1955
+ }
1956
+ try {
1957
+ const hmac = this.crypto
1958
+ .createHmac(this.config.algorithm, this.config.secret)
1959
+ .update(payload)
1960
+ .digest('hex');
1961
+ return this.timingSafeEqual(hmac, signature);
1962
+ }
1963
+ catch (error) {
1964
+ console.error('Signature verification failed:', error);
1965
+ return false;
1966
+ }
1967
+ }
1968
+ parseEvent(payload) {
1969
+ const data = typeof payload === 'string' ? JSON.parse(payload) : payload;
1970
+ if (!data.type || !data.data) {
1971
+ throw new Error('Invalid webhook event format');
1972
+ }
1973
+ const event = {
1974
+ type: data.type,
1975
+ timestamp: data.timestamp || Date.now(),
1976
+ data: data.data,
1977
+ shop_id: data.shop_id,
1978
+ user_id: data.user_id
1979
+ };
1980
+ this.triggerHandlers(event);
1981
+ return event;
1982
+ }
1983
+ on(eventType, handler) {
1984
+ if (!this.handlers.has(eventType)) {
1985
+ this.handlers.set(eventType, new Set());
1986
+ }
1987
+ const handlers = this.handlers.get(eventType);
1988
+ if (handlers) {
1989
+ handlers.add(handler);
1990
+ }
1991
+ }
1992
+ off(eventType, handler) {
1993
+ const handlers = this.handlers.get(eventType);
1994
+ if (handlers) {
1995
+ handlers.delete(handler);
1996
+ }
1997
+ }
1998
+ removeAllListeners(eventType) {
1999
+ if (eventType) {
2000
+ this.handlers.delete(eventType);
2001
+ }
2002
+ else {
2003
+ this.handlers.clear();
2004
+ }
2005
+ }
2006
+ async triggerHandlers(event) {
2007
+ const handlers = this.handlers.get(event.type);
2008
+ if (!handlers || handlers.size === 0) {
2009
+ return;
2010
+ }
2011
+ const promises = Array.from(handlers).map(handler => {
2012
+ try {
2013
+ return Promise.resolve(handler(event.data));
2014
+ }
2015
+ catch (error) {
2016
+ console.error(`Error in webhook handler for ${event.type}:`, error);
2017
+ return Promise.resolve();
2018
+ }
2019
+ });
2020
+ await Promise.all(promises);
2021
+ }
2022
+ timingSafeEqual(a, b) {
2023
+ if (this.crypto?.timingSafeEqual) {
2024
+ const bufA = Buffer.from(a);
2025
+ const bufB = Buffer.from(b);
2026
+ if (bufA.length !== bufB.length) {
2027
+ return false;
2028
+ }
2029
+ return this.crypto.timingSafeEqual(bufA, bufB);
2030
+ }
2031
+ else {
2032
+ return a === b;
2033
+ }
2034
+ }
2035
+ getHandlerCount(eventType) {
2036
+ return this.handlers.get(eventType)?.size || 0;
2037
+ }
2038
+ getRegisteredEventTypes() {
2039
+ return Array.from(this.handlers.keys());
2040
+ }
2041
+ }
2042
+ function createWebhookHandler(config) {
2043
+ return new EtsyWebhookHandler(config);
2044
+ }
2045
+
2046
+ class LRUCache {
2047
+ constructor(config = {}) {
2048
+ this.cache = new Map();
2049
+ this.currentSize = 0;
2050
+ this.stats = {
2051
+ hits: 0,
2052
+ misses: 0,
2053
+ evictions: 0
2054
+ };
2055
+ this.maxSize = config.maxSize ?? 10485760;
2056
+ this.maxEntries = config.maxEntries ?? 1000;
2057
+ this.ttl = (config.ttl ?? 3600) * 1000;
2058
+ this.trackStats = config.trackStats ?? true;
2059
+ }
2060
+ async get(key) {
2061
+ const entry = this.cache.get(key);
2062
+ if (!entry) {
2063
+ if (this.trackStats)
2064
+ this.stats.misses++;
2065
+ return null;
2066
+ }
2067
+ if (Date.now() > entry.expires) {
2068
+ this.cache.delete(key);
2069
+ this.currentSize -= entry.size;
2070
+ if (this.trackStats)
2071
+ this.stats.misses++;
2072
+ return null;
2073
+ }
2074
+ entry.lastAccessed = Date.now();
2075
+ entry.accessCount++;
2076
+ this.cache.delete(key);
2077
+ this.cache.set(key, entry);
2078
+ if (this.trackStats)
2079
+ this.stats.hits++;
2080
+ return entry.value;
2081
+ }
2082
+ async set(key, value, ttl) {
2083
+ const size = this.estimateSize(value);
2084
+ const expiryTime = ttl ? ttl * 1000 : this.ttl;
2085
+ const now = Date.now();
2086
+ const existing = this.cache.get(key);
2087
+ if (existing) {
2088
+ this.currentSize -= existing.size;
2089
+ this.cache.delete(key);
2090
+ }
2091
+ while ((this.currentSize + size > this.maxSize || this.cache.size >= this.maxEntries) &&
2092
+ this.cache.size > 0) {
2093
+ await this.evictLRU();
2094
+ }
2095
+ const entry = {
2096
+ key,
2097
+ value,
2098
+ expires: now + expiryTime,
2099
+ size,
2100
+ accessCount: 0,
2101
+ lastAccessed: now,
2102
+ created: now
2103
+ };
2104
+ this.cache.set(key, entry);
2105
+ this.currentSize += size;
2106
+ }
2107
+ async delete(key) {
2108
+ const entry = this.cache.get(key);
2109
+ if (entry) {
2110
+ this.cache.delete(key);
2111
+ this.currentSize -= entry.size;
2112
+ }
2113
+ }
2114
+ async clear() {
2115
+ this.cache.clear();
2116
+ this.currentSize = 0;
2117
+ this.stats = { hits: 0, misses: 0, evictions: 0 };
2118
+ }
2119
+ async evictLRU() {
2120
+ const firstKey = this.cache.keys().next().value;
2121
+ if (firstKey) {
2122
+ const entry = this.cache.get(firstKey);
2123
+ if (entry) {
2124
+ this.currentSize -= entry.size;
2125
+ }
2126
+ this.cache.delete(firstKey);
2127
+ if (this.trackStats)
2128
+ this.stats.evictions++;
2129
+ }
2130
+ }
2131
+ estimateSize(str) {
2132
+ return str.length * 2;
2133
+ }
2134
+ getStats() {
2135
+ const total = this.stats.hits + this.stats.misses;
2136
+ return {
2137
+ hits: this.stats.hits,
2138
+ misses: this.stats.misses,
2139
+ hitRate: total > 0 ? this.stats.hits / total : 0,
2140
+ missRate: total > 0 ? this.stats.misses / total : 0,
2141
+ size: this.currentSize,
2142
+ entryCount: this.cache.size,
2143
+ evictions: this.stats.evictions,
2144
+ maxSize: this.maxSize,
2145
+ maxEntries: this.maxEntries
2146
+ };
2147
+ }
2148
+ }
2149
+ class LFUCache {
2150
+ constructor(config = {}) {
2151
+ this.cache = new Map();
2152
+ this.currentSize = 0;
2153
+ this.stats = {
2154
+ hits: 0,
2155
+ misses: 0,
2156
+ evictions: 0
2157
+ };
2158
+ this.maxSize = config.maxSize ?? 10485760;
2159
+ this.maxEntries = config.maxEntries ?? 1000;
2160
+ this.ttl = (config.ttl ?? 3600) * 1000;
2161
+ this.trackStats = config.trackStats ?? true;
2162
+ }
2163
+ async get(key) {
2164
+ const entry = this.cache.get(key);
2165
+ if (!entry) {
2166
+ if (this.trackStats)
2167
+ this.stats.misses++;
2168
+ return null;
2169
+ }
2170
+ if (Date.now() > entry.expires) {
2171
+ this.cache.delete(key);
2172
+ this.currentSize -= entry.size;
2173
+ if (this.trackStats)
2174
+ this.stats.misses++;
2175
+ return null;
2176
+ }
2177
+ entry.accessCount++;
2178
+ entry.lastAccessed = Date.now();
2179
+ if (this.trackStats)
2180
+ this.stats.hits++;
2181
+ return entry.value;
2182
+ }
2183
+ async set(key, value, ttl) {
2184
+ const size = this.estimateSize(value);
2185
+ const expiryTime = ttl ? ttl * 1000 : this.ttl;
2186
+ const now = Date.now();
2187
+ const existing = this.cache.get(key);
2188
+ if (existing) {
2189
+ this.currentSize -= existing.size;
2190
+ this.cache.delete(key);
2191
+ }
2192
+ while ((this.currentSize + size > this.maxSize || this.cache.size >= this.maxEntries) &&
2193
+ this.cache.size > 0) {
2194
+ await this.evictLFU();
2195
+ }
2196
+ const entry = {
2197
+ key,
2198
+ value,
2199
+ expires: now + expiryTime,
2200
+ size,
2201
+ accessCount: 0,
2202
+ lastAccessed: now,
2203
+ created: now
2204
+ };
2205
+ this.cache.set(key, entry);
2206
+ this.currentSize += size;
2207
+ }
2208
+ async delete(key) {
2209
+ const entry = this.cache.get(key);
2210
+ if (entry) {
2211
+ this.cache.delete(key);
2212
+ this.currentSize -= entry.size;
2213
+ }
2214
+ }
2215
+ async clear() {
2216
+ this.cache.clear();
2217
+ this.currentSize = 0;
2218
+ this.stats = { hits: 0, misses: 0, evictions: 0 };
2219
+ }
2220
+ async evictLFU() {
2221
+ let minAccessCount = Infinity;
2222
+ let keyToEvict = null;
2223
+ for (const [key, entry] of this.cache.entries()) {
2224
+ if (entry.accessCount < minAccessCount) {
2225
+ minAccessCount = entry.accessCount;
2226
+ keyToEvict = key;
2227
+ }
2228
+ }
2229
+ if (keyToEvict) {
2230
+ const entry = this.cache.get(keyToEvict);
2231
+ if (entry) {
2232
+ this.currentSize -= entry.size;
2233
+ }
2234
+ this.cache.delete(keyToEvict);
2235
+ if (this.trackStats)
2236
+ this.stats.evictions++;
2237
+ }
2238
+ }
2239
+ estimateSize(str) {
2240
+ return str.length * 2;
2241
+ }
2242
+ getStats() {
2243
+ const total = this.stats.hits + this.stats.misses;
2244
+ return {
2245
+ hits: this.stats.hits,
2246
+ misses: this.stats.misses,
2247
+ hitRate: total > 0 ? this.stats.hits / total : 0,
2248
+ missRate: total > 0 ? this.stats.misses / total : 0,
2249
+ size: this.currentSize,
2250
+ entryCount: this.cache.size,
2251
+ evictions: this.stats.evictions,
2252
+ maxSize: this.maxSize,
2253
+ maxEntries: this.maxEntries
2254
+ };
2255
+ }
2256
+ }
2257
+ class CacheWithInvalidation {
2258
+ constructor(baseCache, config = {}) {
2259
+ this.baseCache = baseCache;
2260
+ this.invalidationPatterns = new Set(config.invalidateOn?.patterns || []);
2261
+ this.mutationPatterns = new Map();
2262
+ if (config.invalidateOn?.mutations !== false) {
2263
+ this.mutationPatterns.set('shops/*/update', ['shops/*']);
2264
+ this.mutationPatterns.set('listings/*/update', ['listings/*']);
2265
+ this.mutationPatterns.set('receipts/*/update', ['receipts/*']);
2266
+ }
2267
+ }
2268
+ async get(key) {
2269
+ return this.baseCache.get(key);
2270
+ }
2271
+ async set(key, value, ttl) {
2272
+ return this.baseCache.set(key, value, ttl);
2273
+ }
2274
+ async delete(key) {
2275
+ return this.baseCache.delete(key);
2276
+ }
2277
+ async clear() {
2278
+ return this.baseCache.clear();
2279
+ }
2280
+ async invalidatePattern(_pattern) {
2281
+ await this.baseCache.clear();
2282
+ }
2283
+ async invalidateOnMutation(mutationType) {
2284
+ const patterns = this.mutationPatterns.get(mutationType);
2285
+ if (patterns) {
2286
+ for (const pattern of patterns) {
2287
+ await this.invalidatePattern(pattern);
2288
+ }
2289
+ }
2290
+ }
2291
+ addInvalidationPattern(pattern) {
2292
+ this.invalidationPatterns.add(pattern);
2293
+ }
2294
+ removeInvalidationPattern(pattern) {
2295
+ this.invalidationPatterns.delete(pattern);
2296
+ }
2297
+ addMutationPattern(mutationType, patterns) {
2298
+ this.mutationPatterns.set(mutationType, patterns);
2299
+ }
2300
+ }
2301
+ class RedisCacheStorage {
2302
+ constructor(config, client) {
2303
+ this.config = config;
2304
+ this.keyPrefix = config.keyPrefix || 'etsy:';
2305
+ if (!client) {
2306
+ throw new Error('Redis client must be provided. Install a Redis client library (e.g., ioredis) and pass the client instance.');
2307
+ }
2308
+ this.client = client;
2309
+ }
2310
+ async get(key) {
2311
+ try {
2312
+ const value = await this.client.get(this.keyPrefix + key);
2313
+ return value;
2314
+ }
2315
+ catch (error) {
2316
+ console.error('Redis get error:', error);
2317
+ return null;
2318
+ }
2319
+ }
2320
+ async set(key, value, ttl = 3600) {
2321
+ try {
2322
+ await this.client.setex(this.keyPrefix + key, ttl, value);
2323
+ }
2324
+ catch (error) {
2325
+ console.error('Redis set error:', error);
2326
+ }
2327
+ }
2328
+ async delete(key) {
2329
+ try {
2330
+ await this.client.del(this.keyPrefix + key);
2331
+ }
2332
+ catch (error) {
2333
+ console.error('Redis delete error:', error);
2334
+ }
2335
+ }
2336
+ async clear() {
2337
+ try {
2338
+ const keys = await this.client.keys(this.keyPrefix + '*');
2339
+ if (keys.length > 0) {
2340
+ await this.client.del(...keys);
2341
+ }
2342
+ }
2343
+ catch (error) {
2344
+ console.error('Redis clear error:', error);
2345
+ }
2346
+ }
2347
+ getClient() {
2348
+ return this.client;
2349
+ }
2350
+ }
2351
+ function createCacheStorage(config = {}) {
2352
+ const strategy = config.strategy || 'lru';
2353
+ switch (strategy) {
2354
+ case 'lru':
2355
+ return new LRUCache(config);
2356
+ case 'lfu':
2357
+ return new LFUCache(config);
2358
+ case 'ttl':
2359
+ return new LRUCache(config);
2360
+ default:
2361
+ return new LRUCache(config);
2362
+ }
2363
+ }
2364
+ function createCacheWithInvalidation(baseCache, config = {}) {
2365
+ return new CacheWithInvalidation(baseCache, config);
2366
+ }
2367
+ function createRedisCacheStorage(config, client) {
2368
+ return new RedisCacheStorage(config, client);
2369
+ }
2370
+
2371
+ class ListingQueryBuilder {
2372
+ constructor(client, shopId) {
2373
+ this.params = {};
2374
+ this.includeFields = [];
2375
+ this.client = client;
2376
+ this.shopId = shopId;
2377
+ }
2378
+ where(filters) {
2379
+ if (filters.state) {
2380
+ this.params.state = filters.state;
2381
+ }
2382
+ return this;
2383
+ }
2384
+ include(fields) {
2385
+ this.includeFields = fields;
2386
+ this.params.includes = fields;
2387
+ return this;
2388
+ }
2389
+ limit(limit) {
2390
+ this.params.limit = limit;
2391
+ return this;
2392
+ }
2393
+ offset(offset) {
2394
+ this.params.offset = offset;
2395
+ return this;
2396
+ }
2397
+ sortBy(field, order = 'asc') {
2398
+ this.params.sort_on = field;
2399
+ this.params.sort_order = (order === 'asc' ? 'up' : 'down');
2400
+ return this;
2401
+ }
2402
+ async fetch() {
2403
+ if (!this.shopId) {
2404
+ throw new Error('Shop ID is required for listing queries');
2405
+ }
2406
+ return this.client.getListingsByShop(this.shopId, this.params);
2407
+ }
2408
+ async first() {
2409
+ this.params.limit = 1;
2410
+ const results = await this.fetch();
2411
+ return results[0] || null;
2412
+ }
2413
+ async count() {
2414
+ const results = await this.fetch();
2415
+ return results.length;
2416
+ }
2417
+ }
2418
+ class ReceiptQueryBuilder {
2419
+ constructor(client, shopId) {
2420
+ this.params = {};
2421
+ this.client = client;
2422
+ this.shopId = shopId;
2423
+ }
2424
+ where(filters) {
2425
+ Object.assign(this.params, filters);
2426
+ return this;
2427
+ }
2428
+ limit(limit) {
2429
+ this.params.limit = limit;
2430
+ return this;
2431
+ }
2432
+ offset(offset) {
2433
+ this.params.offset = offset;
2434
+ return this;
2435
+ }
2436
+ sortBy(field, order = 'desc') {
2437
+ this.params.sort_on = field;
2438
+ this.params.sort_order = (order === 'asc' ? 'up' : 'down');
2439
+ return this;
2440
+ }
2441
+ async fetch() {
2442
+ return this.client.getShopReceipts(this.shopId, this.params);
2443
+ }
2444
+ async first() {
2445
+ this.params.limit = 1;
2446
+ const results = await this.fetch();
2447
+ return results[0] || null;
2448
+ }
2449
+ }
2450
+ class BatchQueryExecutor {
2451
+ constructor(client) {
2452
+ this.operations = [];
2453
+ this.client = client;
2454
+ }
2455
+ getShop(shopId) {
2456
+ this.operations.push(() => this.client.getShop(shopId));
2457
+ return this;
2458
+ }
2459
+ getListings(shopId, params) {
2460
+ this.operations.push(() => this.client.getListingsByShop(shopId, params));
2461
+ return this;
2462
+ }
2463
+ getReceipts(shopId, params) {
2464
+ this.operations.push(() => this.client.getShopReceipts(shopId, params));
2465
+ return this;
2466
+ }
2467
+ custom(operation) {
2468
+ this.operations.push(operation);
2469
+ return this;
2470
+ }
2471
+ async execute() {
2472
+ return Promise.all(this.operations.map(op => op()));
2473
+ }
2474
+ async executeSequential() {
2475
+ const results = [];
2476
+ for (const operation of this.operations) {
2477
+ results.push(await operation());
2478
+ }
2479
+ return results;
2480
+ }
2481
+ clear() {
2482
+ this.operations = [];
2483
+ return this;
2484
+ }
2485
+ size() {
2486
+ return this.operations.length;
2487
+ }
2488
+ }
2489
+ function withQueryBuilder(client) {
2490
+ const extendedClient = client;
2491
+ extendedClient.listings = function (shopId) {
2492
+ return new ListingQueryBuilder(this, shopId);
2493
+ };
2494
+ extendedClient.receipts = function (shopId) {
2495
+ return new ReceiptQueryBuilder(this, shopId);
2496
+ };
2497
+ extendedClient.batch = function () {
2498
+ return new BatchQueryExecutor(this);
2499
+ };
2500
+ return extendedClient;
2501
+ }
2502
+ function createListingQuery(client, shopId) {
2503
+ return new ListingQueryBuilder(client, shopId);
2504
+ }
2505
+ function createReceiptQuery(client, shopId) {
2506
+ return new ReceiptQueryBuilder(client, shopId);
2507
+ }
2508
+ function createBatchQuery(client) {
2509
+ return new BatchQueryExecutor(client);
2510
+ }
2511
+
2512
+ async function encryptAES256GCM(data, key) {
2513
+ if (!isNode$1) {
2514
+ throw new Error('AES-256-GCM encryption is only available in Node.js environments');
2515
+ }
2516
+ const crypto = await import('crypto');
2517
+ const keyBuffer = Buffer.isBuffer(key) ? key : Buffer.from(key, 'utf8');
2518
+ if (keyBuffer.length !== 32) {
2519
+ throw new Error('Encryption key must be exactly 32 bytes for AES-256');
2520
+ }
2521
+ const iv = crypto.randomBytes(12);
2522
+ const cipher = crypto.createCipheriv('aes-256-gcm', keyBuffer, iv);
2523
+ const encrypted = Buffer.concat([
2524
+ cipher.update(data, 'utf8'),
2525
+ cipher.final()
2526
+ ]);
2527
+ const authTag = cipher.getAuthTag();
2528
+ return {
2529
+ ciphertext: encrypted.toString('base64'),
2530
+ iv: iv.toString('base64'),
2531
+ authTag: authTag.toString('base64'),
2532
+ algorithm: 'aes-256-gcm'
2533
+ };
2534
+ }
2535
+ async function decryptAES256GCM(encryptedData, key) {
2536
+ if (!isNode$1) {
2537
+ throw new Error('AES-256-GCM decryption is only available in Node.js environments');
2538
+ }
2539
+ const crypto = await import('crypto');
2540
+ const keyBuffer = Buffer.isBuffer(key) ? key : Buffer.from(key, 'utf8');
2541
+ if (keyBuffer.length !== 32) {
2542
+ throw new Error('Decryption key must be exactly 32 bytes for AES-256');
2543
+ }
2544
+ if (encryptedData.algorithm !== 'aes-256-gcm') {
2545
+ throw new Error(`Unsupported encryption algorithm: ${encryptedData.algorithm}`);
2546
+ }
2547
+ const iv = Buffer.from(encryptedData.iv, 'base64');
2548
+ const authTag = Buffer.from(encryptedData.authTag, 'base64');
2549
+ const ciphertext = Buffer.from(encryptedData.ciphertext, 'base64');
2550
+ const decipher = crypto.createDecipheriv('aes-256-gcm', keyBuffer, iv);
2551
+ decipher.setAuthTag(authTag);
2552
+ const decrypted = Buffer.concat([
2553
+ decipher.update(ciphertext),
2554
+ decipher.final()
2555
+ ]);
2556
+ return decrypted.toString('utf8');
2557
+ }
2558
+ async function generateEncryptionKey(length = 32) {
2559
+ if (!isNode$1) {
2560
+ throw new Error('Key generation is only available in Node.js environments');
2561
+ }
2562
+ const crypto = await import('crypto');
2563
+ return crypto.randomBytes(length);
2564
+ }
2565
+ async function deriveKeyFromPassword(password, salt, iterations = 100000, keyLength = 32) {
2566
+ if (!isNode$1) {
2567
+ throw new Error('Key derivation is only available in Node.js environments');
2568
+ }
2569
+ const crypto = await import('crypto');
2570
+ const saltBuffer = Buffer.isBuffer(salt) ? salt : Buffer.from(salt, 'utf8');
2571
+ return new Promise((resolve, reject) => {
2572
+ crypto.pbkdf2(password, saltBuffer, iterations, keyLength, 'sha256', (err, derivedKey) => {
2573
+ if (err) {
2574
+ reject(err);
2575
+ }
2576
+ else {
2577
+ resolve(derivedKey);
2578
+ }
2579
+ });
2580
+ });
2581
+ }
2582
+ function validateEncryptionKey(key, requiredLength = 32) {
2583
+ const keyBuffer = Buffer.isBuffer(key) ? key : Buffer.from(key, 'utf8');
2584
+ if (keyBuffer.length !== requiredLength) {
2585
+ throw new Error(`Invalid encryption key length: expected ${requiredLength} bytes, got ${keyBuffer.length} bytes`);
2586
+ }
2587
+ return true;
2588
+ }
2589
+
2590
+ class EncryptedFileTokenStorage {
2591
+ constructor(config) {
2592
+ if (!isNode$1) {
2593
+ throw new Error('EncryptedFileTokenStorage is only available in Node.js environments');
2594
+ }
2595
+ this.filePath = config.filePath;
2596
+ this.fileMode = config.fileMode ?? 0o600;
2597
+ const keyBuffer = Buffer.isBuffer(config.encryptionKey)
2598
+ ? config.encryptionKey
2599
+ : Buffer.from(config.encryptionKey, 'utf8');
2600
+ validateEncryptionKey(keyBuffer, 32);
2601
+ this.encryptionKey = keyBuffer;
2602
+ }
2603
+ async save(tokens) {
2604
+ if (!isNode$1) {
2605
+ throw new Error('EncryptedFileTokenStorage is only available in Node.js');
2606
+ }
2607
+ try {
2608
+ const tokenData = JSON.stringify(tokens);
2609
+ const encrypted = await encryptAES256GCM(tokenData, this.encryptionKey);
2610
+ const fileData = {
2611
+ version: 1,
2612
+ encrypted,
2613
+ timestamp: Date.now()
2614
+ };
2615
+ const fileContent = JSON.stringify(fileData, null, 2);
2616
+ await this._writeFile(this.filePath, fileContent);
2617
+ await this._setFilePermissions(this.filePath, this.fileMode);
2618
+ }
2619
+ catch (error) {
2620
+ throw new Error(`Failed to save encrypted tokens: ${error instanceof Error ? error.message : 'Unknown error'}`);
2621
+ }
2622
+ }
2623
+ async load() {
2624
+ if (!isNode$1) {
2625
+ return null;
2626
+ }
2627
+ try {
2628
+ const fileContent = await this._readFile(this.filePath);
2629
+ const fileData = JSON.parse(fileContent);
2630
+ if (fileData.version !== 1) {
2631
+ throw new Error(`Unsupported encrypted file format version: ${fileData.version}`);
2632
+ }
2633
+ const decryptedData = await decryptAES256GCM(fileData.encrypted, this.encryptionKey);
2634
+ const tokens = JSON.parse(decryptedData);
2635
+ if (tokens.expires_at) {
2636
+ tokens.expires_at = new Date(tokens.expires_at);
2637
+ }
2638
+ return tokens;
2639
+ }
2640
+ catch (error) {
2641
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
2642
+ return null;
2643
+ }
2644
+ console.error('Failed to load encrypted tokens:', error instanceof Error ? error.message : 'Unknown error');
2645
+ return null;
2646
+ }
2647
+ }
2648
+ async clear() {
2649
+ if (!isNode$1) {
2650
+ return;
2651
+ }
2652
+ try {
2653
+ await this._deleteFile(this.filePath);
2654
+ }
2655
+ catch (error) {
2656
+ if (error instanceof Error && 'code' in error && error.code !== 'ENOENT') {
2657
+ console.error('Failed to clear encrypted tokens:', error instanceof Error ? error.message : 'Unknown error');
2658
+ }
2659
+ }
2660
+ }
2661
+ getFilePath() {
2662
+ return this.filePath;
2663
+ }
2664
+ async exists() {
2665
+ if (!isNode$1) {
2666
+ return false;
2667
+ }
2668
+ try {
2669
+ await this._stat(this.filePath);
2670
+ return true;
2671
+ }
2672
+ catch {
2673
+ return false;
2674
+ }
2675
+ }
2676
+ async _writeFile(filePath, data) {
2677
+ if (typeof process === 'undefined')
2678
+ return;
2679
+ const fs = await import('fs');
2680
+ await fs.promises.writeFile(filePath, data, 'utf8');
2681
+ }
2682
+ async _setFilePermissions(filePath, mode) {
2683
+ if (typeof process === 'undefined')
2684
+ return;
2685
+ try {
2686
+ const fs = await import('fs');
2687
+ await fs.promises.chmod(filePath, mode);
2688
+ }
2689
+ catch {
2690
+ }
2691
+ }
2692
+ async _readFile(filePath) {
2693
+ if (typeof process === 'undefined')
2694
+ throw new Error('Not available');
2695
+ const fs = await import('fs');
2696
+ return await fs.promises.readFile(filePath, 'utf8');
2697
+ }
2698
+ async _deleteFile(filePath) {
2699
+ if (typeof process === 'undefined')
2700
+ return;
2701
+ const fs = await import('fs');
2702
+ await fs.promises.unlink(filePath);
2703
+ }
2704
+ async _stat(filePath) {
2705
+ if (typeof process === 'undefined')
2706
+ throw new Error('Not available');
2707
+ const fs = await import('fs');
2708
+ return await fs.promises.stat(filePath);
2709
+ }
2710
+ }
2711
+
2712
+ class WebhookSecurity {
2713
+ constructor(config) {
2714
+ this.secret = config.secret;
2715
+ this.algorithm = config.algorithm || 'sha256';
2716
+ if (!this.secret) {
2717
+ throw new Error('Webhook secret is required');
2718
+ }
2719
+ if (isNode$1) {
2720
+ try {
2721
+ this.crypto = require('crypto');
2722
+ }
2723
+ catch {
2724
+ throw new Error('crypto module not available, webhook security requires Node.js');
2725
+ }
2726
+ }
2727
+ else {
2728
+ throw new Error('WebhookSecurity is only available in Node.js environments');
2729
+ }
2730
+ }
2731
+ signRequest(payload, encoding = 'hex') {
2732
+ if (!this.crypto) {
2733
+ throw new Error('Crypto module not available');
2734
+ }
2735
+ const data = typeof payload === 'string' ? payload : JSON.stringify(payload);
2736
+ const hmac = this.crypto
2737
+ .createHmac(this.algorithm, this.secret)
2738
+ .update(data)
2739
+ .digest(encoding);
2740
+ return hmac;
2741
+ }
2742
+ verifySignature(payload, signature, encoding = 'hex') {
2743
+ if (!this.crypto) {
2744
+ throw new Error('Crypto module not available');
2745
+ }
2746
+ try {
2747
+ const expectedSignature = this.signRequest(payload, encoding);
2748
+ return this.timingSafeEqual(expectedSignature, signature);
2749
+ }
2750
+ catch (error) {
2751
+ console.error('Signature verification failed:', error);
2752
+ return false;
2753
+ }
2754
+ }
2755
+ signRequestWithTimestamp(payload, timestamp, encoding = 'hex') {
2756
+ const ts = timestamp || Math.floor(Date.now() / 1000);
2757
+ const data = typeof payload === 'string' ? payload : JSON.stringify(payload);
2758
+ const signedPayload = `${ts}.${data}`;
2759
+ const signature = this.signRequest(signedPayload, encoding);
2760
+ return {
2761
+ timestamp: ts,
2762
+ signature
2763
+ };
2764
+ }
2765
+ verifySignatureWithTimestamp(payload, signature, timestamp, maxAge = 300, encoding = 'hex') {
2766
+ const now = Math.floor(Date.now() / 1000);
2767
+ const age = now - timestamp;
2768
+ if (age < 0) {
2769
+ console.warn('Webhook timestamp is in the future');
2770
+ return false;
2771
+ }
2772
+ if (age > maxAge) {
2773
+ console.warn(`Webhook request is too old: ${age} seconds`);
2774
+ return false;
2775
+ }
2776
+ const data = typeof payload === 'string' ? payload : JSON.stringify(payload);
2777
+ const signedPayload = `${timestamp}.${data}`;
2778
+ return this.verifySignature(signedPayload, signature, encoding);
2779
+ }
2780
+ timingSafeEqual(a, b) {
2781
+ if (!this.crypto?.timingSafeEqual) {
2782
+ console.warn('crypto.timingSafeEqual not available, using simple comparison');
2783
+ return a === b;
2784
+ }
2785
+ const bufA = Buffer.from(a);
2786
+ const bufB = Buffer.from(b);
2787
+ if (bufA.length !== bufB.length) {
2788
+ return false;
2789
+ }
2790
+ return this.crypto.timingSafeEqual(bufA, bufB);
2791
+ }
2792
+ getAlgorithm() {
2793
+ return this.algorithm;
2794
+ }
2795
+ updateSecret(newSecret) {
2796
+ if (!newSecret) {
2797
+ throw new Error('Secret cannot be empty');
2798
+ }
2799
+ this.secret = newSecret;
2800
+ }
2801
+ }
2802
+ function createWebhookSecurity(secret, algorithm = 'sha256') {
2803
+ return new WebhookSecurity({ secret, algorithm });
2804
+ }
2805
+
920
2806
  function createEtsyClient(config) {
921
2807
  return new EtsyClient(config);
922
2808
  }
@@ -929,43 +2815,81 @@ function createTokenManager(config, storage) {
929
2815
  function createRateLimiter(config) {
930
2816
  return new EtsyRateLimiter(config);
931
2817
  }
932
- const VERSION = '1.0.0';
2818
+ const VERSION = '2.2.0';
933
2819
  const LIBRARY_NAME = 'etsy-v3-api-client';
934
2820
  function getLibraryInfo() {
935
2821
  return {
936
2822
  name: LIBRARY_NAME,
937
2823
  version: VERSION,
938
2824
  description: 'JavaScript/TypeScript client for the Etsy Open API v3 with OAuth 2.0 authentication',
939
- author: 'Henry',
2825
+ author: 'profplum700',
940
2826
  license: 'MIT',
941
2827
  homepage: 'https://github.com/ForestHillArtsHouse/etsy-v3-api-client#readme'
942
2828
  };
943
2829
  }
944
2830
 
945
2831
  exports.AuthHelper = AuthHelper;
2832
+ exports.BatchQueryExecutor = BatchQueryExecutor;
2833
+ exports.BulkOperationManager = BulkOperationManager;
946
2834
  exports.COMMON_SCOPE_COMBINATIONS = COMMON_SCOPE_COMBINATIONS;
2835
+ exports.CacheWithInvalidation = CacheWithInvalidation;
2836
+ exports.CreateListingSchema = CreateListingSchema;
2837
+ exports.DEFAULT_RETRY_CONFIG = DEFAULT_RETRY_CONFIG;
947
2838
  exports.ETSY_SCOPES = ETSY_SCOPES;
2839
+ exports.EncryptedFileTokenStorage = EncryptedFileTokenStorage;
948
2840
  exports.EtsyApiError = EtsyApiError;
949
2841
  exports.EtsyAuthError = EtsyAuthError;
950
2842
  exports.EtsyClient = EtsyClient;
951
2843
  exports.EtsyRateLimitError = EtsyRateLimitError;
952
2844
  exports.EtsyRateLimiter = EtsyRateLimiter;
2845
+ exports.EtsyWebhookHandler = EtsyWebhookHandler;
2846
+ exports.FieldValidator = FieldValidator;
953
2847
  exports.FileTokenStorage = FileTokenStorage;
2848
+ exports.LFUCache = LFUCache;
954
2849
  exports.LIBRARY_NAME = LIBRARY_NAME;
2850
+ exports.LRUCache = LRUCache;
2851
+ exports.ListingQueryBuilder = ListingQueryBuilder;
955
2852
  exports.LocalStorageTokenStorage = LocalStorageTokenStorage;
956
2853
  exports.MemoryTokenStorage = MemoryTokenStorage;
2854
+ exports.PaginatedResults = PaginatedResults;
2855
+ exports.ReceiptQueryBuilder = ReceiptQueryBuilder;
2856
+ exports.RedisCacheStorage = RedisCacheStorage;
2857
+ exports.RetryManager = RetryManager;
957
2858
  exports.SessionStorageTokenStorage = SessionStorageTokenStorage;
958
2859
  exports.TokenManager = TokenManager;
2860
+ exports.UpdateListingSchema = UpdateListingSchema;
2861
+ exports.UpdateShopSchema = UpdateShopSchema;
959
2862
  exports.VERSION = VERSION;
2863
+ exports.ValidationException = ValidationException;
2864
+ exports.Validator = Validator;
2865
+ exports.WebhookSecurity = WebhookSecurity;
2866
+ exports.combineValidators = combineValidators;
960
2867
  exports.createAuthHelper = createAuthHelper;
2868
+ exports.createBatchQuery = createBatchQuery;
2869
+ exports.createBulkOperationManager = createBulkOperationManager;
2870
+ exports.createCacheStorage = createCacheStorage;
2871
+ exports.createCacheWithInvalidation = createCacheWithInvalidation;
961
2872
  exports.createCodeChallenge = createCodeChallenge;
962
2873
  exports.createDefaultTokenStorage = createDefaultTokenStorage;
963
2874
  exports.createEtsyClient = createEtsyClient;
2875
+ exports.createListingQuery = createListingQuery;
2876
+ exports.createPaginatedResults = createPaginatedResults;
964
2877
  exports.createRateLimiter = createRateLimiter;
2878
+ exports.createReceiptQuery = createReceiptQuery;
2879
+ exports.createRedisCacheStorage = createRedisCacheStorage;
965
2880
  exports.createTokenManager = createTokenManager;
2881
+ exports.createValidator = createValidator;
2882
+ exports.createWebhookHandler = createWebhookHandler;
2883
+ exports.createWebhookSecurity = createWebhookSecurity;
2884
+ exports.decryptAES256GCM = decryptAES256GCM;
966
2885
  exports.default = EtsyClient;
967
2886
  exports.defaultRateLimiter = defaultRateLimiter;
2887
+ exports.deriveKeyFromPassword = deriveKeyFromPassword;
2888
+ exports.encryptAES256GCM = encryptAES256GCM;
2889
+ exports.executeBulkOperation = executeBulkOperation;
2890
+ exports.field = field;
968
2891
  exports.generateCodeVerifier = generateCodeVerifier;
2892
+ exports.generateEncryptionKey = generateEncryptionKey;
969
2893
  exports.generateRandomBase64Url = generateRandomBase64Url;
970
2894
  exports.generateState = generateState;
971
2895
  exports.getAvailableStorage = getAvailableStorage;
@@ -977,4 +2901,9 @@ exports.isBrowser = isBrowser$1;
977
2901
  exports.isNode = isNode$1;
978
2902
  exports.sha256 = sha256;
979
2903
  exports.sha256Base64Url = sha256Base64Url;
2904
+ exports.validate = validate;
2905
+ exports.validateEncryptionKey = validateEncryptionKey;
2906
+ exports.validateOrThrow = validateOrThrow;
2907
+ exports.withQueryBuilder = withQueryBuilder;
2908
+ exports.withRetry = withRetry;
980
2909
  //# sourceMappingURL=node.cjs.map