@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.
@@ -1,3 +1,3 @@
1
- [0] Successfully compiled: 8 files with swc (144.02ms)
1
+ [0] Successfully compiled: 8 files with swc (272.53ms)
2
2
  [0] swc --strip-leading-paths src -d dist exited with code 0
3
3
  [1] tsc --project tsconfig.build.json exited with code 0
@@ -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;AA+KxF;;;;;;;;;;;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;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAgBjF;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,CAwCxD;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAKvG;AA2eD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,QAUjD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,SAIhC"}
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(recomputeAllConfigs));
56
- configSubscriptions.push(temporaryConfigStore.subscribe(recomputeAllConfigs));
57
- configSubscriptions.push(configExtensionStore.subscribe(recomputeAllConfigs));
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.moduleLoaded[moduleName]) {
72
- const config = getConfigForModule(moduleName, state, tempState);
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: false,
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 newSlotStoreEntries = Object.fromEntries(Object.entries(slotConfigs).map(([slotName, config])=>[
92
- slotName,
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
- if (!isEqual(oldState.slots, newState.slots)) {
107
- slotStore.setState(newState);
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
- if (!configs[extension.slotName]) {
128
- configs[extension.slotName] = {};
129
- }
130
- configs[extension.slotName][extension.extensionId] = {
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
- const extensionsConfigStore = getExtensionsConfigStore();
136
- const oldState = extensionsConfigStore.getState();
137
- const newState = {
138
- configs
139
- };
140
- // Use deep equality to only update if configs actually changed
141
- if (!isEqual(oldState.configs, newState.configs)) {
142
- extensionsConfigStore.setState(newState);
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 new Promise((resolve)=>{
286
- const store = getConfigStore(moduleName);
287
- function update(state) {
288
- if (state.loaded && state.config) {
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
- new Promise((resolve)=>{
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(new Promise((resolve)=>{
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
- * Helper functions
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
- let result = getSchemaWithValuesAndSources(cloneDeep(configState.schemas));
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
- for (let [config, source] of configsAndSources){
412
- result = mergeConfigs([
413
- result,
414
- createValuesAndSourcesTree(config, source)
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
- /** @internal */
112
- export declare function getExtensionConfig(slotName: string, extensionId: string): StoreApi<Omit<ConfigStore, 'translationOverridesLoaded'>>;
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
- /** @internal */
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":"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"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/module-config/state.ts"],"names":[],"mappings":"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"}