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