@openmrs/esm-config 3.2.1-pre.986 → 3.3.1-pre.1173

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.
@@ -11,13 +11,14 @@ import {
11
11
  configExtensionStore,
12
12
  ConfigInternalStore,
13
13
  configInternalStore,
14
- getExtensionConfigStore,
14
+ getExtensionConfig,
15
+ getExtensionsConfigStore,
15
16
  ImplementerToolsConfigStore,
16
17
  implementerToolsConfigStore,
17
18
  temporaryConfigStore,
18
19
  } from "./state";
19
20
  import { Type } from "../types";
20
- import { getExtensionSlotConfigStore } from "..";
21
+ import { getExtensionSlotsConfigStore } from "..";
21
22
 
22
23
  const mockConfigInternalStore =
23
24
  configInternalStore as MockedStore<ConfigInternalStore>;
@@ -844,7 +845,7 @@ describe("extension slot config", () => {
844
845
  // modules that define schemas.
845
846
  Config.provide({
846
847
  "foo-module": {
847
- extensions: {
848
+ extensionSlots: {
848
849
  fooSlot: {
849
850
  add: ["bar", "baz"],
850
851
  remove: ["zap"],
@@ -854,7 +855,8 @@ describe("extension slot config", () => {
854
855
  },
855
856
  },
856
857
  });
857
- const { config } = getExtensionSlotConfigStore("fooSlot").getState();
858
+ const { config } =
859
+ getExtensionSlotsConfigStore().getState().slots["fooSlot"];
858
860
  expect(config).toStrictEqual({
859
861
  add: ["bar", "baz"],
860
862
  remove: ["zap"],
@@ -874,7 +876,7 @@ describe("extension slot config", () => {
874
876
  });
875
877
  Config.provide({
876
878
  "foo-module": {
877
- extensions: { fooSlot: { remove: ["bar"] } },
879
+ extensionSlots: { fooSlot: { remove: ["bar"] } },
878
880
  },
879
881
  });
880
882
  const config = await Config.getConfig("foo-module");
@@ -888,11 +890,12 @@ describe("extension slot config", () => {
888
890
  });
889
891
  Config.provide({
890
892
  "foo-module": {
891
- extensions: { fooSlot: { remove: ["bar"] } },
893
+ extensionSlots: { fooSlot: { remove: ["bar"] } },
892
894
  },
893
895
  });
894
896
  await Config.getConfig("foo-module");
895
- const { config } = getExtensionSlotConfigStore("fooSlot").getState();
897
+ const { config } =
898
+ getExtensionSlotsConfigStore().getState().slots["fooSlot"];
896
899
  expect(config).toStrictEqual({ remove: ["bar"] });
897
900
  });
898
901
 
@@ -902,14 +905,14 @@ describe("extension slot config", () => {
902
905
  });
903
906
  Config.provide({
904
907
  "foo-module": {
905
- extensions: { fooSlot: { remove: ["bar"] } },
908
+ extensionSlots: { fooSlot: { remove: ["bar"] } },
906
909
  },
907
910
  });
908
911
  const config = implementerToolsConfigStore.getState().config;
909
912
  expect(config).toStrictEqual({
910
913
  "foo-module": {
911
914
  foo: { _default: 0, _value: 0, _source: "default" },
912
- extensions: {
915
+ extensionSlots: {
913
916
  fooSlot: {
914
917
  remove: { _value: ["bar"], _source: "provided" },
915
918
  },
@@ -921,11 +924,13 @@ describe("extension slot config", () => {
921
924
  it("validates that no other keys are present for the slot", async () => {
922
925
  Config.provide({
923
926
  "foo-module": {
924
- extensions: { fooSlot: { quitar: ["bar"] } },
927
+ extensionSlots: { fooSlot: { quitar: ["bar"] } },
925
928
  },
926
929
  });
927
930
  expect(console.error).toHaveBeenCalledWith(
928
- expect.stringMatching(/foo-module.extensions.fooSlot.*invalid.*quitar/)
931
+ expect.stringMatching(
932
+ /foo-module.extensionSlots.fooSlot.*invalid.*quitar/
933
+ )
929
934
  );
930
935
  });
931
936
  });
@@ -933,99 +938,152 @@ describe("extension slot config", () => {
933
938
  describe("extension config", () => {
934
939
  beforeEach(() => {
935
940
  console.error = jest.fn();
941
+ Config.defineConfigSchema("ext-mod", {
942
+ bar: { _default: "barry" },
943
+ baz: { _default: "bazzy" },
944
+ });
936
945
  });
937
946
 
938
947
  afterEach(resetAll);
939
948
 
940
949
  it("returns the module config", async () => {
941
- Config.defineConfigSchema("ext-mod", {
942
- bar: { _default: "barry" },
943
- baz: { _default: "bazzy" },
944
- });
945
- const testConfig = { "ext-mod": { bar: "qux" } };
946
- Config.provide(testConfig);
947
- configExtensionStore.setState({
948
- mountedExtensions: [
949
- {
950
- slotModuleName: "slot-mod",
951
- extensionModuleName: "ext-mod",
952
- slotName: "barSlot",
953
- extensionId: "fooExt",
954
- },
955
- ],
956
- });
957
- const config = getExtensionConfigStore(
958
- "slot-mod",
959
- "barSlot",
960
- "fooExt"
961
- ).getState().config;
962
- expect(config).toStrictEqual({ bar: "qux", baz: "bazzy" });
950
+ const moduleLevelConfig = { "ext-mod": { bar: "qux" } };
951
+ updateConfigExtensionStore();
952
+ Config.provide(moduleLevelConfig);
953
+ const result = getExtensionConfig("barSlot", "fooExt").config;
954
+ expect(result).toStrictEqual({ bar: "qux", baz: "bazzy" });
963
955
  expect(console.error).not.toHaveBeenCalled();
964
956
  });
965
957
 
966
- it("uses the 'configure' config if one is present", async () => {
967
- Config.defineConfigSchema("ext-mod", {
968
- bar: { _default: "barry" },
969
- baz: { _default: "bazzy" },
970
- });
971
- const testConfig = {
958
+ it("uses the 'configure' config if one is present, with module config schema", () => {
959
+ updateConfigExtensionStore("fooExt#id0");
960
+ const configureConfig = {
972
961
  "ext-mod": { bar: "qux" },
973
962
  "slot-mod": {
974
- extensions: {
963
+ extensionSlots: {
975
964
  barSlot: {
976
965
  configure: { "fooExt#id0": { baz: "quiz" } },
977
966
  },
978
967
  },
979
968
  },
980
969
  };
981
- Config.provide(testConfig);
982
- configExtensionStore.setState({
983
- mountedExtensions: [
984
- {
985
- slotModuleName: "slot-mod",
986
- extensionModuleName: "ext-mod",
987
- slotName: "barSlot",
988
- extensionId: "fooExt#id0",
970
+ Config.provide(configureConfig);
971
+ const result = getExtensionConfig("barSlot", "fooExt#id0").config;
972
+ expect(result).toStrictEqual({ bar: "qux", baz: "quiz" });
973
+ expect(console.error).not.toHaveBeenCalled();
974
+ });
975
+
976
+ it("validates the extension configure config, with module config schema", () => {
977
+ updateConfigExtensionStore("fooExt#id1");
978
+ const badConfig = {
979
+ "ext-mod": { bar: "qux" },
980
+ "slot-mod": {
981
+ extensionSlots: {
982
+ barSlot: {
983
+ configure: { "fooExt#id1": { beef: "bad" } },
984
+ },
989
985
  },
990
- ],
986
+ },
987
+ };
988
+ Config.provide(badConfig);
989
+ expect(console.error).toHaveBeenCalledWith(
990
+ expect.stringMatching(/unknown config key 'ext-mod.beef' provided.*/i)
991
+ );
992
+ });
993
+
994
+ it("returns the extension config if an extension config schema is defined", async () => {
995
+ updateConfigExtensionStore("fooExt");
996
+ Config.defineExtensionConfigSchema("fooExt", {
997
+ qux: { _default: "quxxy" },
991
998
  });
992
- const config = getExtensionConfigStore(
993
- "slot-mod",
994
- "barSlot",
995
- "fooExt#id0"
996
- ).getState().config;
997
- expect(config).toStrictEqual({ bar: "qux", baz: "quiz" });
999
+ const extensionAtBaseConfig = { fooExt: { qux: "quxolotl" } };
1000
+ Config.provide(extensionAtBaseConfig);
1001
+ const result = getExtensionConfig("barSlot", "fooExt").config;
1002
+ expect(result).toStrictEqual({ qux: "quxolotl" });
998
1003
  expect(console.error).not.toHaveBeenCalled();
999
1004
  });
1000
1005
 
1001
- it("validates the extension slot config", async () => {
1002
- Config.defineConfigSchema("ext-mod", {
1003
- bar: { _default: "barry" },
1004
- baz: { _default: "bazzy" },
1006
+ it("uses the 'configure' config if one is present, with extension config schema", () => {
1007
+ updateConfigExtensionStore("fooExt#id2");
1008
+ Config.defineExtensionConfigSchema("fooExt", {
1009
+ qux: { _default: "quxxy" },
1005
1010
  });
1006
- const testConfig = {
1007
- "ext-mod": { bar: "qux" },
1011
+ const configureConfig = {
1012
+ fooExt: { qux: "quxolotl" },
1008
1013
  "slot-mod": {
1009
- extensions: {
1014
+ extensionSlots: {
1010
1015
  barSlot: {
1011
- configure: { "fooExt#id0": { beef: "bad" } },
1016
+ configure: { "fooExt#id2": { qux: "quxotic" } },
1012
1017
  },
1013
1018
  },
1014
1019
  },
1015
1020
  };
1016
- Config.provide(testConfig);
1017
- configExtensionStore.setState({
1018
- mountedExtensions: [
1019
- {
1020
- slotModuleName: "slot-mod",
1021
- extensionModuleName: "ext-mod",
1022
- slotName: "barSlot",
1023
- extensionId: "fooExt#id0",
1021
+ Config.provide(configureConfig);
1022
+ const result = getExtensionConfig("barSlot", "fooExt#id2").config;
1023
+ expect(result).toStrictEqual({ qux: "quxotic" });
1024
+ });
1025
+
1026
+ it("validates the extension configure config, with extension config schema", () => {
1027
+ updateConfigExtensionStore("fooExt#id3");
1028
+ Config.defineExtensionConfigSchema("fooExt", {
1029
+ qux: { _default: "quxxy" },
1030
+ });
1031
+ const configureConfig = {
1032
+ "slot-mod": {
1033
+ extensionSlots: {
1034
+ barSlot: {
1035
+ configure: { "fooExt#id3": { no: "bad" } },
1036
+ },
1024
1037
  },
1025
- ],
1038
+ },
1039
+ };
1040
+ Config.provide(configureConfig);
1041
+ expect(console.error).toHaveBeenCalledWith(
1042
+ expect.stringMatching(/unknown config key 'fooExt.no' provided.*/i)
1043
+ );
1044
+ });
1045
+
1046
+ it("does not accept module config parameters for extension if extension config schema is defined", () => {
1047
+ Config.defineExtensionConfigSchema("fooExt", {
1048
+ qux: { _default: "quxxy" },
1026
1049
  });
1050
+ const badConfigNoModuleConfigsForExtension = {
1051
+ fooExt: {
1052
+ bar: "no good",
1053
+ },
1054
+ };
1055
+ Config.provide(badConfigNoModuleConfigsForExtension);
1027
1056
  expect(console.error).toHaveBeenCalledWith(
1028
- expect.stringMatching(/unknown config key 'ext-mod.beef' provided.*/i)
1057
+ expect.stringMatching(/unknown config key 'fooExt.bar' provided.*/i)
1058
+ );
1059
+ });
1060
+
1061
+ it("does not accept extension config parameters for the module", () => {
1062
+ updateConfigExtensionStore("fooExt#id5");
1063
+ Config.defineExtensionConfigSchema("fooExt", {
1064
+ qux: { _default: "quxxy" },
1065
+ });
1066
+ const badConfigNoExtensionConfigsForModule = {
1067
+ "ext-mod": {
1068
+ qux: "also bad",
1069
+ },
1070
+ };
1071
+ Config.provide(badConfigNoExtensionConfigsForModule);
1072
+ expect(console.error).toHaveBeenCalledWith(
1073
+ expect.stringMatching(/unknown config key 'ext-mod.qux' provided.*/i)
1029
1074
  );
1030
1075
  });
1031
1076
  });
1077
+
1078
+ function updateConfigExtensionStore(extensionId = "fooExt") {
1079
+ configExtensionStore.setState({
1080
+ mountedExtensions: [
1081
+ {
1082
+ slotModuleName: "slot-mod",
1083
+ extensionModuleName: "ext-mod",
1084
+ slotName: "barSlot",
1085
+ extensionId,
1086
+ },
1087
+ ],
1088
+ });
1089
+ }
@@ -1,4 +1,5 @@
1
- import { clone, reduce, mergeDeepRight } from "ramda";
1
+ /** @module @category Config */
2
+ import { clone, reduce, mergeDeepRight, equals } from "ramda";
2
3
  import {
3
4
  Config,
4
5
  ConfigObject,
@@ -22,10 +23,11 @@ import {
22
23
  ConfigStore,
23
24
  configExtensionStore,
24
25
  getConfigStore,
25
- getExtensionConfigStore,
26
- getExtensionSlotConfigStore,
26
+ getExtensionsConfigStore,
27
27
  implementerToolsConfigStore,
28
28
  temporaryConfigStore,
29
+ getExtensionSlotsConfigStore,
30
+ ExtensionsConfigStore,
29
31
  } from "./state";
30
32
  import type {} from "@openmrs/esm-globals";
31
33
  import { TemporaryConfigStore } from "..";
@@ -46,7 +48,8 @@ import { TemporaryConfigStore } from "..";
46
48
  * the correct set of output stores are updated.
47
49
  *
48
50
  * All `compute...` functions except `computeExtensionConfigs` are pure
49
- * (or are supposed to be). `computeExtensionConfigs` calls `getGlobalStore`,
51
+ * (or are supposed to be), other than the fact that they all `setState`
52
+ * store values at the end. `computeExtensionConfigs` calls `getGlobalStore`,
50
53
  * which creates stores.
51
54
  */
52
55
  computeModuleConfig(
@@ -87,27 +90,27 @@ computeExtensionConfigs(
87
90
  configExtensionStore.getState(),
88
91
  temporaryConfigStore.getState()
89
92
  );
90
- configInternalStore.subscribe((configState) =>
93
+ configInternalStore.subscribe((configState) => {
91
94
  computeExtensionConfigs(
92
95
  configState,
93
96
  configExtensionStore.getState(),
94
97
  temporaryConfigStore.getState()
95
- )
96
- );
97
- configExtensionStore.subscribe((extensionState) =>
98
+ );
99
+ });
100
+ configExtensionStore.subscribe((extensionState) => {
98
101
  computeExtensionConfigs(
99
102
  configInternalStore.getState(),
100
103
  extensionState,
101
104
  temporaryConfigStore.getState()
102
- )
103
- );
104
- temporaryConfigStore.subscribe((tempConfigState) =>
105
+ );
106
+ });
107
+ temporaryConfigStore.subscribe((tempConfigState) => {
105
108
  computeExtensionConfigs(
106
109
  configInternalStore.getState(),
107
110
  configExtensionStore.getState(),
108
111
  tempConfigState
109
- )
110
- );
112
+ );
113
+ });
111
114
 
112
115
  function computeModuleConfig(
113
116
  state: ConfigInternalStore,
@@ -125,9 +128,17 @@ function computeExtensionSlotConfigs(
125
128
  tempState: TemporaryConfigStore
126
129
  ) {
127
130
  const slotConfigs = getExtensionSlotConfigs(state, tempState);
128
- for (let [slotName, config] of Object.entries(slotConfigs)) {
129
- const slotStore = getExtensionSlotConfigStore(slotName);
130
- slotStore.setState({ loaded: true, config });
131
+ const newSlotStoreEntries = Object.fromEntries(
132
+ Object.entries(slotConfigs).map(([slotName, config]) => [
133
+ slotName,
134
+ { loaded: true, config },
135
+ ])
136
+ );
137
+ const slotStore = getExtensionSlotsConfigStore();
138
+ const oldState = slotStore.getState();
139
+ const newState = { slots: { ...oldState.slots, ...newSlotStoreEntries } };
140
+ if (!equals(oldState, newState)) {
141
+ slotStore.setState(newState);
131
142
  }
132
143
  }
133
144
 
@@ -144,13 +155,9 @@ function computeExtensionConfigs(
144
155
  extensionState: ConfigExtensionStore,
145
156
  tempConfigState: TemporaryConfigStore
146
157
  ) {
158
+ const configs = {};
147
159
  for (let extension of extensionState.mountedExtensions) {
148
- const extensionStore = getExtensionConfigStore(
149
- extension.slotModuleName,
150
- extension.slotName,
151
- extension.extensionId
152
- );
153
- const config = getExtensionConfig(
160
+ const config = computeExtensionConfig(
154
161
  extension.slotModuleName,
155
162
  extension.extensionModuleName,
156
163
  extension.slotName,
@@ -158,8 +165,12 @@ function computeExtensionConfigs(
158
165
  configState,
159
166
  tempConfigState
160
167
  );
161
- extensionStore.setState({ loaded: true, config });
168
+ configs[extension.slotName] = {
169
+ ...configs[extension.slotName],
170
+ [extension.extensionId]: { config, loaded: true },
171
+ };
162
172
  }
173
+ getExtensionsConfigStore().setState({ configs });
163
174
  }
164
175
 
165
176
  /*
@@ -167,6 +178,18 @@ function computeExtensionConfigs(
167
178
  *
168
179
  */
169
180
 
181
+ /**
182
+ * This defines a configuration schema for a module. The schema tells the
183
+ * configuration system how the module can be configured. It specifies
184
+ * what makes configuration valid or invalid.
185
+ *
186
+ * See [Configuration System](http://o3-dev.docs.openmrs.org/#/main/config)
187
+ * for more information about defining a config schema.
188
+ *
189
+ * @param moduleName Name of the module the schema is being defined for. Generally
190
+ * should be the one in which the `defineConfigSchema` call takes place.
191
+ * @param schema The config schema for the module
192
+ */
170
193
  export function defineConfigSchema(moduleName: string, schema: ConfigSchema) {
171
194
  validateConfigSchema(moduleName, schema);
172
195
  const state = configInternalStore.getState();
@@ -175,6 +198,39 @@ export function defineConfigSchema(moduleName: string, schema: ConfigSchema) {
175
198
  });
176
199
  }
177
200
 
201
+ /**
202
+ * This defines a configuration schema for an extension. When a schema is defined
203
+ * for an extension, that extension will receive the configuration corresponding
204
+ * to that schema, rather than the configuration corresponding to the module
205
+ * in which it is defined.
206
+ *
207
+ * The schema tells the configuration system how the module can be configured.
208
+ * It specifies what makes configuration valid or invalid.
209
+ *
210
+ * See [Configuration System](http://o3-dev.docs.openmrs.org/#/main/config)
211
+ * for more information about defining a config schema.
212
+ *
213
+ * @param extensionName Name of the extension the schema is being defined for.
214
+ * Should match the `name` of one of the `extensions` entries being returned
215
+ * by `setupOpenMRS`.
216
+ * @param schema The config schema for the extension
217
+ */
218
+ export function defineExtensionConfigSchema(
219
+ extensionName: string,
220
+ schema: ConfigSchema
221
+ ) {
222
+ validateConfigSchema(extensionName, schema);
223
+ const state = configInternalStore.getState();
224
+ if (state.schemas[extensionName]) {
225
+ console.warn(
226
+ `Config schema for extension ${extensionName} already exists. If there are multiple extensions with this same name, one will probably crash.`
227
+ );
228
+ }
229
+ configInternalStore.setState({
230
+ schemas: { ...state.schemas, [extensionName]: schema },
231
+ });
232
+ }
233
+
178
234
  export function provide(config: Config, sourceName = "provided") {
179
235
  const state = configInternalStore.getState();
180
236
  configInternalStore.setState({
@@ -216,6 +272,7 @@ export function getConfig(moduleName: string): Promise<Config> {
216
272
  * @param providedConfig an object of config values (without the top-level module name)
217
273
  * @param keyPathContext a dot-deparated string which helps the user figure out where
218
274
  * the provided config came from
275
+ * @internal
219
276
  */
220
277
  export function processConfig(
221
278
  schema: ConfigSchema,
@@ -237,15 +294,16 @@ export function processConfig(
237
294
  * Returns the configuration for an extension. This configuration is specific
238
295
  * to the slot in which it is mounted, and its ID within that slot.
239
296
  *
240
- * The schema for that configuration is the schema for the module in which the
241
- * extension is defined.
297
+ * The schema for that configuration is the extension schema. If no extension
298
+ * schema has been provided, the schema used is the schema of the module in
299
+ * which the extension is defined.
242
300
  *
243
301
  * @param slotModuleName The name of the module which defines the extension slot
244
302
  * @param extensionModuleName The name of the module which defines the extension (and therefore the config schema)
245
303
  * @param slotName The name of the extension slot where the extension is mounted
246
304
  * @param extensionId The ID of the extension in its slot
247
305
  */
248
- function getExtensionConfig(
306
+ function computeExtensionConfig(
249
307
  slotModuleName: string,
250
308
  extensionModuleName: string,
251
309
  slotName: string,
@@ -253,23 +311,28 @@ function getExtensionConfig(
253
311
  configState: ConfigInternalStore,
254
312
  tempConfigState: TemporaryConfigStore
255
313
  ) {
314
+ const extensionName = getExtensionNameFromId(extensionId);
315
+ const extensionConfigSchema = configState.schemas[extensionName];
316
+ const nameOfSchemaSource = extensionConfigSchema
317
+ ? extensionName
318
+ : extensionModuleName;
256
319
  const providedConfigs = getProvidedConfigs(configState, tempConfigState);
257
320
  const slotModuleConfig = mergeConfigsFor(slotModuleName, providedConfigs);
258
321
  const configOverride =
259
- slotModuleConfig?.extensions?.[slotName]?.configure?.[extensionId] ?? {};
260
- const extensionModuleConfig = mergeConfigsFor(
261
- extensionModuleName,
262
- providedConfigs
263
- );
264
- const extensionConfig = mergeConfigs([extensionModuleConfig, configOverride]);
265
- const schema = configState.schemas[extensionModuleName]; // TODO: validate that a schema exists for the module
266
- validateConfig(schema, extensionConfig, extensionModuleName);
322
+ slotModuleConfig?.extensionSlots?.[slotName]?.configure?.[extensionId] ??
323
+ {};
324
+ const extensionConfig = mergeConfigsFor(nameOfSchemaSource, providedConfigs);
325
+ const combinedConfig = mergeConfigs([extensionConfig, configOverride]);
326
+ // TODO: validate that a schema exists for the module
327
+ const schema =
328
+ extensionConfigSchema ?? configState.schemas[extensionModuleName];
329
+ validateConfig(schema, combinedConfig, nameOfSchemaSource);
267
330
  const config = setDefaults(
268
331
  schema,
269
- extensionConfig,
332
+ combinedConfig,
270
333
  configState.devDefaultsAreOn
271
334
  );
272
- delete config.extensions;
335
+ delete config.extensionSlots;
273
336
  return config;
274
337
  }
275
338
 
@@ -324,8 +387,8 @@ function getExtensionSlotConfigs(
324
387
  string,
325
388
  Record<string, ExtensionSlotConfig>
326
389
  > = Object.keys(allConfigs).reduce((obj, key) => {
327
- if (allConfigs[key]?.extensions) {
328
- obj[key] = allConfigs[key]?.extensions;
390
+ if (allConfigs[key]?.extensionSlots) {
391
+ obj[key] = allConfigs[key]?.extensionSlots;
329
392
  }
330
393
  return obj;
331
394
  }, {});
@@ -354,7 +417,7 @@ function validateExtensionSlotConfig(
354
417
  moduleName: string,
355
418
  slotName: string
356
419
  ): void {
357
- const errorPrefix = `Extension slot config '${moduleName}.extensions.${slotName}`;
420
+ const errorPrefix = `Extension slot config '${moduleName}.extensionSlots.${slotName}`;
358
421
  const invalidKeys = Object.keys(config).filter(
359
422
  (k) => !["add", "remove", "order", "configure"].includes(k)
360
423
  );
@@ -510,7 +573,7 @@ function getConfigForModule(
510
573
  );
511
574
  validateConfig(schema, inputConfig, moduleName);
512
575
  const config = setDefaults(schema, inputConfig, configState.devDefaultsAreOn);
513
- delete config.extensions;
576
+ delete config.extensionSlots;
514
577
  return config;
515
578
  }
516
579
 
@@ -544,7 +607,7 @@ const validateConfig = (
544
607
  const schemaPart = schema[key] as ConfigSchema;
545
608
 
546
609
  if (!schema.hasOwnProperty(key)) {
547
- if (key !== "extensions") {
610
+ if (key !== "extensionSlots") {
548
611
  console.error(
549
612
  `Unknown config key '${thisKeyPath}' provided. Ignoring.`
550
613
  );
@@ -618,6 +681,8 @@ function checkType(keyPath: string, _type: Type | undefined, value: any) {
618
681
  Object: isObject,
619
682
  String: isString,
620
683
  UUID: isUuid,
684
+ PersonAttributeTypeUuid: isUuid,
685
+ PatientIdentifierTypeUuid: isUuid,
621
686
  };
622
687
  runValidators(keyPath, [validator[_type]], value);
623
688
  }
@@ -731,3 +796,12 @@ function hasObjectSchema(
731
796
  function isOrdinaryObject(value) {
732
797
  return typeof value === "object" && !Array.isArray(value) && value !== null;
733
798
  }
799
+
800
+ /**
801
+ * Copied over from esm-extensions. It rightly belongs to that module, but esm-config
802
+ * cannot depend on esm-extensions.
803
+ */
804
+ function getExtensionNameFromId(extensionId: string) {
805
+ const [extensionName] = extensionId.split("#");
806
+ return extensionName;
807
+ }