@openmrs/esm-config 10.0.1-pre.5263 → 10.0.1-pre.5299
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/.turbo/turbo-build.log +1 -1
- package/dist/module-config/module-config.d.ts.map +1 -1
- package/dist/module-config/module-config.js +205 -96
- package/dist/module-config/state.d.ts +47 -3
- package/dist/module-config/state.d.ts.map +1 -1
- package/dist/module-config/state.js +112 -22
- package/package.json +7 -7
- package/src/module-config/config-derivation-failure.test.ts +58 -0
- package/src/module-config/implementer-tools-config.test.ts +214 -0
- package/src/module-config/module-config.test.ts +118 -0
- package/src/module-config/module-config.ts +329 -79
- package/src/module-config/state.ts +144 -28
package/.turbo/turbo-build.log
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-config.d.ts","sourceRoot":"","sources":["../../src/module-config/module-config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAuB,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"module-config.d.ts","sourceRoot":"","sources":["../../src/module-config/module-config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAuB,MAAM,UAAU,CAAC;AAySxF;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,QAS1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,MAAM,QAKhE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,QAKpD;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,QAK7D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,QAetF;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,SAAa,QAK9D;AAiCD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAMjF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,EACjB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAoBxD;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAKvG;AAomBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,QAUjD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,SAUhC"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Type } from "../types.js";
|
|
3
3
|
import { isArray, isBoolean, isUuid, isNumber, isObject, isString } from "../validators/type-validators.js";
|
|
4
4
|
import { validator } from "../validators/validator.js";
|
|
5
|
-
import { configExtensionStore, configInternalStore, getConfigStore, getExtensionConfig, getExtensionSlotsConfigStore, getExtensionsConfigStore, implementerToolsConfigStore, temporaryConfigStore } from "./state.js";
|
|
5
|
+
import { configExtensionStore, configInternalStore, getConfigStore, getExtensionConfig, getExtensionSlotsConfigStore, getExtensionsConfigStore, clearConfigDerivationError, implementerToolsConfigStore, invalidateImplementerToolsConfig, recordConfigDerivationError, setImplementerToolsConfigRecomputer, temporaryConfigStore } from "./state.js";
|
|
6
6
|
/**
|
|
7
7
|
* Deep merge two objects, with right-side values taking precedence.
|
|
8
8
|
* Arrays are replaced entirely (not merged by index), matching the
|
|
@@ -43,21 +43,33 @@ const configSubscriptions = [];
|
|
|
43
43
|
const tempConfigState = temporaryConfigStore.getState();
|
|
44
44
|
const extensionState = configExtensionStore.getState();
|
|
45
45
|
computeModuleConfig(configState, tempConfigState);
|
|
46
|
-
computeImplementerToolsConfig(configState, tempConfigState);
|
|
47
46
|
computeExtensionSlotConfigs(configState, tempConfigState);
|
|
48
47
|
computeExtensionConfigs(configState, extensionState, tempConfigState);
|
|
48
|
+
// Last, because a failure here must not skip the outputs above. Only kept current while the
|
|
49
|
+
// implementer tools are watching it; otherwise derived on read.
|
|
50
|
+
invalidateImplementerToolsConfig();
|
|
49
51
|
}
|
|
50
52
|
function setupConfigSubscriptions() {
|
|
53
|
+
// Registered here rather than at module load so that `resetConfigSystem` restores it.
|
|
54
|
+
setImplementerToolsConfigRecomputer(()=>computeImplementerToolsConfig(configInternalStore.getState(), temporaryConfigStore.getState()));
|
|
51
55
|
// Initial computation
|
|
52
56
|
recomputeAllConfigs();
|
|
53
57
|
// Subscribe to all input stores with a single handler
|
|
54
58
|
// This ensures we only recompute once even if multiple stores change simultaneously
|
|
55
|
-
configSubscriptions.push(configInternalStore.subscribe(
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
configSubscriptions.push(configInternalStore.subscribe(recomputeAllConfigsSafely), temporaryConfigStore.subscribe(recomputeAllConfigsSafely), configExtensionStore.subscribe(recomputeAllConfigsSafely));
|
|
60
|
+
}
|
|
61
|
+
/** Ensure that we do not throw during a store subscription */ function recomputeAllConfigsSafely() {
|
|
62
|
+
clearConfigDerivationError();
|
|
63
|
+
try {
|
|
64
|
+
recomputeAllConfigs();
|
|
65
|
+
} catch (e) {
|
|
66
|
+
recordConfigDerivationError(e);
|
|
67
|
+
console.error('Failed to recompute the configuration', e);
|
|
68
|
+
}
|
|
58
69
|
}
|
|
59
70
|
// Set up subscriptions at module load time
|
|
60
71
|
setupConfigSubscriptions();
|
|
72
|
+
const moduleConfigCache = new Map();
|
|
61
73
|
function computeModuleConfig(state, tempState) {
|
|
62
74
|
for (let moduleName of Object.keys(state.schemas)){
|
|
63
75
|
// At this point the schema could be either just the implicit schema or the actually
|
|
@@ -67,44 +79,62 @@ function computeModuleConfig(state, tempState) {
|
|
|
67
79
|
// available, which as of this writing blocks the schema definition from occurring
|
|
68
80
|
// for modules loaded based on their extensions.
|
|
69
81
|
const moduleStore = getConfigStore(moduleName);
|
|
82
|
+
const schema = state.schemas[moduleName];
|
|
83
|
+
const moduleLoaded = Boolean(state.moduleLoaded[moduleName]);
|
|
84
|
+
const cached = moduleConfigCache.get(moduleName);
|
|
70
85
|
let newState;
|
|
71
|
-
if (state.
|
|
72
|
-
|
|
73
|
-
newState = {
|
|
74
|
-
translationOverridesLoaded: true,
|
|
75
|
-
loaded: true,
|
|
76
|
-
config
|
|
77
|
-
};
|
|
86
|
+
if (cached && cached.schema === schema && cached.moduleLoaded === moduleLoaded && cached.providedConfigs === state.providedConfigs && cached.temporaryConfig === tempState.config) {
|
|
87
|
+
newState = cached.state;
|
|
78
88
|
} else {
|
|
79
|
-
const config = getConfigForModuleImplicitSchema(moduleName, state, tempState);
|
|
89
|
+
const config = moduleLoaded ? getConfigForModule(moduleName, state, tempState) : getConfigForModuleImplicitSchema(moduleName, state, tempState);
|
|
80
90
|
newState = {
|
|
81
91
|
translationOverridesLoaded: true,
|
|
82
|
-
loaded:
|
|
92
|
+
loaded: moduleLoaded,
|
|
83
93
|
config
|
|
84
94
|
};
|
|
95
|
+
moduleConfigCache.set(moduleName, {
|
|
96
|
+
schema,
|
|
97
|
+
moduleLoaded,
|
|
98
|
+
providedConfigs: state.providedConfigs,
|
|
99
|
+
temporaryConfig: tempState.config,
|
|
100
|
+
state: newState
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// Writing unconditionally would hand every `useConfig` consumer in the application a new
|
|
104
|
+
// object — and a re-render — whenever any module or extension anywhere is loaded.
|
|
105
|
+
const oldState = moduleStore.getState();
|
|
106
|
+
if (oldState.loaded !== newState.loaded || oldState.translationOverridesLoaded !== newState.translationOverridesLoaded || !isEqual(oldState.config, newState.config)) {
|
|
107
|
+
moduleStore.setState(newState);
|
|
85
108
|
}
|
|
86
|
-
moduleStore.setState(newState);
|
|
87
109
|
}
|
|
88
110
|
}
|
|
89
111
|
function computeExtensionSlotConfigs(state, tempState) {
|
|
90
112
|
const slotConfigs = getExtensionSlotConfigs(state, tempState);
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
113
|
+
const slotStore = getExtensionSlotsConfigStore();
|
|
114
|
+
const oldSlots = slotStore.getState().slots;
|
|
115
|
+
const slots = {};
|
|
116
|
+
let changed = false;
|
|
117
|
+
for (const [slotName, config] of Object.entries(slotConfigs)){
|
|
118
|
+
const previous = oldSlots[slotName];
|
|
119
|
+
// Entries keep their identity when the configuration behind them hasn't changed, so that
|
|
120
|
+
// changing one slot's configuration doesn't invalidate every other configured slot.
|
|
121
|
+
if (previous?.loaded && isEqual(previous.config, config)) {
|
|
122
|
+
slots[slotName] = previous;
|
|
123
|
+
} else {
|
|
124
|
+
slots[slotName] = {
|
|
94
125
|
loaded: true,
|
|
95
126
|
config
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const slotStore = getExtensionSlotsConfigStore();
|
|
99
|
-
const oldState = slotStore.getState();
|
|
100
|
-
const newState = {
|
|
101
|
-
slots: {
|
|
102
|
-
...oldState.slots,
|
|
103
|
-
...newSlotStoreEntries
|
|
127
|
+
};
|
|
128
|
+
changed = true;
|
|
104
129
|
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
130
|
+
}
|
|
131
|
+
// Built from scratch rather than merged over the previous state: a slot whose configuration
|
|
132
|
+
// has been removed — clearing the temporary config, say — has to lose its entry, or the old
|
|
133
|
+
// configuration stays in force until the page is reloaded.
|
|
134
|
+
if (changed || Object.keys(oldSlots).length !== Object.keys(slots).length) {
|
|
135
|
+
slotStore.setState({
|
|
136
|
+
slots
|
|
137
|
+
});
|
|
108
138
|
}
|
|
109
139
|
}
|
|
110
140
|
function computeImplementerToolsConfig(state, tempConfigState) {
|
|
@@ -119,27 +149,49 @@ function computeImplementerToolsConfig(state, tempConfigState) {
|
|
|
119
149
|
}
|
|
120
150
|
}
|
|
121
151
|
function computeExtensionConfigs(configState, extensionState, tempConfigState) {
|
|
152
|
+
const extensionsConfigStore = getExtensionsConfigStore();
|
|
153
|
+
const oldConfigs = extensionsConfigStore.getState().configs;
|
|
122
154
|
const configs = {};
|
|
155
|
+
let changed = false;
|
|
156
|
+
let entryCount = 0;
|
|
123
157
|
// We assume that the module schema has already been defined, since the extension
|
|
124
158
|
// it contains is mounted.
|
|
125
159
|
for (let extension of extensionState.mountedExtensions){
|
|
126
160
|
const config = computeExtensionConfig(extension.slotModuleName, extension.extensionModuleName, extension.slotName, extension.extensionId, configState, tempConfigState);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
161
|
+
const previous = oldConfigs[extension.slotName]?.[extension.extensionId];
|
|
162
|
+
// Entries keep their identity when the configuration behind them hasn't changed, so that
|
|
163
|
+
// mounting one extension doesn't hand every other mounted extension a new config object.
|
|
164
|
+
const entry = previous?.loaded && isEqual(previous.config, config) ? previous : {
|
|
131
165
|
config,
|
|
132
166
|
loaded: true
|
|
133
167
|
};
|
|
168
|
+
if (entry !== previous) {
|
|
169
|
+
changed = true;
|
|
170
|
+
}
|
|
171
|
+
if (!configs[extension.slotName]) {
|
|
172
|
+
configs[extension.slotName] = {};
|
|
173
|
+
}
|
|
174
|
+
configs[extension.slotName][extension.extensionId] = entry;
|
|
175
|
+
entryCount++;
|
|
176
|
+
}
|
|
177
|
+
// Slots keep their identity too when none of their entries changed. Subscribers compare these
|
|
178
|
+
// per-slot objects to work out which slots to re-derive, so handing out a fresh one for every
|
|
179
|
+
// slot on every write would mark every slot with a mounted extension dirty.
|
|
180
|
+
for (const [slotName, slotConfigs] of Object.entries(configs)){
|
|
181
|
+
const previousSlot = oldConfigs[slotName];
|
|
182
|
+
if (previousSlot && Object.keys(previousSlot).length === Object.keys(slotConfigs).length && Object.entries(slotConfigs).every(([extensionId, entry])=>previousSlot[extensionId] === entry)) {
|
|
183
|
+
configs[slotName] = previousSlot;
|
|
184
|
+
}
|
|
134
185
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
configs
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
186
|
+
if (!changed) {
|
|
187
|
+
// Nothing was added or altered, but an extension may have unmounted, which only shows up as a
|
|
188
|
+
// missing entry.
|
|
189
|
+
changed = Object.keys(oldConfigs).length !== Object.keys(configs).length || Object.values(oldConfigs).reduce((total, slot)=>total + Object.keys(slot).length, 0) !== entryCount;
|
|
190
|
+
}
|
|
191
|
+
if (changed) {
|
|
192
|
+
extensionsConfigStore.setState({
|
|
193
|
+
configs
|
|
194
|
+
});
|
|
143
195
|
}
|
|
144
196
|
}
|
|
145
197
|
/*
|
|
@@ -273,6 +325,28 @@ function computeExtensionConfigs(configState, extensionState, tempConfigState) {
|
|
|
273
325
|
]
|
|
274
326
|
}));
|
|
275
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* Resolves with `select(state)` as soon as the store's state satisfies `ready`, leaving no
|
|
330
|
+
* subscription behind: nothing subscribes at all when the store is already ready. That matters
|
|
331
|
+
* because `getConfig` runs on every HTTP request, and a subscription that outlives its promise
|
|
332
|
+
* both retains its closure forever and slows down every later write to that store.
|
|
333
|
+
*/ function whenStoreReady(store, ready, select) {
|
|
334
|
+
return new Promise((resolve)=>{
|
|
335
|
+
let unsubscribe;
|
|
336
|
+
let resolved = false;
|
|
337
|
+
function update(state) {
|
|
338
|
+
if (!resolved && ready(state)) {
|
|
339
|
+
resolved = true;
|
|
340
|
+
resolve(select(state));
|
|
341
|
+
unsubscribe?.();
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
update(store.getState());
|
|
345
|
+
if (!resolved) {
|
|
346
|
+
unsubscribe = store.subscribe(update);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
}
|
|
276
350
|
/**
|
|
277
351
|
* A promise-based way to access the config as soon as it is fully loaded.
|
|
278
352
|
* If it is already loaded, resolves the config in its present state.
|
|
@@ -282,23 +356,10 @@ function computeExtensionConfigs(configState, extensionState, tempConfigState) {
|
|
|
282
356
|
*
|
|
283
357
|
* @param moduleName The name of the module for which to look up the config
|
|
284
358
|
*/ export function getConfig(moduleName) {
|
|
285
|
-
return
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const config = lodashOmit(state.config, [
|
|
290
|
-
'Display conditions',
|
|
291
|
-
'Translation overrides'
|
|
292
|
-
]);
|
|
293
|
-
resolve(config);
|
|
294
|
-
if (unsubscribe) {
|
|
295
|
-
unsubscribe();
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
update(store.getState());
|
|
300
|
-
const unsubscribe = store.subscribe(update);
|
|
301
|
-
});
|
|
359
|
+
return whenStoreReady(getConfigStore(moduleName), (state)=>Boolean(state.loaded && state.config), (state)=>lodashOmit(state.config, [
|
|
360
|
+
'Display conditions',
|
|
361
|
+
'Translation overrides'
|
|
362
|
+
]));
|
|
302
363
|
}
|
|
303
364
|
/**
|
|
304
365
|
* Retrieves translation overrides for an extension, optionally scoped to a specific
|
|
@@ -314,36 +375,10 @@ function computeExtensionConfigs(configState, extensionState, tempConfigState) {
|
|
|
314
375
|
* @internal
|
|
315
376
|
*/ export function getTranslationOverrides(moduleName, slotName, extensionId) {
|
|
316
377
|
const promises = [
|
|
317
|
-
|
|
318
|
-
const configStore = getConfigStore(moduleName);
|
|
319
|
-
function update(state) {
|
|
320
|
-
if (state.translationOverridesLoaded && state.config) {
|
|
321
|
-
const translationOverrides = state.config['Translation overrides'] ?? {};
|
|
322
|
-
resolve(translationOverrides);
|
|
323
|
-
if (unsubscribe) {
|
|
324
|
-
unsubscribe();
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
update(configStore.getState());
|
|
329
|
-
const unsubscribe = configStore.subscribe(update);
|
|
330
|
-
})
|
|
378
|
+
whenStoreReady(getConfigStore(moduleName), (state)=>Boolean(state.translationOverridesLoaded && state.config), (state)=>state.config['Translation overrides'] ?? {})
|
|
331
379
|
];
|
|
332
380
|
if (slotName && extensionId) {
|
|
333
|
-
promises.push(
|
|
334
|
-
const configStore = getExtensionConfig(slotName, extensionId);
|
|
335
|
-
function update(state) {
|
|
336
|
-
if (state.loaded && state.config) {
|
|
337
|
-
const translationOverrides = state.config['Translation overrides'] ?? {};
|
|
338
|
-
resolve(translationOverrides);
|
|
339
|
-
if (unsubscribe) {
|
|
340
|
-
unsubscribe();
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
update(configStore.getState());
|
|
345
|
-
const unsubscribe = configStore.subscribe(update);
|
|
346
|
-
}));
|
|
381
|
+
promises.push(whenStoreReady(getExtensionConfig(slotName, extensionId), (state)=>Boolean(state.loaded && state.config), (state)=>state.config['Translation overrides'] ?? {}));
|
|
347
382
|
}
|
|
348
383
|
return Promise.all(promises);
|
|
349
384
|
}
|
|
@@ -362,10 +397,8 @@ function computeExtensionConfigs(configState, extensionState, tempConfigState) {
|
|
|
362
397
|
runAllValidatorsInConfigTree(schema, config, keyPathContext);
|
|
363
398
|
return config;
|
|
364
399
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
*
|
|
368
|
-
*/ /**
|
|
400
|
+
const extensionConfigCache = new Map();
|
|
401
|
+
/**
|
|
369
402
|
* Returns the configuration for an extension. This configuration is specific
|
|
370
403
|
* to the slot in which it is mounted, and its ID within that slot.
|
|
371
404
|
*
|
|
@@ -380,6 +413,17 @@ function computeExtensionConfigs(configState, extensionState, tempConfigState) {
|
|
|
380
413
|
*/ function computeExtensionConfig(slotModuleName, extensionModuleName, slotName, extensionId, configState, tempConfigState) {
|
|
381
414
|
const extensionName = getExtensionNameFromId(extensionId);
|
|
382
415
|
const extensionConfigSchema = configState.schemas[extensionName];
|
|
416
|
+
// JSON string of these elements should be unique-per-extension
|
|
417
|
+
const cacheKey = JSON.stringify([
|
|
418
|
+
slotName,
|
|
419
|
+
extensionId,
|
|
420
|
+
slotModuleName,
|
|
421
|
+
extensionModuleName
|
|
422
|
+
]);
|
|
423
|
+
const cached = extensionConfigCache.get(cacheKey);
|
|
424
|
+
if (cached && cached.extensionSchema === extensionConfigSchema && cached.extensionModuleSchema === configState.schemas[extensionModuleName] && cached.providedConfigs === configState.providedConfigs && cached.temporaryConfig === tempConfigState.config) {
|
|
425
|
+
return cached.config;
|
|
426
|
+
}
|
|
383
427
|
const nameOfSchemaSource = extensionConfigSchema ? extensionName : extensionModuleName;
|
|
384
428
|
const providedConfigs = getProvidedConfigs(configState, tempConfigState);
|
|
385
429
|
const slotModuleConfig = mergeConfigsFor(slotModuleName, providedConfigs);
|
|
@@ -394,10 +438,18 @@ function computeExtensionConfigs(configState, extensionState, tempConfigState) {
|
|
|
394
438
|
const config = setDefaults(schema, combinedConfig);
|
|
395
439
|
runAllValidatorsInConfigTree(schema, config, nameOfSchemaSource);
|
|
396
440
|
delete config.extensionSlots;
|
|
441
|
+
extensionConfigCache.set(cacheKey, {
|
|
442
|
+
extensionSchema: extensionConfigSchema,
|
|
443
|
+
extensionModuleSchema: configState.schemas[extensionModuleName],
|
|
444
|
+
providedConfigs: configState.providedConfigs,
|
|
445
|
+
temporaryConfig: tempConfigState.config,
|
|
446
|
+
config
|
|
447
|
+
});
|
|
397
448
|
return config;
|
|
398
449
|
}
|
|
450
|
+
const implementerToolsModuleCache = new Map();
|
|
399
451
|
function getImplementerToolsConfig(configState, tempConfigState) {
|
|
400
|
-
|
|
452
|
+
const annotatedSchemas = getAllSchemasWithValuesAndSources(configState.schemas);
|
|
401
453
|
const configsAndSources = [
|
|
402
454
|
...configState.providedConfigs.map((c)=>[
|
|
403
455
|
c.config,
|
|
@@ -408,11 +460,62 @@ function getImplementerToolsConfig(configState, tempConfigState) {
|
|
|
408
460
|
'temporary config'
|
|
409
461
|
]
|
|
410
462
|
];
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
463
|
+
// A module can be configured without having declared a schema, so the module list is the union
|
|
464
|
+
// of both rather than just the schemas.
|
|
465
|
+
const moduleNames = new Set(Object.keys(annotatedSchemas));
|
|
466
|
+
for (const [config] of configsAndSources){
|
|
467
|
+
for (const moduleName of Object.keys(config)){
|
|
468
|
+
moduleNames.add(moduleName);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const result = {};
|
|
472
|
+
for (const moduleName of moduleNames){
|
|
473
|
+
const annotatedSchema = annotatedSchemas[moduleName];
|
|
474
|
+
const configSlices = configsAndSources.map(([config])=>config[moduleName]);
|
|
475
|
+
const sources = configsAndSources.map(([, source])=>source);
|
|
476
|
+
const cached = implementerToolsModuleCache.get(moduleName);
|
|
477
|
+
if (cached && cached.annotatedSchema === annotatedSchema && cached.configSlices.length === configSlices.length && cached.configSlices.every((slice, i)=>slice === configSlices[i]) && cached.sources.every((source, i)=>source === sources[i])) {
|
|
478
|
+
result[moduleName] = cached.result;
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
let merged = annotatedSchema ?? {};
|
|
482
|
+
configsAndSources.forEach(([, source], i)=>{
|
|
483
|
+
const slice = configSlices[i];
|
|
484
|
+
if (slice !== undefined) {
|
|
485
|
+
merged = mergeDeepReplace(merged, createValuesAndSourcesTree(slice, source));
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
implementerToolsModuleCache.set(moduleName, {
|
|
489
|
+
annotatedSchema,
|
|
490
|
+
configSlices,
|
|
491
|
+
sources,
|
|
492
|
+
result: merged
|
|
493
|
+
});
|
|
494
|
+
result[moduleName] = merged;
|
|
495
|
+
}
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Caching per module is what stops the walk being quadratic: defining one module's schema
|
|
500
|
+
* recomputes every module.
|
|
501
|
+
*
|
|
502
|
+
* A module with no configuration of its own is published straight from this cache, so nothing may
|
|
503
|
+
* mutate a tree it reads out of the implementer tools config — it is the cache entry.
|
|
504
|
+
*/ const schemaValuesAndSourcesCache = new Map();
|
|
505
|
+
function getAllSchemasWithValuesAndSources(schemas) {
|
|
506
|
+
const result = {};
|
|
507
|
+
for (const [moduleName, schema] of Object.entries(schemas)){
|
|
508
|
+
const cached = schemaValuesAndSourcesCache.get(moduleName);
|
|
509
|
+
if (cached && cached.schema === schema) {
|
|
510
|
+
result[moduleName] = cached.annotated;
|
|
511
|
+
} else {
|
|
512
|
+
const annotated = getSchemaWithValuesAndSources(cloneDeep(schema));
|
|
513
|
+
schemaValuesAndSourcesCache.set(moduleName, {
|
|
514
|
+
schema,
|
|
515
|
+
annotated
|
|
516
|
+
});
|
|
517
|
+
result[moduleName] = annotated;
|
|
518
|
+
}
|
|
416
519
|
}
|
|
417
520
|
return result;
|
|
418
521
|
}
|
|
@@ -809,6 +912,12 @@ function logError(keyPath, message) {
|
|
|
809
912
|
*/ export function resetConfigSystem() {
|
|
810
913
|
configSubscriptions.forEach((unsubscribe)=>unsubscribe());
|
|
811
914
|
configSubscriptions.length = 0;
|
|
915
|
+
// Cached configs are keyed on input identity, so a test that reuses the same schema or config
|
|
916
|
+
// objects across cases would otherwise be served the previous case's result.
|
|
917
|
+
moduleConfigCache.clear();
|
|
918
|
+
extensionConfigCache.clear();
|
|
919
|
+
schemaValuesAndSourcesCache.clear();
|
|
920
|
+
implementerToolsModuleCache.clear();
|
|
812
921
|
setupConfigSubscriptions();
|
|
813
922
|
}
|
|
814
923
|
/**
|
|
@@ -108,8 +108,19 @@ export interface ExtensionsConfigStore {
|
|
|
108
108
|
* @internal
|
|
109
109
|
*/
|
|
110
110
|
export declare function getExtensionsConfigStore(): StoreApi<ExtensionsConfigStore>;
|
|
111
|
-
/**
|
|
112
|
-
|
|
111
|
+
/**
|
|
112
|
+
* The read-only part of a store's API.
|
|
113
|
+
* @internal
|
|
114
|
+
*/
|
|
115
|
+
export type ReadableStore<T> = Pick<StoreApi<T>, 'getInitialState' | 'getState' | 'subscribe'>;
|
|
116
|
+
/**
|
|
117
|
+
* A read-only view of one extension instance's config within a slot. Read-only because these
|
|
118
|
+
* configs are derived from the config system's inputs, so a write here would be overwritten by the
|
|
119
|
+
* next recomputation; provide a config or set a temporary one instead.
|
|
120
|
+
*
|
|
121
|
+
* @internal
|
|
122
|
+
*/
|
|
123
|
+
export declare function getExtensionConfig(slotName: string, extensionId: string): ReadableStore<Omit<ConfigStore, 'translationOverridesLoaded'>>;
|
|
113
124
|
/** @internal */
|
|
114
125
|
export declare function getExtensionConfigFromStore(state: ExtensionsConfigStore, slotName: string, extensionId: string): ConfigStore;
|
|
115
126
|
/** @internal */
|
|
@@ -120,7 +131,40 @@ export declare function getExtensionConfigFromExtensionSlotStore(state: Extensio
|
|
|
120
131
|
*/
|
|
121
132
|
export interface ImplementerToolsConfigStore {
|
|
122
133
|
config: Config;
|
|
134
|
+
/**
|
|
135
|
+
* Set when the last attempt to derive `config` threw, in which case `config` is whatever was
|
|
136
|
+
* last derived successfully — on a first failure, nothing at all. Cleared by a later success.
|
|
137
|
+
*/
|
|
138
|
+
derivationError?: string;
|
|
123
139
|
}
|
|
124
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* Registers how to derive the implementer tools config; the store decides whether to.
|
|
142
|
+
* @internal
|
|
143
|
+
*/
|
|
144
|
+
export declare function setImplementerToolsConfigRecomputer(recompute: () => void): void;
|
|
145
|
+
/**
|
|
146
|
+
* Marks the implementer tools config stale, deriving it again only if something is watching.
|
|
147
|
+
* @internal
|
|
148
|
+
*/
|
|
149
|
+
export declare function invalidateImplementerToolsConfig(): void;
|
|
150
|
+
/**
|
|
151
|
+
* Records that configuration could not be rebuilt so that the user can be informed
|
|
152
|
+
*
|
|
153
|
+
* @internal
|
|
154
|
+
*/
|
|
155
|
+
export declare function recordConfigDerivationError(error: unknown): void;
|
|
156
|
+
/**
|
|
157
|
+
* Cleared at the start of a derivation pass rather than the end of a successful one, so that a
|
|
158
|
+
* failure recorded partway through the pass is not wiped by the pass completing.
|
|
159
|
+
*
|
|
160
|
+
* @internal
|
|
161
|
+
*/
|
|
162
|
+
export declare function clearConfigDerivationError(): void;
|
|
163
|
+
/**
|
|
164
|
+
* Published rather than the store it wraps, so that a lookup by name can't hand out the one that
|
|
165
|
+
* skips the derivation and always reads empty.
|
|
166
|
+
*
|
|
167
|
+
* @internal
|
|
168
|
+
*/
|
|
125
169
|
export declare const implementerToolsConfigStore: StoreApi<ImplementerToolsConfigStore>;
|
|
126
170
|
//# sourceMappingURL=state.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/module-config/state.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/module-config/state.ts"],"names":[],"mappings":"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"}
|