@openmrs/esm-config 10.0.1-pre.5635 → 10.0.1-pre.5649

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 (224.69ms)
1
+ [0] Successfully compiled: 8 files with swc (213.01ms)
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+SxF;;;;;;;;;;;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;AA6nBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,QAUjD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,SAUhC"}
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;AA4VxF;;;;;;;;;;;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;AA6nBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,QAUjD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,SAYhC"}
@@ -43,14 +43,33 @@ const configSubscriptions = [];
43
43
  const configState = configInternalStore.getState();
44
44
  const tempConfigState = temporaryConfigStore.getState();
45
45
  const extensionState = configExtensionStore.getState();
46
+ // Mounting an extension changes only `configExtensionStore`, which only the extension configs
47
+ // depend on, so the rest of the last derivation is reused.
48
+ const configInputsUnchanged = lastDerivation?.configState === configState && lastDerivation.tempConfigState === tempConfigState;
49
+ if (configInputsUnchanged) {
50
+ computeExtensionConfigs(configState, extensionState, tempConfigState, lastDerivation.slotConfigs, true);
51
+ return;
52
+ }
46
53
  computeModuleConfig(configState, tempConfigState);
47
54
  const slotConfigs = getExtensionSlotConfigs(configState, tempConfigState);
48
55
  computeExtensionSlotConfigs(slotConfigs);
49
- computeExtensionConfigs(configState, extensionState, tempConfigState, slotConfigs);
56
+ computeExtensionConfigs(configState, extensionState, tempConfigState, slotConfigs, false);
57
+ // Recorded only after the outputs above are written, so a failure forces a full derivation next time.
58
+ lastDerivation = {
59
+ configState,
60
+ tempConfigState,
61
+ slotConfigs
62
+ };
50
63
  // Last, because a failure here must not skip the outputs above. Only kept current while the
51
64
  // implementer tools are watching it; otherwise derived on read.
52
65
  invalidateImplementerToolsConfig();
53
66
  }
67
+ /** The inputs the outputs were last fully derived from, compared by identity. */ let lastDerivation = null;
68
+ /**
69
+ * The mounted extension each extension config entry was derived for. An extension ID can be
70
+ * mounted under a different module while its slot and ID stay the same, so an entry is only
71
+ * current if its modules match too.
72
+ */ let entryOrigins = new WeakMap();
54
73
  function setupConfigSubscriptions() {
55
74
  // Registered here rather than at module load so that `resetConfigSystem` restores it.
56
75
  setImplementerToolsConfigRecomputer(()=>computeImplementerToolsConfig(configInternalStore.getState(), temporaryConfigStore.getState()));
@@ -149,7 +168,7 @@ function computeImplementerToolsConfig(state, tempConfigState) {
149
168
  implementerToolsConfigStore.setState(newState);
150
169
  }
151
170
  }
152
- function computeExtensionConfigs(configState, extensionState, tempConfigState, slotConfigs) {
171
+ function computeExtensionConfigs(configState, extensionState, tempConfigState, slotConfigs, configInputsUnchanged) {
153
172
  const extensionsConfigStore = getExtensionsConfigStore();
154
173
  const oldConfigs = extensionsConfigStore.getState().configs;
155
174
  const configs = {};
@@ -158,14 +177,22 @@ function computeExtensionConfigs(configState, extensionState, tempConfigState, s
158
177
  // We assume that the module schema has already been defined, since the extension
159
178
  // it contains is mounted.
160
179
  for (let extension of extensionState.mountedExtensions){
161
- const config = computeExtensionConfig(extension.slotModuleName, extension.extensionModuleName, extension.slotName, extension.extensionId, configState, tempConfigState, slotConfigs[extension.slotName]);
162
180
  const previous = oldConfigs[extension.slotName]?.[extension.extensionId];
163
- // Entries keep their identity when the configuration behind them hasn't changed, so that
164
- // mounting one extension doesn't hand every other mounted extension a new config object.
165
- const entry = previous?.loaded && isEqual(previous.config, config) ? previous : {
166
- config,
167
- loaded: true
168
- };
181
+ let entry;
182
+ const origin = previous && entryOrigins.get(previous);
183
+ if (configInputsUnchanged && previous?.loaded && origin?.slotModuleName === extension.slotModuleName && origin.extensionModuleName === extension.extensionModuleName) {
184
+ // Derived from these same inputs by the previous run, so it is still current.
185
+ entry = previous;
186
+ } else {
187
+ const config = computeExtensionConfig(extension.slotModuleName, extension.extensionModuleName, extension.slotName, extension.extensionId, configState, tempConfigState, slotConfigs[extension.slotName]);
188
+ // Entries keep their identity when the configuration behind them hasn't changed, so that
189
+ // mounting one extension doesn't hand every other mounted extension a new config object.
190
+ entry = previous?.loaded && isEqual(previous.config, config) ? previous : {
191
+ config,
192
+ loaded: true
193
+ };
194
+ }
195
+ entryOrigins.set(entry, extension);
169
196
  if (entry !== previous) {
170
197
  changed = true;
171
198
  }
@@ -937,6 +964,8 @@ function logError(keyPath, message) {
937
964
  extensionConfigCache.clear();
938
965
  schemaValuesAndSourcesCache.clear();
939
966
  implementerToolsModuleCache.clear();
967
+ lastDerivation = null;
968
+ entryOrigins = new WeakMap();
940
969
  setupConfigSubscriptions();
941
970
  }
942
971
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-config",
3
- "version": "10.0.1-pre.5635",
3
+ "version": "10.0.1-pre.5649",
4
4
  "license": "MPL-2.0",
5
5
  "description": "A configuration library for the OpenMRS Single-Spa framework.",
6
6
  "type": "module",
@@ -57,15 +57,15 @@
57
57
  "lodash-es": "^4.17.21"
58
58
  },
59
59
  "peerDependencies": {
60
- "@openmrs/esm-globals": "^10.0.1-pre.5635",
61
- "@openmrs/esm-state": "^10.0.1-pre.5635",
62
- "@openmrs/esm-utils": "^10.0.1-pre.5635",
60
+ "@openmrs/esm-globals": "^10.0.1-pre.5649",
61
+ "@openmrs/esm-state": "^10.0.1-pre.5649",
62
+ "@openmrs/esm-utils": "^10.0.1-pre.5649",
63
63
  "single-spa": "6.x"
64
64
  },
65
65
  "devDependencies": {
66
- "@openmrs/esm-globals": "10.0.1-pre.5635",
67
- "@openmrs/esm-state": "10.0.1-pre.5635",
68
- "@openmrs/esm-utils": "10.0.1-pre.5635",
66
+ "@openmrs/esm-globals": "10.0.1-pre.5649",
67
+ "@openmrs/esm-state": "10.0.1-pre.5649",
68
+ "@openmrs/esm-utils": "10.0.1-pre.5649",
69
69
  "@swc/cli": "0.8.1",
70
70
  "@swc/core": "1.15.21",
71
71
  "@types/lodash-es": "^4.17.12",
@@ -1304,6 +1304,54 @@ describe('extension config', () => {
1304
1304
  expect(derivations).toBe(3);
1305
1305
  });
1306
1306
 
1307
+ it('keeps every mounted extension current across config changes interleaved with mounts', () => {
1308
+ const mount = (extensionIds: Array<string>) =>
1309
+ configExtensionStore.setState({
1310
+ mountedExtensions: extensionIds.map((extensionId) => ({
1311
+ slotModuleName: 'slot-mod',
1312
+ extensionModuleName: 'ext-mod',
1313
+ slotName: 'barSlot',
1314
+ extensionId,
1315
+ })),
1316
+ });
1317
+ const bar = (extensionId: string) => getExtensionConfig('barSlot', extensionId).getState().config.bar;
1318
+
1319
+ mount(['fooExt#a']);
1320
+ expect(bar('fooExt#a')).toBe('barry');
1321
+
1322
+ temporaryConfigStore.setState({ config: { 'ext-mod': { bar: 'temporary' } } });
1323
+ expect(bar('fooExt#a')).toBe('temporary');
1324
+
1325
+ // Mounting with the config unchanged must neither revert the extension already mounted nor
1326
+ // hand the new one a config derived from anything but the current inputs.
1327
+ mount(['fooExt#a', 'fooExt#b']);
1328
+ expect(bar('fooExt#a')).toBe('temporary');
1329
+ expect(bar('fooExt#b')).toBe('temporary');
1330
+
1331
+ temporaryConfigStore.setState({ config: {} });
1332
+ expect(bar('fooExt#a')).toBe('barry');
1333
+ expect(bar('fooExt#b')).toBe('barry');
1334
+ });
1335
+
1336
+ it('re-derives the config of an extension mounted again under a different module', () => {
1337
+ Config.defineConfigSchema('other-mod', { bar: { _default: 'other' } });
1338
+ Config.registerModuleLoad('other-mod');
1339
+
1340
+ const mount = (extensionModuleName: string) =>
1341
+ configExtensionStore.setState({
1342
+ mountedExtensions: [
1343
+ { slotModuleName: 'slot-mod', extensionModuleName, slotName: 'barSlot', extensionId: 'fooExt' },
1344
+ ],
1345
+ });
1346
+ const bar = () => getExtensionConfig('barSlot', 'fooExt').getState().config.bar;
1347
+
1348
+ mount('ext-mod');
1349
+ expect(bar()).toBe('barry');
1350
+
1351
+ mount('other-mod');
1352
+ expect(bar()).toBe('other');
1353
+ });
1354
+
1307
1355
  it('returns the module config', async () => {
1308
1356
  const moduleLevelConfig = { 'ext-mod': { bar: 'qux' } };
1309
1357
  updateConfigExtensionStore();
@@ -6,6 +6,7 @@ import { isArray, isBoolean, isUuid, isNumber, isObject, isString } from '../val
6
6
  import { validator } from '../validators/validator';
7
7
  import {
8
8
  type ConfigExtensionStore,
9
+ type ConfigExtensionStoreElement,
9
10
  type ConfigInternalStore,
10
11
  type ConfigStore,
11
12
  type ExtensionSlotsConfigStore,
@@ -74,17 +75,44 @@ function recomputeAllConfigs() {
74
75
  const tempConfigState = temporaryConfigStore.getState();
75
76
  const extensionState = configExtensionStore.getState();
76
77
 
78
+ // Mounting an extension changes only `configExtensionStore`, which only the extension configs
79
+ // depend on, so the rest of the last derivation is reused.
80
+ const configInputsUnchanged =
81
+ lastDerivation?.configState === configState && lastDerivation.tempConfigState === tempConfigState;
82
+
83
+ if (configInputsUnchanged) {
84
+ computeExtensionConfigs(configState, extensionState, tempConfigState, lastDerivation!.slotConfigs, true);
85
+ return;
86
+ }
87
+
77
88
  computeModuleConfig(configState, tempConfigState);
78
89
 
79
90
  const slotConfigs = getExtensionSlotConfigs(configState, tempConfigState);
80
91
  computeExtensionSlotConfigs(slotConfigs);
81
- computeExtensionConfigs(configState, extensionState, tempConfigState, slotConfigs);
92
+ computeExtensionConfigs(configState, extensionState, tempConfigState, slotConfigs, false);
93
+
94
+ // Recorded only after the outputs above are written, so a failure forces a full derivation next time.
95
+ lastDerivation = { configState, tempConfigState, slotConfigs };
82
96
 
83
97
  // Last, because a failure here must not skip the outputs above. Only kept current while the
84
98
  // implementer tools are watching it; otherwise derived on read.
85
99
  invalidateImplementerToolsConfig();
86
100
  }
87
101
 
102
+ /** The inputs the outputs were last fully derived from, compared by identity. */
103
+ let lastDerivation: {
104
+ configState: ConfigInternalStore;
105
+ tempConfigState: TemporaryConfigStore;
106
+ slotConfigs: Record<string, ExtensionSlotConfig>;
107
+ } | null = null;
108
+
109
+ /**
110
+ * The mounted extension each extension config entry was derived for. An extension ID can be
111
+ * mounted under a different module while its slot and ID stay the same, so an entry is only
112
+ * current if its modules match too.
113
+ */
114
+ let entryOrigins = new WeakMap<object, ConfigExtensionStoreElement>();
115
+
88
116
  function setupConfigSubscriptions() {
89
117
  // Registered here rather than at module load so that `resetConfigSystem` restores it.
90
118
  setImplementerToolsConfigRecomputer(() =>
@@ -233,6 +261,7 @@ function computeExtensionConfigs(
233
261
  extensionState: ConfigExtensionStore,
234
262
  tempConfigState: TemporaryConfigStore,
235
263
  slotConfigs: Record<string, ExtensionSlotConfig>,
264
+ configInputsUnchanged: boolean,
236
265
  ) {
237
266
  const extensionsConfigStore = getExtensionsConfigStore();
238
267
  const oldConfigs = extensionsConfigStore.getState().configs;
@@ -243,20 +272,36 @@ function computeExtensionConfigs(
243
272
  // We assume that the module schema has already been defined, since the extension
244
273
  // it contains is mounted.
245
274
  for (let extension of extensionState.mountedExtensions) {
246
- const config = computeExtensionConfig(
247
- extension.slotModuleName,
248
- extension.extensionModuleName,
249
- extension.slotName,
250
- extension.extensionId,
251
- configState,
252
- tempConfigState,
253
- slotConfigs[extension.slotName],
254
- );
255
-
256
275
  const previous = oldConfigs[extension.slotName]?.[extension.extensionId];
257
- // Entries keep their identity when the configuration behind them hasn't changed, so that
258
- // mounting one extension doesn't hand every other mounted extension a new config object.
259
- const entry = previous?.loaded && isEqual(previous.config, config) ? previous : { config, loaded: true };
276
+ let entry: ConfigStore | { config: ConfigObject; loaded: true };
277
+
278
+ const origin = previous && entryOrigins.get(previous);
279
+
280
+ if (
281
+ configInputsUnchanged &&
282
+ previous?.loaded &&
283
+ origin?.slotModuleName === extension.slotModuleName &&
284
+ origin.extensionModuleName === extension.extensionModuleName
285
+ ) {
286
+ // Derived from these same inputs by the previous run, so it is still current.
287
+ entry = previous;
288
+ } else {
289
+ const config = computeExtensionConfig(
290
+ extension.slotModuleName,
291
+ extension.extensionModuleName,
292
+ extension.slotName,
293
+ extension.extensionId,
294
+ configState,
295
+ tempConfigState,
296
+ slotConfigs[extension.slotName],
297
+ );
298
+
299
+ // Entries keep their identity when the configuration behind them hasn't changed, so that
300
+ // mounting one extension doesn't hand every other mounted extension a new config object.
301
+ entry = previous?.loaded && isEqual(previous.config, config) ? previous : { config, loaded: true };
302
+ }
303
+
304
+ entryOrigins.set(entry, extension);
260
305
 
261
306
  if (entry !== previous) {
262
307
  changed = true;
@@ -1198,6 +1243,8 @@ export function resetConfigSystem() {
1198
1243
  extensionConfigCache.clear();
1199
1244
  schemaValuesAndSourcesCache.clear();
1200
1245
  implementerToolsModuleCache.clear();
1246
+ lastDerivation = null;
1247
+ entryOrigins = new WeakMap();
1201
1248
  setupConfigSubscriptions();
1202
1249
  }
1203
1250