@adobe/spacecat-shared-tokowaka-client 1.7.1 → 1.7.3

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.7.3](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tokowaka-client-v1.7.2...@adobe/spacecat-shared-tokowaka-client-v1.7.3) (2026-02-04)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * edge-deploy: update patch lastUpdated to now ([#1315](https://github.com/adobe/spacecat-shared/issues/1315)) ([754be98](https://github.com/adobe/spacecat-shared/commit/754be986ac8ee8aa76a53f142338b214eca30b8c))
7
+
8
+ # [@adobe/spacecat-shared-tokowaka-client-v1.7.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tokowaka-client-v1.7.1...@adobe/spacecat-shared-tokowaka-client-v1.7.2) (2026-02-04)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * preview for all using apply Stale ([#1310](https://github.com/adobe/spacecat-shared/issues/1310)) ([ccadd8f](https://github.com/adobe/spacecat-shared/commit/ccadd8f230259c781c33b977fd6214375a563ab9))
14
+
1
15
  # [@adobe/spacecat-shared-tokowaka-client-v1.7.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tokowaka-client-v1.7.0...@adobe/spacecat-shared-tokowaka-client-v1.7.1) (2026-02-03)
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.7.1",
3
+ "version": "1.7.3",
4
4
  "description": "Tokowaka Client for SpaceCat - Edge optimization config management",
5
5
  "type": "module",
6
6
  "engines": {
package/src/index.js CHANGED
@@ -949,6 +949,12 @@ class TokowakaClient {
949
949
  };
950
950
  }
951
951
 
952
+ // preview for all: never drop stale suggestions that are being previewed
953
+ newConfig.patches = newConfig.patches.map((patch) => ({
954
+ ...patch,
955
+ applyStale: true,
956
+ }));
957
+
952
958
  // Merge with existing deployed config to include already-deployed patches for this URL
953
959
  let config = newConfig;
954
960
  if (existingConfig && existingConfig.patches?.length > 0) {
@@ -88,20 +88,13 @@ export default class BaseOpportunityMapper {
88
88
  * @returns {Object} - Base patch object
89
89
  */
90
90
  createBasePatch(suggestion, opportunityId) {
91
- const updatedAt = suggestion.getUpdatedAt();
92
-
93
- // Parse timestamp, fallback to Date.now() if invalid
94
- let lastUpdated = Date.now();
95
- if (updatedAt) {
96
- const parsed = new Date(updatedAt).getTime();
97
- lastUpdated = Number.isNaN(parsed) ? Date.now() : parsed;
98
- }
99
-
100
91
  return {
101
92
  opportunityId,
102
93
  suggestionId: suggestion.getId(),
103
94
  prerenderRequired: this.requiresPrerender(),
104
- lastUpdated,
95
+ // the lastUpdated is kept as 'now' assuming the user has reviewed
96
+ // the suggestion being deployed, we will revisit this if need arises.
97
+ lastUpdated: Date.now(),
105
98
  };
106
99
  }
107
100
 
@@ -108,19 +108,7 @@ export default class FaqMapper extends BaseOpportunityMapper {
108
108
  const firstData = firstSuggestion.getData();
109
109
  const { headingText = 'FAQs', transformRules } = firstData;
110
110
 
111
- // Calculate the most recent lastUpdated from all eligible suggestions
112
- // The heading patch should have the same timestamp as the newest FAQ
113
- const maxLastUpdated = Math.max(...eligibleSuggestions.map((suggestion) => {
114
- const updatedAt = suggestion.getUpdatedAt();
115
-
116
- if (updatedAt) {
117
- const parsed = new Date(updatedAt).getTime();
118
- return Number.isNaN(parsed) ? Date.now() : parsed;
119
- }
120
- return Date.now();
121
- }));
122
-
123
- // Always create/update heading patch with latest timestamp
111
+ // Always create/update heading patch
124
112
  // mergePatches will replace existing heading if it already exists
125
113
  this.log.debug(`Creating/updating heading patch for ${urlPath}`);
126
114
 
@@ -135,7 +123,7 @@ export default class FaqMapper extends BaseOpportunityMapper {
135
123
  opportunityId,
136
124
  // No suggestionId for FAQ heading patch
137
125
  prerenderRequired: this.requiresPrerender(),
138
- lastUpdated: maxLastUpdated,
126
+ lastUpdated: Date.now(),
139
127
  op: transformRules.action,
140
128
  selector: transformRules.selector,
141
129
  value: headingHast,
@@ -2871,6 +2871,51 @@ describe('TokowakaClient', () => {
2871
2871
  expect(s3Client.send).to.have.been.calledOnce;
2872
2872
  });
2873
2873
 
2874
+ it('should set applyStale to true for all preview patches', async () => {
2875
+ // Create a scenario with existing deployed patches
2876
+ client.fetchConfig.resolves({
2877
+ url: 'https://example.com/page1',
2878
+ version: '1.0',
2879
+ forceFail: false,
2880
+ prerender: true,
2881
+ patches: [
2882
+ {
2883
+ op: 'replace',
2884
+ selector: 'h3',
2885
+ value: 'Existing Heading',
2886
+ opportunityId: 'opp-999',
2887
+ suggestionId: 'sugg-999',
2888
+ prerenderRequired: true,
2889
+ lastUpdated: 1234567890,
2890
+ },
2891
+ ],
2892
+ });
2893
+
2894
+ const result = await client.previewSuggestions(
2895
+ mockSite,
2896
+ mockOpportunity,
2897
+ mockSuggestions,
2898
+ { warmupDelayMs: 0 },
2899
+ );
2900
+
2901
+ expect(result.config).to.exist;
2902
+ expect(result.config.patches).to.have.length(3); // 2 new + 1 existing
2903
+
2904
+ // Verify that all new preview patches have applyStale: true
2905
+ const newPatches = result.config.patches.filter(
2906
+ (p) => p.suggestionId === 'sugg-1' || p.suggestionId === 'sugg-2',
2907
+ );
2908
+ expect(newPatches).to.have.length(2);
2909
+ newPatches.forEach((patch) => {
2910
+ expect(patch.applyStale).to.equal(true);
2911
+ });
2912
+
2913
+ // Existing patch should not have applyStale field
2914
+ const existingPatch = result.config.patches.find((p) => p.suggestionId === 'sugg-999');
2915
+ expect(existingPatch).to.exist;
2916
+ expect(existingPatch.applyStale).to.be.undefined;
2917
+ });
2918
+
2874
2919
  it('should preview prerender-only suggestions with no patches', async () => {
2875
2920
  // Update fetchConfig to return existing config with deployed patches
2876
2921
  client.fetchConfig.resolves({
@@ -53,7 +53,7 @@ describe('BaseOpportunityMapper', () => {
53
53
  });
54
54
 
55
55
  describe('createBasePatch', () => {
56
- it('should use getUpdatedAt method when available', () => {
56
+ it('should use Date.now() for lastUpdated', () => {
57
57
  // Create a concrete subclass for testing
58
58
  class TestMapper extends BaseOpportunityMapper {
59
59
  getOpportunityType() { return 'test'; }
@@ -71,11 +71,14 @@ describe('BaseOpportunityMapper', () => {
71
71
  getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
72
72
  };
73
73
 
74
+ const beforeTime = Date.now();
74
75
  const patch = testMapper.createBasePatch(suggestion, 'opp-456');
76
+ const afterTime = Date.now();
75
77
 
76
78
  expect(patch.suggestionId).to.equal('test-123');
77
79
  expect(patch.opportunityId).to.equal('opp-456');
78
- expect(patch.lastUpdated).to.equal(new Date('2025-01-15T10:00:00.000Z').getTime());
80
+ expect(patch.lastUpdated).to.be.at.least(beforeTime);
81
+ expect(patch.lastUpdated).to.be.at.most(afterTime);
79
82
  expect(patch.prerenderRequired).to.be.true;
80
83
  });
81
84
 
@@ -760,7 +760,7 @@ Overall, Bulk positions itself as a better choice for sports nutrition through i
760
760
  expect(patches[1].suggestionId).to.equal('sugg-faq-new'); // FAQ
761
761
  });
762
762
 
763
- it('should use correct updatedAt timestamp for each patch', () => {
763
+ it('should use Date.now() for all patch timestamps', () => {
764
764
  const suggestions = [
765
765
  {
766
766
  getId: () => 'sugg-faq-1',
@@ -798,17 +798,18 @@ Overall, Bulk positions itself as a better choice for sports nutrition through i
798
798
  },
799
799
  ];
800
800
 
801
+ const beforeTime = Date.now();
801
802
  const patches = mapper.suggestionsToPatches('/page', suggestions, 'opp-faq-123', null);
802
-
803
- const expectedTimestamp1 = new Date('2025-01-15T10:00:00.000Z').getTime();
804
- const expectedTimestamp2 = new Date('2025-01-15T12:00:00.000Z').getTime();
803
+ const afterTime = Date.now();
805
804
 
806
805
  expect(patches.length).to.equal(3); // heading + 2 FAQs
807
- // Heading uses the most recent timestamp from suggestions (12:00:00)
808
- expect(patches[0].lastUpdated).to.equal(expectedTimestamp2);
809
- // FAQ patches use their respective suggestion timestamps
810
- expect(patches[1].lastUpdated).to.equal(expectedTimestamp1);
811
- expect(patches[2].lastUpdated).to.equal(expectedTimestamp2);
806
+ // All patches should use Date.now()
807
+ expect(patches[0].lastUpdated).to.be.at.least(beforeTime);
808
+ expect(patches[0].lastUpdated).to.be.at.most(afterTime);
809
+ expect(patches[1].lastUpdated).to.be.at.least(beforeTime);
810
+ expect(patches[1].lastUpdated).to.be.at.most(afterTime);
811
+ expect(patches[2].lastUpdated).to.be.at.least(beforeTime);
812
+ expect(patches[2].lastUpdated).to.be.at.most(afterTime);
812
813
  });
813
814
 
814
815
  it('should use Date.now() when getUpdatedAt returns null', () => {
@@ -1191,20 +1192,23 @@ Overall, Bulk positions itself as a better choice for sports nutrition through i
1191
1192
  },
1192
1193
  };
1193
1194
 
1195
+ const beforeTime = Date.now();
1194
1196
  const patches = mapper.suggestionsToPatches(
1195
1197
  '/page',
1196
1198
  [newSuggestion],
1197
1199
  'opp-faq-123',
1198
1200
  existingConfig,
1199
1201
  );
1202
+ const afterTime = Date.now();
1200
1203
 
1201
1204
  expect(patches).to.be.an('array');
1202
1205
  expect(patches.length).to.equal(2); // Heading + FAQ (always create heading)
1203
1206
 
1204
- // First patch: heading with updated timestamp
1207
+ // First patch: heading with Date.now() timestamp
1205
1208
  expect(patches[0].suggestionId).to.be.undefined;
1206
1209
  expect(patches[0].value.tagName).to.equal('h2');
1207
- expect(patches[0].lastUpdated).to.equal(new Date('2025-01-15T10:00:00.000Z').getTime());
1210
+ expect(patches[0].lastUpdated).to.be.at.least(beforeTime);
1211
+ expect(patches[0].lastUpdated).to.be.at.most(afterTime);
1208
1212
 
1209
1213
  // Second patch: FAQ
1210
1214
  expect(patches[1].suggestionId).to.equal('sugg-new-1');
@@ -289,7 +289,7 @@ describe('ReadabilityMapper', () => {
289
289
  expect(patch.lastUpdated).to.be.a('number');
290
290
  });
291
291
 
292
- it('should create patch with updatedAt timestamp', () => {
292
+ it('should create patch with Date.now() timestamp', () => {
293
293
  const suggestion = {
294
294
  getId: () => 'sugg-456',
295
295
  getUpdatedAt: () => '2025-09-20T06:21:12.584Z',
@@ -304,11 +304,15 @@ describe('ReadabilityMapper', () => {
304
304
  }),
305
305
  };
306
306
 
307
+ const beforeTime = Date.now();
307
308
  const patches = mapper.suggestionsToPatches('/path', [suggestion], 'opp-456');
309
+ const afterTime = Date.now();
310
+
308
311
  expect(patches.length).to.equal(1);
309
312
  const patch = patches[0];
310
313
 
311
- expect(patch.lastUpdated).to.equal(new Date('2025-09-20T06:21:12.584Z').getTime());
314
+ expect(patch.lastUpdated).to.be.at.least(beforeTime);
315
+ expect(patch.lastUpdated).to.be.at.most(afterTime);
312
316
  });
313
317
 
314
318
  it('should use default target when not specified', () => {