@adobe/helix-config-storage 1.2.3 → 1.3.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,10 @@
1
+ # [1.3.0](https://github.com/adobe/helix-config-storage/compare/v1.2.3...v1.3.0) (2024-08-26)
2
+
3
+
4
+ ### Features
5
+
6
+ * include profile aggregate in purge call ([02a3580](https://github.com/adobe/helix-config-storage/commit/02a3580bbae7f4d2f646f8de4e92a9efc0bedd60))
7
+
1
8
  ## [1.2.3](https://github.com/adobe/helix-config-storage/compare/v1.2.2...v1.2.3) (2024-08-24)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-config-storage",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "description": "Helix Config Storage",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -158,6 +158,31 @@ function redact(config, frag) {
158
158
  * General purpose config store.
159
159
  */
160
160
  export class ConfigStore {
161
+ /**
162
+ * @member {string} org the org id
163
+ */
164
+ org;
165
+
166
+ /**
167
+ * @member {string} type store type (org, sites, profiles, secrets, users)
168
+ */
169
+ type;
170
+
171
+ /**
172
+ * @member {string} name config name
173
+ */
174
+ name;
175
+
176
+ /**
177
+ * @member {string} key storage key
178
+ */
179
+ key;
180
+
181
+ /**
182
+ * @member {object} profiles the cached profiles config
183
+ */
184
+ #profiles = {};
185
+
161
186
  /**
162
187
  * @param {string} org the org id
163
188
  * @param {string} type store type (org, sites, profiles, secrets, users)
@@ -174,8 +199,54 @@ export class ConfigStore {
174
199
  this.type = type;
175
200
  this.name = name;
176
201
  this.key = type === 'org'
177
- ? `/orgs/${this.org}/${this.name || 'config'}.json`
178
- : `/orgs/${this.org}/${this.type}/${this.name}.json`;
202
+ ? `/orgs/${org}/${name || 'config'}.json`
203
+ : `/orgs/${org}/${type}/${name}.json`;
204
+ }
205
+
206
+ async #list(ctx) {
207
+ const storage = HelixStorage.fromContext(ctx).configBus();
208
+ const key = `orgs/${this.org}/${this.type}/`;
209
+ const list = await storage.list(key);
210
+ return {
211
+ [this.type]: list.map((entry) => {
212
+ const siteKey = entry.key;
213
+ if (siteKey.endsWith('.json')) {
214
+ const name = siteKey.split('/').pop();
215
+ return {
216
+ path: `/config/${this.org}/${this.type}/${name}`,
217
+ name: name.substring(0, name.length - 5),
218
+ };
219
+ }
220
+ return null;
221
+ }).filter((entry) => !!entry),
222
+ };
223
+ }
224
+
225
+ async #getProfile(ctx, data) {
226
+ const profileName = data.extends?.profile || 'default';
227
+ let profile = this.#profiles[profileName];
228
+ if (profile !== undefined) {
229
+ return profile;
230
+ }
231
+ const storage = HelixStorage.fromContext(ctx).configBus();
232
+ const profileJson = await storage.get(`/orgs/${this.org}/profiles/${profileName}.json`);
233
+ profile = profileJson ? JSON.parse(profileJson) : null;
234
+ this.#profiles[profileName] = profile;
235
+ return profile;
236
+ }
237
+
238
+ /**
239
+ * Returns the aggregated config. For site configs, this includes the profile config.
240
+ * @param {AdminContext} ctx
241
+ * @param {HelixSiteConfig|HelixProfileConfig} data the data of the config to be updated
242
+ * @returns {Promise<HelixSiteConfig|HelixProfileConfig>}
243
+ */
244
+ async getAggregatedConfig(ctx, data) {
245
+ if (this.type !== 'sites' || !data) {
246
+ return data;
247
+ }
248
+ const profile = await this.#getProfile(ctx, data);
249
+ return getMergedConfig(data, profile);
179
250
  }
180
251
 
181
252
  /**
@@ -185,16 +256,7 @@ export class ConfigStore {
185
256
  * @returns {Promise<boolean|undefined>}
186
257
  */
187
258
  async validate(ctx, data) {
188
- let config = data;
189
- if (this.type === 'sites') {
190
- // validate the config after merging with the profile
191
- const storage = HelixStorage.fromContext(ctx).configBus();
192
- const profileName = data.extends?.profile || 'default';
193
- const profileJson = await storage.get(`/orgs/${this.org}/profiles/${profileName}.json`);
194
- const profile = profileJson ? JSON.parse(profileJson) : null;
195
- config = getMergedConfig(config, profile);
196
- }
197
- return validateSchema(config, this.type);
259
+ return validateSchema(data, this.type);
198
260
  }
199
261
 
200
262
  async create(ctx, data, relPath = '') {
@@ -215,28 +277,10 @@ export class ConfigStore {
215
277
  updateContentSource(ctx, data.content);
216
278
  updateCodeSource(ctx, data.code);
217
279
  }
218
- await this.validate(ctx, data);
280
+ const config = await this.getAggregatedConfig(ctx, data);
281
+ await this.validate(ctx, config);
219
282
  await storage.put(this.key, JSON.stringify(data), 'application/json');
220
- await this.purge(ctx, null, data);
221
- }
222
-
223
- async #list(ctx) {
224
- const storage = HelixStorage.fromContext(ctx).configBus();
225
- const key = `orgs/${this.org}/${this.type}/`;
226
- const list = await storage.list(key);
227
- return {
228
- [this.type]: list.map((entry) => {
229
- const siteKey = entry.key;
230
- if (siteKey.endsWith('.json')) {
231
- const name = siteKey.split('/').pop();
232
- return {
233
- path: `/config/${this.org}/${this.type}/${name}`,
234
- name: name.substring(0, name.length - 5),
235
- };
236
- }
237
- return null;
238
- }).filter((entry) => entry),
239
- };
283
+ await this.purge(ctx, null, config);
240
284
  }
241
285
 
242
286
  async read(ctx, relPath = '') {
@@ -337,9 +381,17 @@ export class ConfigStore {
337
381
  updateContentSource(ctx, config.content);
338
382
  updateCodeSource(ctx, config.code);
339
383
  }
340
- await this.validate(ctx, config);
384
+ let oldConfig = buf ? JSON.parse(buf) : null;
385
+ let newConfig = config;
386
+ if (this.type === 'sites') {
387
+ // apply profile
388
+ oldConfig = await this.getAggregatedConfig(ctx, oldConfig);
389
+ newConfig = await this.getAggregatedConfig(ctx, newConfig);
390
+ }
391
+
392
+ await this.validate(ctx, newConfig);
341
393
  await storage.put(this.key, JSON.stringify(config), 'application/json');
342
- await this.purge(ctx, buf ? JSON.parse(buf) : null, config);
394
+ await this.purge(ctx, oldConfig, newConfig);
343
395
  return ret ?? redact(data, frag);
344
396
  }
345
397
 
@@ -350,16 +402,23 @@ export class ConfigStore {
350
402
  throw new StatusCodeError(404, 'config not found.');
351
403
  }
352
404
  const frag = getFragmentInfo(this.type, relPath);
405
+ let oldConfig = JSON.parse(buf);
406
+ if (this.type === 'sites') {
407
+ // apply profile
408
+ oldConfig = await this.getAggregatedConfig(ctx, oldConfig);
409
+ }
410
+
353
411
  if (frag) {
354
412
  const data = deepPut(JSON.parse(buf), relPath, null);
355
- await this.validate(ctx, data);
413
+ const newConfig = await this.getAggregatedConfig(ctx, data);
414
+ await this.validate(ctx, newConfig);
356
415
  await storage.put(this.key, JSON.stringify(data), 'application/json');
357
- await this.purge(ctx, JSON.parse(buf), data);
416
+ await this.purge(ctx, oldConfig, newConfig);
358
417
  return;
359
418
  }
360
419
 
361
420
  await storage.remove(this.key);
362
- await this.purge(ctx, JSON.parse(buf), null);
421
+ await this.purge(ctx, oldConfig, null);
363
422
  }
364
423
 
365
424
  // eslint-disable-next-line class-methods-use-this,no-unused-vars