@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 +7 -0
- package/package.json +1 -1
- package/src/config-store.js +97 -38
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
package/src/config-store.js
CHANGED
|
@@ -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/${
|
|
178
|
-
: `/orgs/${
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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,
|
|
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,
|
|
421
|
+
await this.purge(ctx, oldConfig, null);
|
|
363
422
|
}
|
|
364
423
|
|
|
365
424
|
// eslint-disable-next-line class-methods-use-this,no-unused-vars
|