@adobe/spacecat-shared-tokowaka-client 1.5.7 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [@adobe/spacecat-shared-tokowaka-client-v1.6.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tokowaka-client-v1.5.8...@adobe/spacecat-shared-tokowaka-client-v1.6.0) (2026-01-29)
2
+
3
+
4
+ ### Features
5
+
6
+ * new flag opted added in edgeoptimizeconfig ([#1294](https://github.com/adobe/spacecat-shared/issues/1294)) ([8386e38](https://github.com/adobe/spacecat-shared/commit/8386e3805e9e67c18e4569428bef7ac5723eb991))
7
+
8
+ # [@adobe/spacecat-shared-tokowaka-client-v1.5.8](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tokowaka-client-v1.5.7...@adobe/spacecat-shared-tokowaka-client-v1.5.8) (2026-01-27)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * rename x-edge-optimize to x-edgeoptimize ([#1284](https://github.com/adobe/spacecat-shared/issues/1284)) ([63ba2ac](https://github.com/adobe/spacecat-shared/commit/63ba2ac9141612a5f581b0162cb1b30343c5980c))
14
+
1
15
  # [@adobe/spacecat-shared-tokowaka-client-v1.5.7](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tokowaka-client-v1.5.6...@adobe/spacecat-shared-tokowaka-client-v1.5.7) (2026-01-22)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-tokowaka-client",
3
- "version": "1.5.7",
3
+ "version": "1.6.0",
4
4
  "description": "Tokowaka Client for SpaceCat - Edge optimization config management",
5
5
  "type": "module",
6
6
  "engines": {
package/src/index.js CHANGED
@@ -290,7 +290,7 @@ class TokowakaClient {
290
290
  * @param {boolean} options.enhancements - Whether to enable enhancements (default: true)
291
291
  * @returns {Promise<Object>} - Object with s3Path and metaconfig
292
292
  */
293
- async createMetaconfig(url, siteId, options = {}) {
293
+ async createMetaconfig(url, siteId, options = {}, metadata = {}) {
294
294
  if (!hasText(url)) {
295
295
  throw this.#createError('URL is required', HTTP_BAD_REQUEST);
296
296
  }
@@ -316,8 +316,7 @@ class TokowakaClient {
316
316
  patches: {},
317
317
  };
318
318
 
319
- const s3Path = await this.uploadMetaconfig(url, metaconfig);
320
-
319
+ const s3Path = await this.uploadMetaconfig(url, metaconfig, metadata);
321
320
  this.log.info(`Created new Tokowaka metaconfig for ${normalizedHostName} at ${s3Path}`);
322
321
 
323
322
  return metaconfig;
@@ -331,7 +330,7 @@ class TokowakaClient {
331
330
  * @param {Object} options - Optional configuration
332
331
  * @returns {Promise<Object>} - Object with s3Path and metaconfig
333
332
  */
334
- async updateMetaconfig(url, siteId, options = {}) {
333
+ async updateMetaconfig(url, siteId, options = {}, metadata = {}) {
335
334
  if (!hasText(url)) {
336
335
  throw this.#createError('URL is required', HTTP_BAD_REQUEST);
337
336
  }
@@ -371,8 +370,7 @@ class TokowakaClient {
371
370
  ...(hasPrerender && { prerender }),
372
371
  };
373
372
 
374
- const s3Path = await this.uploadMetaconfig(url, metaconfig);
375
-
373
+ const s3Path = await this.uploadMetaconfig(url, metaconfig, metadata);
376
374
  this.log.info(`Updated Tokowaka metaconfig for ${normalizedHostName} at ${s3Path}`);
377
375
 
378
376
  return metaconfig;
@@ -382,9 +380,10 @@ class TokowakaClient {
382
380
  * Uploads domain-level metaconfig to S3
383
381
  * @param {string} url - Full URL (used to extract domain)
384
382
  * @param {Object} metaconfig - Metaconfig object (siteId, apiKeys, prerender)
383
+ * @param {Object} metadata - Optional S3 user-defined metadata (key-value pairs)
385
384
  * @returns {Promise<string>} - S3 key of uploaded metaconfig
386
- */
387
- async uploadMetaconfig(url, metaconfig) {
385
+ */
386
+ async uploadMetaconfig(url, metaconfig, metadata = {}) {
388
387
  if (!hasText(url)) {
389
388
  throw this.#createError('URL is required', HTTP_BAD_REQUEST);
390
389
  }
@@ -397,12 +396,19 @@ class TokowakaClient {
397
396
  const bucketName = this.deployBucketName;
398
397
 
399
398
  try {
400
- const command = new PutObjectCommand({
399
+ const putObjectParams = {
401
400
  Bucket: bucketName,
402
401
  Key: s3Path,
403
402
  Body: JSON.stringify(metaconfig, null, 2),
404
403
  ContentType: 'application/json',
405
- });
404
+ };
405
+
406
+ // Add user-defined metadata if provided
407
+ if (isNonEmptyObject(metadata)) {
408
+ putObjectParams.Metadata = metadata;
409
+ }
410
+
411
+ const command = new PutObjectCommand(putObjectParams);
406
412
 
407
413
  await this.s3Client.send(command);
408
414
  this.log.info(`Successfully uploaded metaconfig to s3://${bucketName}/${s3Path}`);
@@ -52,14 +52,14 @@ async function fetchWithRetry(url, options, maxRetries, retryDelayMs, log, fetch
52
52
  }
53
53
 
54
54
  // Check for edge optimize headers
55
- const cacheHeader = response.headers.get('x-edge-optimize-cache');
56
- const proxyHeader = response.headers.get('x-edge-optimize-proxy');
55
+ const cacheHeader = response.headers.get('x-edgeoptimize-cache');
56
+ const proxyHeader = response.headers.get('x-edgeoptimize-proxy');
57
57
 
58
58
  log.debug(`Headers - cache: ${cacheHeader || 'none'}, proxy: ${proxyHeader || 'none'}`);
59
59
 
60
60
  // Case 1: Cache header present (regardless of proxy) -> Success
61
61
  if (cacheHeader) {
62
- log.debug(`Cache header found (x-edge-optimize-cache: ${cacheHeader}), stopping retry logic`);
62
+ log.debug(`Cache header found (x-edgeoptimize-cache: ${cacheHeader}), stopping retry logic`);
63
63
  return response;
64
64
  }
65
65
 
@@ -80,7 +80,7 @@ async function fetchWithRetry(url, options, maxRetries, retryDelayMs, log, fetch
80
80
  } else {
81
81
  // Last attempt - throw error
82
82
  log.error(`Max retries (${maxRetries}) exhausted. Proxy header present but cache header not found`);
83
- throw new Error(`Cache header (x-edge-optimize-cache) not found after ${maxRetries} retries`);
83
+ throw new Error(`Cache header (x-edgeoptimize-cache) not found after ${maxRetries} retries`);
84
84
  }
85
85
  } catch (error) {
86
86
  log.warn(`Attempt ${attempt} failed for ${fetchType} HTML, error: ${error.message}`);
@@ -158,15 +158,15 @@ export async function fetchHtmlWithWarmup(
158
158
 
159
159
  const headers = {
160
160
  'x-forwarded-host': forwardedHost,
161
- 'x-edge-optimize-api-key': apiKey,
162
- 'x-edge-optimize-url': urlPath,
161
+ 'x-edgeoptimize-api-key': apiKey,
162
+ 'x-edgeoptimize-url': urlPath,
163
163
  'Accept-Encoding': 'identity', // Disable compression to avoid content-length: 0 issue
164
164
  };
165
165
 
166
166
  if (isOptimized) {
167
167
  // Add tokowakaPreview param for optimized HTML
168
168
  fullUrl = `${fullUrl}?tokowakaPreview=true`;
169
- headers['x-edge-optimize-url'] = `${urlPath}?tokowakaPreview=true`;
169
+ headers['x-edgeoptimize-url'] = `${urlPath}?tokowakaPreview=true`;
170
170
  }
171
171
 
172
172
  const fetchOptions = {
@@ -454,6 +454,43 @@ describe('TokowakaClient', () => {
454
454
  expect(error.status).to.equal(500);
455
455
  }
456
456
  });
457
+
458
+ it('should upload metaconfig with user-defined metadata when provided', async () => {
459
+ const metaconfig = {
460
+ siteId: 'site-123',
461
+ prerender: true,
462
+ };
463
+ const metadata = {
464
+ 'last-modified-by': 'john@example.com',
465
+ 'created-by': 'admin',
466
+ };
467
+
468
+ const s3Path = await client.uploadMetaconfig('https://example.com/page1', metaconfig, metadata);
469
+
470
+ expect(s3Path).to.equal('opportunities/example.com/config');
471
+
472
+ const command = s3Client.send.firstCall.args[0];
473
+ expect(command.input.Bucket).to.equal('test-bucket');
474
+ expect(command.input.Key).to.equal('opportunities/example.com/config');
475
+ expect(command.input.ContentType).to.equal('application/json');
476
+ expect(JSON.parse(command.input.Body)).to.deep.equal(metaconfig);
477
+ expect(command.input.Metadata).to.deep.equal(metadata);
478
+ });
479
+
480
+ it('should upload metaconfig without Metadata field when metadata is empty object', async () => {
481
+ const metaconfig = {
482
+ siteId: 'site-123',
483
+ prerender: true,
484
+ };
485
+
486
+ const s3Path = await client.uploadMetaconfig('https://example.com/page1', metaconfig, {});
487
+
488
+ expect(s3Path).to.equal('opportunities/example.com/config');
489
+
490
+ const command = s3Client.send.firstCall.args[0];
491
+ expect(command.input.Bucket).to.equal('test-bucket');
492
+ expect(command.input.Metadata).to.be.undefined;
493
+ });
457
494
  });
458
495
 
459
496
  describe('createMetaconfig', () => {
@@ -567,6 +604,50 @@ describe('TokowakaClient', () => {
567
604
  const command = s3Client.send.firstCall.args[0];
568
605
  expect(command.input.Key).to.equal('opportunities/example.com/config');
569
606
  });
607
+
608
+ it('should include user-defined metadata when metadata is provided', async () => {
609
+ const siteId = 'site-123';
610
+ const url = 'https://www.example.com/page1';
611
+ const noSuchKeyError = new Error('NoSuchKey');
612
+ noSuchKeyError.name = 'NoSuchKey';
613
+ s3Client.send.onFirstCall().rejects(noSuchKeyError);
614
+
615
+ await client.createMetaconfig(url, siteId, {}, { 'last-modified-by': 'john@example.com' });
616
+
617
+ // Second call is the uploadMetaconfig
618
+ const uploadCommand = s3Client.send.secondCall.args[0];
619
+ expect(uploadCommand.input.Metadata).to.deep.equal({
620
+ 'last-modified-by': 'john@example.com',
621
+ });
622
+ });
623
+
624
+ it('should not include metadata when metadata is empty object', async () => {
625
+ const siteId = 'site-123';
626
+ const url = 'https://www.example.com/page1';
627
+ const noSuchKeyError = new Error('NoSuchKey');
628
+ noSuchKeyError.name = 'NoSuchKey';
629
+ s3Client.send.onFirstCall().rejects(noSuchKeyError);
630
+
631
+ await client.createMetaconfig(url, siteId, {}, {});
632
+
633
+ // Second call is the uploadMetaconfig
634
+ const uploadCommand = s3Client.send.secondCall.args[0];
635
+ expect(uploadCommand.input.Metadata).to.be.undefined;
636
+ });
637
+
638
+ it('should not include metadata when metadata is not provided', async () => {
639
+ const siteId = 'site-123';
640
+ const url = 'https://www.example.com/page1';
641
+ const noSuchKeyError = new Error('NoSuchKey');
642
+ noSuchKeyError.name = 'NoSuchKey';
643
+ s3Client.send.onFirstCall().rejects(noSuchKeyError);
644
+
645
+ await client.createMetaconfig(url, siteId);
646
+
647
+ // Second call is the uploadMetaconfig
648
+ const uploadCommand = s3Client.send.secondCall.args[0];
649
+ expect(uploadCommand.input.Metadata).to.be.undefined;
650
+ });
570
651
  });
571
652
 
572
653
  describe('updateMetaconfig', () => {
@@ -1308,6 +1389,47 @@ describe('TokowakaClient', () => {
1308
1389
  expect(result).to.have.property('prerender');
1309
1390
  expect(result.prerender).to.deep.equal(prerenderConfig);
1310
1391
  });
1392
+
1393
+ it('should include user-defined metadata when metadata is provided', async () => {
1394
+ const siteId = 'site-456';
1395
+ const url = 'https://www.example.com';
1396
+
1397
+ await client.updateMetaconfig(url, siteId, {
1398
+ tokowakaEnabled: true,
1399
+ }, { 'last-modified-by': 'jane@example.com' });
1400
+
1401
+ // Second call is the uploadMetaconfig
1402
+ const uploadCommand = s3Client.send.secondCall.args[0];
1403
+ expect(uploadCommand.input.Metadata).to.deep.equal({
1404
+ 'last-modified-by': 'jane@example.com',
1405
+ });
1406
+ });
1407
+
1408
+ it('should not include metadata when metadata is empty object', async () => {
1409
+ const siteId = 'site-456';
1410
+ const url = 'https://www.example.com';
1411
+
1412
+ await client.updateMetaconfig(url, siteId, {
1413
+ tokowakaEnabled: true,
1414
+ }, {});
1415
+
1416
+ // Second call is the uploadMetaconfig
1417
+ const uploadCommand = s3Client.send.secondCall.args[0];
1418
+ expect(uploadCommand.input.Metadata).to.be.undefined;
1419
+ });
1420
+
1421
+ it('should not include metadata when metadata is not provided', async () => {
1422
+ const siteId = 'site-456';
1423
+ const url = 'https://www.example.com';
1424
+
1425
+ await client.updateMetaconfig(url, siteId, {
1426
+ tokowakaEnabled: true,
1427
+ });
1428
+
1429
+ // Second call is the uploadMetaconfig
1430
+ const uploadCommand = s3Client.send.secondCall.args[0];
1431
+ expect(uploadCommand.input.Metadata).to.be.undefined;
1432
+ });
1311
1433
  });
1312
1434
 
1313
1435
  describe('uploadConfig', () => {
@@ -2692,7 +2814,7 @@ describe('TokowakaClient', () => {
2692
2814
  status: 200,
2693
2815
  statusText: 'OK',
2694
2816
  headers: {
2695
- get: (name) => (name === 'x-edge-optimize-cache' ? 'HIT' : null),
2817
+ get: (name) => (name === 'x-edgeoptimize-cache' ? 'HIT' : null),
2696
2818
  },
2697
2819
  text: async () => '<html><body>Test HTML</body></html>',
2698
2820
  });
@@ -104,7 +104,7 @@ describe('HTML Utils', () => {
104
104
  status: 200,
105
105
  statusText: 'OK',
106
106
  headers: {
107
- get: (name) => (name === 'x-edge-optimize-cache' ? 'HIT' : null),
107
+ get: (name) => (name === 'x-edgeoptimize-cache' ? 'HIT' : null),
108
108
  },
109
109
  text: async () => '<html>Test HTML</html>',
110
110
  });
@@ -129,7 +129,7 @@ describe('HTML Utils', () => {
129
129
  status: 200,
130
130
  statusText: 'OK',
131
131
  headers: {
132
- get: (name) => (name === 'x-edge-optimize-cache' ? 'HIT' : null),
132
+ get: (name) => (name === 'x-edgeoptimize-cache' ? 'HIT' : null),
133
133
  },
134
134
  text: async () => '<html>Optimized HTML</html>',
135
135
  });
@@ -208,7 +208,7 @@ describe('HTML Utils', () => {
208
208
  status: 200,
209
209
  statusText: 'OK',
210
210
  headers: {
211
- get: (name) => (name === 'x-edge-optimize-proxy' ? 'true' : null),
211
+ get: (name) => (name === 'x-edgeoptimize-proxy' ? 'true' : null),
212
212
  },
213
213
  text: async () => '<html>Proxy only 1</html>',
214
214
  });
@@ -217,7 +217,7 @@ describe('HTML Utils', () => {
217
217
  status: 200,
218
218
  statusText: 'OK',
219
219
  headers: {
220
- get: (name) => (name === 'x-edge-optimize-proxy' ? 'true' : null),
220
+ get: (name) => (name === 'x-edgeoptimize-proxy' ? 'true' : null),
221
221
  },
222
222
  text: async () => '<html>Proxy only 2</html>',
223
223
  });
@@ -226,7 +226,7 @@ describe('HTML Utils', () => {
226
226
  status: 200,
227
227
  statusText: 'OK',
228
228
  headers: {
229
- get: (name) => (name === 'x-edge-optimize-proxy' ? 'true' : null),
229
+ get: (name) => (name === 'x-edgeoptimize-proxy' ? 'true' : null),
230
230
  },
231
231
  text: async () => '<html>Proxy only 3</html>',
232
232
  });
@@ -244,7 +244,7 @@ describe('HTML Utils', () => {
244
244
  expect.fail('Should have thrown error');
245
245
  } catch (error) {
246
246
  expect(error.message).to.include('Failed to fetch optimized HTML');
247
- expect(error.message).to.include('Cache header (x-edge-optimize-cache) not found after 2 retries');
247
+ expect(error.message).to.include('Cache header (x-edgeoptimize-cache) not found after 2 retries');
248
248
  }
249
249
 
250
250
  // Should have tried 3 times (initial + 2 retries) plus warmup
@@ -268,7 +268,7 @@ describe('HTML Utils', () => {
268
268
  status: 200,
269
269
  statusText: 'OK',
270
270
  headers: {
271
- get: (name) => (name === 'x-edge-optimize-proxy' ? 'true' : null),
271
+ get: (name) => (name === 'x-edgeoptimize-proxy' ? 'true' : null),
272
272
  },
273
273
  text: async () => '<html>Proxy only</html>',
274
274
  });
@@ -278,7 +278,7 @@ describe('HTML Utils', () => {
278
278
  status: 200,
279
279
  statusText: 'OK',
280
280
  headers: {
281
- get: (name) => (name === 'x-edge-optimize-cache' ? 'HIT' : null),
281
+ get: (name) => (name === 'x-edgeoptimize-cache' ? 'HIT' : null),
282
282
  },
283
283
  text: async () => '<html>Cached HTML</html>',
284
284
  });
@@ -483,7 +483,7 @@ describe('HTML Utils', () => {
483
483
  status: 200,
484
484
  statusText: 'OK',
485
485
  headers: {
486
- get: (name) => (name === 'x-edge-optimize-proxy' ? 'true' : null),
486
+ get: (name) => (name === 'x-edgeoptimize-proxy' ? 'true' : null),
487
487
  },
488
488
  text: async () => '<html>Proxy only</html>',
489
489
  });
@@ -494,8 +494,8 @@ describe('HTML Utils', () => {
494
494
  statusText: 'OK',
495
495
  headers: {
496
496
  get: (name) => {
497
- if (name === 'x-edge-optimize-cache') return 'HIT';
498
- if (name === 'x-edge-optimize-proxy') return 'true';
497
+ if (name === 'x-edgeoptimize-cache') return 'HIT';
498
+ if (name === 'x-edgeoptimize-proxy') return 'true';
499
499
  return null;
500
500
  },
501
501
  },
@@ -534,7 +534,7 @@ describe('HTML Utils', () => {
534
534
  status: 200,
535
535
  statusText: 'OK',
536
536
  headers: {
537
- get: (name) => (name === 'x-edge-optimize-proxy' ? 'true' : null),
537
+ get: (name) => (name === 'x-edgeoptimize-proxy' ? 'true' : null),
538
538
  },
539
539
  text: async () => '<html>Proxy only 1</html>',
540
540
  });
@@ -543,7 +543,7 @@ describe('HTML Utils', () => {
543
543
  status: 200,
544
544
  statusText: 'OK',
545
545
  headers: {
546
- get: (name) => (name === 'x-edge-optimize-proxy' ? 'true' : null),
546
+ get: (name) => (name === 'x-edgeoptimize-proxy' ? 'true' : null),
547
547
  },
548
548
  text: async () => '<html>Proxy only 2</html>',
549
549
  });
@@ -552,7 +552,7 @@ describe('HTML Utils', () => {
552
552
  status: 200,
553
553
  statusText: 'OK',
554
554
  headers: {
555
- get: (name) => (name === 'x-edge-optimize-proxy' ? 'true' : null),
555
+ get: (name) => (name === 'x-edgeoptimize-proxy' ? 'true' : null),
556
556
  },
557
557
  text: async () => '<html>Proxy only 3</html>',
558
558
  });
@@ -570,7 +570,7 @@ describe('HTML Utils', () => {
570
570
  expect.fail('Should have thrown error');
571
571
  } catch (error) {
572
572
  expect(error.message).to.include('Failed to fetch original HTML');
573
- expect(error.message).to.include('Cache header (x-edge-optimize-cache) not found after 2 retries');
573
+ expect(error.message).to.include('Cache header (x-edgeoptimize-cache) not found after 2 retries');
574
574
  }
575
575
 
576
576
  // Should have tried 3 times (initial + 2 retries) plus warmup
@@ -594,7 +594,7 @@ describe('HTML Utils', () => {
594
594
  status: 200,
595
595
  statusText: 'OK',
596
596
  headers: {
597
- get: (name) => (name === 'x-edge-optimize-cache' ? 'HIT' : null),
597
+ get: (name) => (name === 'x-edgeoptimize-cache' ? 'HIT' : null),
598
598
  },
599
599
  text: async () => '<html>Cached HTML</html>',
600
600
  });
@@ -632,8 +632,8 @@ describe('HTML Utils', () => {
632
632
  statusText: 'OK',
633
633
  headers: {
634
634
  get: (name) => {
635
- if (name === 'x-edge-optimize-cache') return 'HIT';
636
- if (name === 'x-edge-optimize-proxy') return 'true';
635
+ if (name === 'x-edgeoptimize-cache') return 'HIT';
636
+ if (name === 'x-edgeoptimize-proxy') return 'true';
637
637
  return null;
638
638
  },
639
639
  },
@@ -672,7 +672,7 @@ describe('HTML Utils', () => {
672
672
  status: 200,
673
673
  statusText: 'OK',
674
674
  headers: {
675
- get: (name) => (name === 'x-edge-optimize-cache' ? 'HIT' : null),
675
+ get: (name) => (name === 'x-edgeoptimize-cache' ? 'HIT' : null),
676
676
  },
677
677
  text: async () => '<html>Cache only HTML</html>',
678
678
  });