@openmrs/esm-routes 5.3.0

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.
@@ -0,0 +1,8 @@
1
+ @openmrs/esm-routes:build: cache hit, replaying output 5465ab12753b78af
2
+ @openmrs/esm-routes:build: asset openmrs-esm-utils.js 3.13 KiB [emitted] [minimized] (name: main) 1 related asset
3
+ @openmrs/esm-routes:build: runtime modules 670 bytes 3 modules
4
+ @openmrs/esm-routes:build: orphan modules 6.88 KiB [orphan] 2 modules
5
+ @openmrs/esm-routes:build: built modules 6.97 KiB [built]
6
+ @openmrs/esm-routes:build:  ./src/index.ts + 2 modules 6.93 KiB [built] [code generated]
7
+ @openmrs/esm-routes:build:  external "@openmrs/esm-utils" 42 bytes [built] [code generated]
8
+ @openmrs/esm-routes:build: webpack 5.88.0 compiled successfully in 7712 ms
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # openmrs-esm-routes
2
+
3
+ openmrs-esm-routes provides helper functions for working with the O3 routes.json files.
@@ -0,0 +1,2 @@
1
+ System.register(["@openmrs/esm-utils"],(function(e,r){var t={};return{setters:[function(e){t.canAccessStorage=e.canAccessStorage}],execute:function(){e((()=>{"use strict";var e={618:e=>{e.exports=t}},r={};function o(t){var n=r[t];if(void 0!==n)return n.exports;var a=r[t]={exports:{}};return e[t](a,a.exports,o),a.exports}o.d=(e,r)=>{for(var t in r)o.o(r,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},o.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var n={};return(()=>{o.r(n),o.d(n,{addRoutesOverride:()=>a,isOpenmrsAppRoutes:()=>u,isOpenmrsRoutes:()=>l,localStorageRoutesPrefix:()=>e,removeRoutesOverride:()=>i,resetAllRoutesOverrides:()=>s});var e="openmrs-routes:";function r(e,r){(null==r||r>e.length)&&(r=e.length);for(var t=0,o=new Array(r);t<r;t++)o[t]=e[t];return o}var t=(0,o(618).canAccessStorage)();function a(e,r){if(t){if("string"==typeof r){if(r.startsWith("http"))return c(e,r);try{var o=JSON.parse(r);if(u(o))return c(e,o);console.error("The supplied routes for ".concat(e," is not a valid OpenmrsAppRoutes object"),r)}catch(r){console.error("Could not add routes override for ".concat(e,": "),r)}}else{if(n=r,null!=(a=URL)&&"undefined"!=typeof Symbol&&a[Symbol.hasInstance]?a[Symbol.hasInstance](n):n instanceof a)return c(e,r.toString());if(u(r))return c(e,r)}var n,a;console.error("Override for ".concat(e," is not in a valid format. Expected either a Javascript Object, a JSON string of a Javascript object, or a URL"),r)}}function i(r){if(t){var o=e+r;localStorage.removeItem(o)}}function s(){if(t)for(var r=window.localStorage,o=0;o<r.length;o++){var n=r.key(o);(null==n?void 0:n.startsWith(e))&&r.removeItem(n)}}function c(r,t){var o=e+r;localStorage.setItem(o,JSON.stringify(t))}function u(e){if(e&&"object"==typeof e){var r=Object.prototype.hasOwnProperty,t=e;return!!(!r.call(e,"pages")||Boolean(t.pages)&&Array.isArray(t.pages))&&!!(!r.call(e,"extensions")||Boolean(t.extensions)&&Array.isArray(t.extensions))}return!1}function l(e){if(e&&"object"==typeof e){var t=e;return Object.entries(t).every((function(e){var t,o,n=(o=2,function(e){if(Array.isArray(e))return e}(t=e)||function(e,r){var t=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=t){var o,n,a=[],i=!0,s=!1;try{for(t=t.call(e);!(i=(o=t.next()).done)&&(a.push(o.value),!r||a.length!==r);i=!0);}catch(e){s=!0,n=e}finally{try{i||null==t.return||t.return()}finally{if(s)throw n}}return a}}(t,o)||function(e,t){if(e){if("string"==typeof e)return r(e,t);var o=Object.prototype.toString.call(e).slice(8,-1);return"Object"===o&&e.constructor&&(o=e.constructor.name),"Map"===o||"Set"===o?Array.from(o):"Arguments"===o||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(o)?r(e,t):void 0}}(t,o)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()),a=n[0],i=n[1];return"string"==typeof a&&u(i)}))}return!1}})(),n})())}}}));
2
+ //# sourceMappingURL=openmrs-esm-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openmrs-esm-utils.js","mappings":"0LAAAA,EAAOC,QAAUC,C,GCCbC,EAA2B,CAAC,EAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBE,IAAjBD,EACH,OAAOA,EAAaL,QAGrB,IAAID,EAASG,EAAyBE,GAAY,CAGjDJ,QAAS,CAAC,GAOX,OAHAO,EAAoBH,GAAUL,EAAQA,EAAOC,QAASG,GAG/CJ,EAAOC,OACf,CCrBAG,EAAoBK,EAAI,CAACR,EAASS,KACjC,IAAI,IAAIC,KAAOD,EACXN,EAAoBQ,EAAEF,EAAYC,KAASP,EAAoBQ,EAAEX,EAASU,IAC5EE,OAAOC,eAAeb,EAASU,EAAK,CAAEI,YAAY,EAAMC,IAAKN,EAAWC,IAE1E,ECNDP,EAAoBQ,EAAI,CAACK,EAAKC,IAAUL,OAAOM,UAAUC,eAAeC,KAAKJ,EAAKC,GCClFd,EAAoBkB,EAAKrB,IACH,oBAAXsB,QAA0BA,OAAOC,aAC1CX,OAAOC,eAAeb,EAASsB,OAAOC,YAAa,CAAEC,MAAO,WAE7DZ,OAAOC,eAAeb,EAAS,aAAc,CAAEwB,OAAO,GAAO,E,oMCLvD,IAAMC,EAA2B,kBCAD,iB,yFAKvC,IAAMC,GAAYC,E,OAAAA,oBAYX,SAASC,EAAkBC,EAAoBC,GACpD,GAAKJ,EAAL,CAIA,GAAsB,iBAAXI,EAAqB,CAC9B,GAAIA,EAAOC,WAAW,QACpB,OAAOC,EAAyBH,EAAYC,GAE5C,IACE,IAAMG,EAAcC,KAAKC,MAAML,GAC/B,GAAIM,EAAmBH,GACrB,OAAOD,EAAyBH,EAAYI,GAE5CI,QAAQC,MAAM,2BAAsC,OAAXT,EAAW,2CAA0CC,EAElG,CAAE,MAAOS,GACPF,QAAQC,MAAM,qCAAgD,OAAXT,EAAW,MAAKU,EACrE,CAEJ,KAAO,I,EAAIT,E,SAAkBU,M,0FAC3B,OAAOR,EAAyBH,EAAYC,EAAOW,YAC9C,GAAIL,EAAmBN,GAC5B,OAAOE,EAAyBH,EAAYC,EAC9C,C,QAEAO,QAAQC,MACN,gBAA2B,OAAXT,EAAW,kHAC3BC,EAzBF,CA2BF,CASO,SAASY,EAAqBb,GACnC,GAAKH,EAAL,CAIA,IAAMhB,EAAMe,EAA2BI,EACvCc,aAAaC,WAAWlC,EAHxB,CAIF,CAQO,SAASmC,IACd,GAAKnB,EAKL,IADA,IAAMiB,EAAeG,OAAOH,aACnBI,EAAI,EAAGA,EAAIJ,EAAaK,OAAQD,IAAK,CAC5C,IAAMrC,EAAMiC,EAAajC,IAAIqC,IACzBrC,aAAAA,EAAAA,EAAKqB,WAAWN,KAClBkB,EAAaC,WAAWlC,EAE5B,CACF,CAEA,SAASsB,EAAyBH,EAAoBC,GACpD,IAAMpB,EAAMe,EAA2BI,EACvCc,aAAaM,QAAQvC,EAAKwB,KAAKgB,UAAUpB,GAC3C,CAUO,SAASM,EAAmBN,GACjC,GAAIA,GAA4B,iBAAXA,EAAqB,CACxC,IAAMX,EAAiBP,OAAOM,UAAUC,eAGlCc,EAAcH,EAEpB,UAAIX,EAAeC,KAAKU,EAAQ,UACzBqB,QAAQlB,EAAYmB,QAAWC,MAAMC,QAAQrB,EAAYmB,aAK5DjC,EAAeC,KAAKU,EAAQ,eACzBqB,QAAQlB,EAAYsB,aAAgBF,MAAMC,QAAQrB,EAAYsB,YAQvE,CAEA,OAAO,CACT,CAUO,SAASC,EAAgB1B,GAC9B,GAAIA,GAA4B,iBAAXA,EAAqB,CACxC,IAAMG,EAAcH,EAEpB,OAAOlB,OAAO6C,QAAQxB,GAAayB,OAAM,Y,g1BAAEhD,EAAAA,EAAAA,GAAKc,EAAAA,EAAAA,G,MAA0B,iBAARd,GAAoB0B,EAAmBZ,E,GAC3G,CAEA,OAAO,CACT,C","sources":["webpack://@openmrs/esm-routes/external system \"@openmrs/esm-utils\"","webpack://@openmrs/esm-routes/webpack/bootstrap","webpack://@openmrs/esm-routes/webpack/runtime/define property getters","webpack://@openmrs/esm-routes/webpack/runtime/hasOwnProperty shorthand","webpack://@openmrs/esm-routes/webpack/runtime/make namespace object","webpack://@openmrs/esm-routes/./src/constants.ts","webpack://@openmrs/esm-routes/./src/routes.ts"],"sourcesContent":["module.exports = __WEBPACK_EXTERNAL_MODULE__618__;","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export const localStorageRoutesPrefix = 'openmrs-routes:';\n","/** @module @category Routes Utilities */\nimport type { OpenmrsAppRoutes, OpenmrsRoutes } from '@openmrs/esm-globals';\nimport { canAccessStorage } from '@openmrs/esm-utils';\nimport { localStorageRoutesPrefix } from './constants';\n\nconst isEnabled = canAccessStorage();\n\n/**\n * Used to add a route override to local storage. These are read as the routes registry\n * is assembled, so the app must be reloaded for new overrides to take effect.\n *\n * @internal\n * @param moduleName The name of the module the routes are for\n * @param routes Either an {@link OpenmrsAppRoutes} object, a string that represents a JSON\n * version of an {@link OpenmrsAppRoutes} object or a string or URL that resolves to a\n * JSON document that represents an {@link OpenmrsAppRoutes} object\n */\nexport function addRoutesOverride(moduleName: string, routes: OpenmrsAppRoutes | string | URL) {\n if (!isEnabled) {\n return;\n }\n\n if (typeof routes === 'string') {\n if (routes.startsWith('http')) {\n return addRouteOverrideInternal(moduleName, routes);\n } else {\n try {\n const maybeRoutes = JSON.parse(routes);\n if (isOpenmrsAppRoutes(maybeRoutes)) {\n return addRouteOverrideInternal(moduleName, maybeRoutes);\n } else {\n console.error(`The supplied routes for ${moduleName} is not a valid OpenmrsAppRoutes object`, routes);\n }\n } catch (e) {\n console.error(`Could not add routes override for ${moduleName}: `, e);\n }\n }\n } else if (routes instanceof URL) {\n return addRouteOverrideInternal(moduleName, routes.toString());\n } else if (isOpenmrsAppRoutes(routes)) {\n return addRouteOverrideInternal(moduleName, routes);\n }\n\n console.error(\n `Override for ${moduleName} is not in a valid format. Expected either a Javascript Object, a JSON string of a Javascript object, or a URL`,\n routes,\n );\n}\n\n/**\n * Used to remove an existing routes override from local storage. These are read as the routes registry\n * is assembled, so the app must be reloaded for removed override to be removed.\n *\n * @internal\n * @param moduleName The module to remove the overrides for\n */\nexport function removeRoutesOverride(moduleName: string) {\n if (!isEnabled) {\n return;\n }\n\n const key = localStorageRoutesPrefix + moduleName;\n localStorage.removeItem(key);\n}\n\n/**\n * Used to remove all existing routes overrides from local storage. These are read as the routes registry\n * is assembled, so the app must be reloaded for the removed overrides to appear to be removed.\n *\n * @internal\n */\nexport function resetAllRoutesOverrides() {\n if (!isEnabled) {\n return;\n }\n\n const localStorage = window.localStorage;\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key?.startsWith(localStorageRoutesPrefix)) {\n localStorage.removeItem(key);\n }\n }\n}\n\nfunction addRouteOverrideInternal(moduleName: string, routes: OpenmrsAppRoutes | string) {\n const key = localStorageRoutesPrefix + moduleName;\n localStorage.setItem(key, JSON.stringify(routes));\n}\n\n/**\n * Simple type-predicate to ensure that the value can be treated as an OpenmrsAppRoutes\n * object.\n *\n * @internal\n * @param routes the object to check to see if it is an OpenmrsAppRoutes object\n * @returns true if the routes value is an OpenmrsAppRoutes\n */\nexport function isOpenmrsAppRoutes(routes: OpenmrsAppRoutes | unknown): routes is OpenmrsAppRoutes {\n if (routes && typeof routes === 'object') {\n const hasOwnProperty = Object.prototype.hasOwnProperty;\n // we cast maybeRoutes as OpenmrsAppRoutes mainly so we can refer to the properties it should\n // have without repeated casts\n const maybeRoutes = routes as OpenmrsAppRoutes;\n\n if (hasOwnProperty.call(routes, 'pages')) {\n if (!Boolean(maybeRoutes.pages) || !Array.isArray(maybeRoutes.pages)) {\n return false;\n }\n }\n\n if (hasOwnProperty.call(routes, 'extensions')) {\n if (!Boolean(maybeRoutes.extensions) || !Array.isArray(maybeRoutes.extensions)) {\n return false;\n }\n }\n\n // Notice that we're essentially testing for things that cannot be treated as an OpenmrsAppRoutes\n // object. This is because a completely empty object is a valid OpenmrsAppRoutes object.\n return true;\n }\n\n return false;\n}\n\n/**\n * Simple type-predicate to ensure that the value can be treated as an OpenmrsRoutes\n * object.\n *\n * @internal\n * @param routes the object to check to see if it is an OpenmrsRoutes object\n * @returns true if the routes value is an OpenmrsRoutes\n */\nexport function isOpenmrsRoutes(routes: OpenmrsRoutes | unknown): routes is OpenmrsRoutes {\n if (routes && typeof routes === 'object') {\n const maybeRoutes = routes as OpenmrsRoutes;\n\n return Object.entries(maybeRoutes).every(([key, value]) => typeof key === 'string' && isOpenmrsAppRoutes(value));\n }\n\n return false;\n}\n"],"names":["module","exports","__WEBPACK_EXTERNAL_MODULE__618__","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","__webpack_modules__","d","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","r","Symbol","toStringTag","value","localStorageRoutesPrefix","isEnabled","canAccessStorage","addRoutesOverride","moduleName","routes","startsWith","addRouteOverrideInternal","maybeRoutes","JSON","parse","isOpenmrsAppRoutes","console","error","e","URL","toString","removeRoutesOverride","localStorage","removeItem","resetAllRoutesOverrides","window","i","length","setItem","stringify","Boolean","pages","Array","isArray","extensions","isOpenmrsRoutes","entries","every"],"sourceRoot":""}
package/jest.config.js ADDED
@@ -0,0 +1,9 @@
1
+ module.exports = {
2
+ transform: {
3
+ "^.+\\.(m?j|t)sx?$": ["@swc/jest"],
4
+ },
5
+ testEnvironment: "jsdom",
6
+ testEnvironmentOptions: {
7
+ url: "http://localhost/",
8
+ },
9
+ };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@openmrs/esm-routes",
3
+ "version": "5.3.0",
4
+ "license": "MPL-2.0",
5
+ "description": "Utilities for working with the routes registry",
6
+ "browser": "dist/openmrs-esm-routes.js",
7
+ "main": "src/index.ts",
8
+ "source": true,
9
+ "sideEffects": false,
10
+ "scripts": {
11
+ "test": "cross-env TZ=UTC jest --config jest.config.js --verbose false --passWithNoTests --color",
12
+ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color",
13
+ "build": "webpack --mode=production",
14
+ "build:development": "webpack --mode development",
15
+ "analyze": "webpack --mode=production --env analyze=true",
16
+ "typescript": "tsc",
17
+ "lint": "eslint src --ext ts,tsx"
18
+ },
19
+ "keywords": [
20
+ "openmrs",
21
+ "microfrontends"
22
+ ],
23
+ "directories": {
24
+ "lib": "dist",
25
+ "src": "src"
26
+ },
27
+ "browserslist": [
28
+ "extends browserslist-config-openmrs"
29
+ ],
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/openmrs/openmrs-esm-core.git"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/openmrs/openmrs-esm-core/issues"
36
+ },
37
+ "homepage": "https://github.com/openmrs/openmrs-esm-core#readme",
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "peerDependencies": {
42
+ "@openmrs/esm-globals": "5.x",
43
+ "@openmrs/esm-utils": "5.x"
44
+ },
45
+ "devDependencies": {
46
+ "@openmrs/esm-globals": "5.4.0",
47
+ "@openmrs/esm-utils": "5.4.0",
48
+ "jest-fetch-mock": "^3.0.3"
49
+ }
50
+ }
@@ -0,0 +1 @@
1
+ export const localStorageRoutesPrefix = 'openmrs-routes:';
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './constants';
2
+ export * from './routes';
@@ -0,0 +1,137 @@
1
+ import fetchMock, { enableFetchMocks } from 'jest-fetch-mock';
2
+ enableFetchMocks();
3
+
4
+ import { addRoutesOverride, isOpenmrsAppRoutes } from './routes';
5
+
6
+ describe('Openmrs Routes Utilities', () => {
7
+ describe('addRoutesOverride', () => {
8
+ beforeEach(() => localStorage.clear());
9
+
10
+ it('should add routes when provided as an object', () => {
11
+ addRoutesOverride('@openmrs/my-module', {
12
+ backendDependencies: {
13
+ fhir2: '^2.0.0',
14
+ 'webservices.rest': '^1.4.0',
15
+ },
16
+ version: '1.2.0-pre.12345+build.8',
17
+ pages: [
18
+ {
19
+ component: 'root',
20
+ route: 'myPage',
21
+ },
22
+ ],
23
+ extensions: [
24
+ {
25
+ name: 'custom extension',
26
+ component: 'customExtension',
27
+ },
28
+ ],
29
+ });
30
+
31
+ expect(localStorage.getItem('openmrs-routes:@openmrs/my-module')).toBe(
32
+ '{"backendDependencies":{"fhir2":"^2.0.0","webservices.rest":"^1.4.0"},"version":"1.2.0-pre.12345+build.8","pages":[{"component":"root","route":"myPage"}],"extensions":[{"name":"custom extension","component":"customExtension"}]}',
33
+ );
34
+ });
35
+
36
+ it('should add routes when provided as a JSON string', () => {
37
+ addRoutesOverride(
38
+ '@openmrs/my-module',
39
+ JSON.stringify({
40
+ backendDependencies: {
41
+ fhir2: '^2.0.0',
42
+ 'webservices.rest': '^1.4.0',
43
+ },
44
+ version: '1.2.0-pre.12345+build.8',
45
+ pages: [
46
+ {
47
+ component: 'root',
48
+ route: 'myPage',
49
+ },
50
+ ],
51
+ extensions: [
52
+ {
53
+ name: 'custom extension',
54
+ component: 'customExtension',
55
+ },
56
+ ],
57
+ }),
58
+ );
59
+
60
+ expect(localStorage.getItem('openmrs-routes:@openmrs/my-module')).toBe(
61
+ '{"backendDependencies":{"fhir2":"^2.0.0","webservices.rest":"^1.4.0"},"version":"1.2.0-pre.12345+build.8","pages":[{"component":"root","route":"myPage"}],"extensions":[{"name":"custom extension","component":"customExtension"}]}',
62
+ );
63
+ });
64
+
65
+ it('should add routes when loaded via a string HTTP endpoint', () => {
66
+ addRoutesOverride('@openmrs/my-module', 'http://localhost/my-route-override.json');
67
+
68
+ expect(localStorage.getItem('openmrs-routes:@openmrs/my-module')).toBe(
69
+ '"http://localhost/my-route-override.json"',
70
+ );
71
+ });
72
+
73
+ it('should add routes when loaded via a URL HTTP endpoint', () => {
74
+ addRoutesOverride('@openmrs/my-module', new URL('http://localhost/my-route-override.json'));
75
+
76
+ expect(localStorage.getItem('openmrs-routes:@openmrs/my-module')).toBe(
77
+ '"http://localhost/my-route-override.json"',
78
+ );
79
+ });
80
+ });
81
+
82
+ describe('isOpenmrsAppRoutes', () => {
83
+ it('should return true for a valid routes object', () => {
84
+ expect(
85
+ isOpenmrsAppRoutes({
86
+ backendDependencies: {
87
+ fhir2: '^2.0.0',
88
+ 'webservices.rest': '^1.4.0',
89
+ },
90
+ version: '1.2.0-pre.12345+build.8',
91
+ pages: [
92
+ {
93
+ component: 'root',
94
+ route: 'myPage',
95
+ },
96
+ ],
97
+ extensions: [
98
+ {
99
+ name: 'custom extension',
100
+ component: 'customExtension',
101
+ },
102
+ ],
103
+ }),
104
+ ).toBe(true);
105
+ });
106
+
107
+ it('should accept an object with only pages', () => {
108
+ expect(
109
+ isOpenmrsAppRoutes({
110
+ pages: [
111
+ {
112
+ component: 'root',
113
+ route: 'myPage',
114
+ },
115
+ ],
116
+ }),
117
+ ).toBe(true);
118
+ });
119
+
120
+ it('should accept an object with only extensions', () => {
121
+ expect(
122
+ isOpenmrsAppRoutes({
123
+ extensions: [
124
+ {
125
+ name: 'custom extension',
126
+ component: 'customExtension',
127
+ },
128
+ ],
129
+ }),
130
+ ).toBe(true);
131
+ });
132
+
133
+ it('should report an empty object as valid', () => {
134
+ expect(isOpenmrsAppRoutes({})).toBe(true);
135
+ });
136
+ });
137
+ });
package/src/routes.ts ADDED
@@ -0,0 +1,142 @@
1
+ /** @module @category Routes Utilities */
2
+ import type { OpenmrsAppRoutes, OpenmrsRoutes } from '@openmrs/esm-globals';
3
+ import { canAccessStorage } from '@openmrs/esm-utils';
4
+ import { localStorageRoutesPrefix } from './constants';
5
+
6
+ const isEnabled = canAccessStorage();
7
+
8
+ /**
9
+ * Used to add a route override to local storage. These are read as the routes registry
10
+ * is assembled, so the app must be reloaded for new overrides to take effect.
11
+ *
12
+ * @internal
13
+ * @param moduleName The name of the module the routes are for
14
+ * @param routes Either an {@link OpenmrsAppRoutes} object, a string that represents a JSON
15
+ * version of an {@link OpenmrsAppRoutes} object or a string or URL that resolves to a
16
+ * JSON document that represents an {@link OpenmrsAppRoutes} object
17
+ */
18
+ export function addRoutesOverride(moduleName: string, routes: OpenmrsAppRoutes | string | URL) {
19
+ if (!isEnabled) {
20
+ return;
21
+ }
22
+
23
+ if (typeof routes === 'string') {
24
+ if (routes.startsWith('http')) {
25
+ return addRouteOverrideInternal(moduleName, routes);
26
+ } else {
27
+ try {
28
+ const maybeRoutes = JSON.parse(routes);
29
+ if (isOpenmrsAppRoutes(maybeRoutes)) {
30
+ return addRouteOverrideInternal(moduleName, maybeRoutes);
31
+ } else {
32
+ console.error(`The supplied routes for ${moduleName} is not a valid OpenmrsAppRoutes object`, routes);
33
+ }
34
+ } catch (e) {
35
+ console.error(`Could not add routes override for ${moduleName}: `, e);
36
+ }
37
+ }
38
+ } else if (routes instanceof URL) {
39
+ return addRouteOverrideInternal(moduleName, routes.toString());
40
+ } else if (isOpenmrsAppRoutes(routes)) {
41
+ return addRouteOverrideInternal(moduleName, routes);
42
+ }
43
+
44
+ console.error(
45
+ `Override for ${moduleName} is not in a valid format. Expected either a Javascript Object, a JSON string of a Javascript object, or a URL`,
46
+ routes,
47
+ );
48
+ }
49
+
50
+ /**
51
+ * Used to remove an existing routes override from local storage. These are read as the routes registry
52
+ * is assembled, so the app must be reloaded for removed override to be removed.
53
+ *
54
+ * @internal
55
+ * @param moduleName The module to remove the overrides for
56
+ */
57
+ export function removeRoutesOverride(moduleName: string) {
58
+ if (!isEnabled) {
59
+ return;
60
+ }
61
+
62
+ const key = localStorageRoutesPrefix + moduleName;
63
+ localStorage.removeItem(key);
64
+ }
65
+
66
+ /**
67
+ * Used to remove all existing routes overrides from local storage. These are read as the routes registry
68
+ * is assembled, so the app must be reloaded for the removed overrides to appear to be removed.
69
+ *
70
+ * @internal
71
+ */
72
+ export function resetAllRoutesOverrides() {
73
+ if (!isEnabled) {
74
+ return;
75
+ }
76
+
77
+ const localStorage = window.localStorage;
78
+ for (let i = 0; i < localStorage.length; i++) {
79
+ const key = localStorage.key(i);
80
+ if (key?.startsWith(localStorageRoutesPrefix)) {
81
+ localStorage.removeItem(key);
82
+ }
83
+ }
84
+ }
85
+
86
+ function addRouteOverrideInternal(moduleName: string, routes: OpenmrsAppRoutes | string) {
87
+ const key = localStorageRoutesPrefix + moduleName;
88
+ localStorage.setItem(key, JSON.stringify(routes));
89
+ }
90
+
91
+ /**
92
+ * Simple type-predicate to ensure that the value can be treated as an OpenmrsAppRoutes
93
+ * object.
94
+ *
95
+ * @internal
96
+ * @param routes the object to check to see if it is an OpenmrsAppRoutes object
97
+ * @returns true if the routes value is an OpenmrsAppRoutes
98
+ */
99
+ export function isOpenmrsAppRoutes(routes: OpenmrsAppRoutes | unknown): routes is OpenmrsAppRoutes {
100
+ if (routes && typeof routes === 'object') {
101
+ const hasOwnProperty = Object.prototype.hasOwnProperty;
102
+ // we cast maybeRoutes as OpenmrsAppRoutes mainly so we can refer to the properties it should
103
+ // have without repeated casts
104
+ const maybeRoutes = routes as OpenmrsAppRoutes;
105
+
106
+ if (hasOwnProperty.call(routes, 'pages')) {
107
+ if (!Boolean(maybeRoutes.pages) || !Array.isArray(maybeRoutes.pages)) {
108
+ return false;
109
+ }
110
+ }
111
+
112
+ if (hasOwnProperty.call(routes, 'extensions')) {
113
+ if (!Boolean(maybeRoutes.extensions) || !Array.isArray(maybeRoutes.extensions)) {
114
+ return false;
115
+ }
116
+ }
117
+
118
+ // Notice that we're essentially testing for things that cannot be treated as an OpenmrsAppRoutes
119
+ // object. This is because a completely empty object is a valid OpenmrsAppRoutes object.
120
+ return true;
121
+ }
122
+
123
+ return false;
124
+ }
125
+
126
+ /**
127
+ * Simple type-predicate to ensure that the value can be treated as an OpenmrsRoutes
128
+ * object.
129
+ *
130
+ * @internal
131
+ * @param routes the object to check to see if it is an OpenmrsRoutes object
132
+ * @returns true if the routes value is an OpenmrsRoutes
133
+ */
134
+ export function isOpenmrsRoutes(routes: OpenmrsRoutes | unknown): routes is OpenmrsRoutes {
135
+ if (routes && typeof routes === 'object') {
136
+ const maybeRoutes = routes as OpenmrsRoutes;
137
+
138
+ return Object.entries(maybeRoutes).every(([key, value]) => typeof key === 'string' && isOpenmrsAppRoutes(value));
139
+ }
140
+
141
+ return false;
142
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "esModuleInterop": true,
4
+ "module": "esnext",
5
+ "target": "es2015",
6
+ "allowSyntheticDefaultImports": true,
7
+ "jsx": "react",
8
+ "strictNullChecks": true,
9
+ "moduleResolution": "node",
10
+ "declaration": true,
11
+ "declarationDir": "dist",
12
+ "emitDeclarationOnly": true,
13
+ "lib": [
14
+ "dom",
15
+ "es5",
16
+ "scripthost",
17
+ "es2015",
18
+ "es2015.promise",
19
+ "es2016.array.include",
20
+ "es2018",
21
+ "esnext"
22
+ ]
23
+ },
24
+ "include": ["src/**/*"]
25
+ }
@@ -0,0 +1,42 @@
1
+ const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
2
+ const { resolve } = require("path");
3
+ const { CleanWebpackPlugin } = require("clean-webpack-plugin");
4
+ const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
5
+
6
+ const { peerDependencies } = require("./package.json");
7
+
8
+ module.exports = (env) => ({
9
+ entry: [resolve(__dirname, "src/index.ts")],
10
+ output: {
11
+ filename: "openmrs-esm-utils.js",
12
+ path: resolve(__dirname, "dist"),
13
+ library: { type: "system" },
14
+ },
15
+ devtool: "source-map",
16
+ module: {
17
+ rules: [
18
+ {
19
+ test: /\.m?(js|ts|tsx)$/,
20
+ exclude: /node_modules/,
21
+ use: "swc-loader",
22
+ },
23
+ ],
24
+ },
25
+ externals: Object.keys(peerDependencies || {}),
26
+ resolve: {
27
+ extensions: [".ts", ".js", ".tsx", ".jsx"],
28
+ },
29
+ plugins: [
30
+ new CleanWebpackPlugin(),
31
+ new ForkTsCheckerWebpackPlugin(),
32
+ new BundleAnalyzerPlugin({
33
+ analyzerMode: env && env.analyze ? "static" : "disabled",
34
+ }),
35
+ ],
36
+ devServer: {
37
+ disableHostCheck: true,
38
+ headers: {
39
+ "Access-Control-Allow-Origin": "*",
40
+ },
41
+ },
42
+ });