@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.
- package/.turbo/turbo-build.log +10 -0
- package/dist/openmrs-esm-module-config.js +1 -1
- package/dist/openmrs-esm-module-config.js.map +1 -1
- package/jest.config.js +3 -0
- package/package.json +4 -4
- package/src/module-config/module-config.test.ts +131 -73
- package/src/module-config/module-config.ts +115 -41
- package/src/module-config/state.ts +59 -22
- package/src/navigation/interpolate-string.ts +2 -2
- package/src/navigation/navigate.ts +1 -1
- package/src/public.ts +12 -0
- package/src/types.ts +1 -0
- package/src/validators/type-validators.ts +1 -1
- package/src/validators/validator.ts +1 -0
- package/src/validators/validators.ts +1 -0
- package/webpack.config.js +1 -1
- package/.babelrc +0 -8
|
@@ -11,13 +11,14 @@ import {
|
|
|
11
11
|
configExtensionStore,
|
|
12
12
|
ConfigInternalStore,
|
|
13
13
|
configInternalStore,
|
|
14
|
-
|
|
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 {
|
|
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
|
-
|
|
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 } =
|
|
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
|
-
|
|
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
|
-
|
|
893
|
+
extensionSlots: { fooSlot: { remove: ["bar"] } },
|
|
892
894
|
},
|
|
893
895
|
});
|
|
894
896
|
await Config.getConfig("foo-module");
|
|
895
|
-
const { config } =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
927
|
+
extensionSlots: { fooSlot: { quitar: ["bar"] } },
|
|
925
928
|
},
|
|
926
929
|
});
|
|
927
930
|
expect(console.error).toHaveBeenCalledWith(
|
|
928
|
-
expect.stringMatching(
|
|
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
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
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",
|
|
967
|
-
|
|
968
|
-
|
|
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
|
-
|
|
963
|
+
extensionSlots: {
|
|
975
964
|
barSlot: {
|
|
976
965
|
configure: { "fooExt#id0": { baz: "quiz" } },
|
|
977
966
|
},
|
|
978
967
|
},
|
|
979
968
|
},
|
|
980
969
|
};
|
|
981
|
-
Config.provide(
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
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
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
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("
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
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
|
|
1007
|
-
|
|
1011
|
+
const configureConfig = {
|
|
1012
|
+
fooExt: { qux: "quxolotl" },
|
|
1008
1013
|
"slot-mod": {
|
|
1009
|
-
|
|
1014
|
+
extensionSlots: {
|
|
1010
1015
|
barSlot: {
|
|
1011
|
-
configure: { "fooExt#
|
|
1016
|
+
configure: { "fooExt#id2": { qux: "quxotic" } },
|
|
1012
1017
|
},
|
|
1013
1018
|
},
|
|
1014
1019
|
},
|
|
1015
1020
|
};
|
|
1016
|
-
Config.provide(
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
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 '
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
241
|
-
*
|
|
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
|
|
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?.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
validateConfig(schema,
|
|
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
|
-
|
|
332
|
+
combinedConfig,
|
|
270
333
|
configState.devDefaultsAreOn
|
|
271
334
|
);
|
|
272
|
-
delete config.
|
|
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]?.
|
|
328
|
-
obj[key] = allConfigs[key]?.
|
|
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}.
|
|
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.
|
|
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 !== "
|
|
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
|
+
}
|