-
Notifications
You must be signed in to change notification settings - Fork 214
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(feat) Create an application-wide context for shared state (#976)
- Loading branch information
Showing
27 changed files
with
914 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# openmrs-esm-context | ||
|
||
openmrs-esm-context provides the AppContext that is useful for sharing contextual state across the application. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
module.exports = { | ||
transform: { | ||
'^.+\\.tsx?$': ['@swc/jest'], | ||
}, | ||
testEnvironment: 'jsdom', | ||
testEnvironmentOptions: { | ||
url: 'http://localhost/', | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
const appContext = {}; | ||
|
||
const nothing = Object(); | ||
|
||
export function registerContext<T extends {} = {}>(namespace: string, initialValue: T = nothing) { | ||
appContext[namespace] = initialValue ?? {}; | ||
} | ||
|
||
export function getContext<T extends {} = {}, U extends {} = T>( | ||
namespace: string, | ||
selector: (state: Readonly<T>) => U = (state) => state as unknown as U, | ||
): Readonly<U> | null { | ||
const value = appContext[namespace]; | ||
|
||
if (!value) { | ||
return null; | ||
} | ||
|
||
return Object.freeze(Object.assign({}, selector ? selector(value) : value)); | ||
} | ||
|
||
export function updateContext<T extends {} = {}>(namespace: string, update: (state: T) => T) { | ||
appContext[namespace] = update(appContext[namespace] ?? {}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
{ | ||
"name": "@openmrs/esm-context", | ||
"version": "5.5.0", | ||
"license": "MPL-2.0", | ||
"description": "Utilities for managing the current execution context", | ||
"browser": "dist/openmrs-esm-context.js", | ||
"main": "src/index.ts", | ||
"source": true, | ||
"sideEffects": false, | ||
"scripts": { | ||
"test": "cross-env TZ=UTC jest --config jest.config.js --verbose false --passWithNoTests --color", | ||
"test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", | ||
"build": "webpack --mode=production", | ||
"build:development": "webpack --mode development", | ||
"analyze": "webpack --mode=production --env analyze=true", | ||
"typescript": "tsc", | ||
"lint": "eslint src --ext ts,tsx" | ||
}, | ||
"keywords": [ | ||
"openmrs", | ||
"microfrontends" | ||
], | ||
"directories": { | ||
"lib": "dist", | ||
"src": "src" | ||
}, | ||
"browserslist": [ | ||
"extends browserslist-config-openmrs" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/openmrs/openmrs-esm-core.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/openmrs/openmrs-esm-core/issues" | ||
}, | ||
"homepage": "https://github.com/openmrs/openmrs-esm-core#readme", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"peerDependencies": { | ||
"@openmrs/esm-globals": "5.x", | ||
"@openmrs/esm-state": "5.x" | ||
}, | ||
"devDependencies": { | ||
"@openmrs/esm-globals": "workspace:*", | ||
"@openmrs/esm-state": "workspace:*" | ||
}, | ||
"dependencies": { | ||
"immer": "^10.0.4" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/** @module @category Context */ | ||
'use strict'; | ||
|
||
import { createStore } from 'zustand/vanilla'; | ||
import { registerGlobalStore } from '@openmrs/esm-state'; | ||
|
||
interface OpenmrsAppContext { | ||
[namespace: string]: unknown; | ||
} | ||
|
||
/** | ||
* @internal | ||
* | ||
* The application context store, using immer to potentially simplify updates | ||
*/ | ||
export const contextStore = createStore<OpenmrsAppContext>()(() => ({})); | ||
|
||
registerGlobalStore<OpenmrsAppContext>('openmrs-app-context', contextStore); | ||
|
||
const nothing = Object(); | ||
|
||
/** | ||
* Used by callers to register a new namespace in the application context. Attempting to register | ||
* an already-registered namespace will display a warning and make no modifications to the state. | ||
* | ||
* @param namespace the namespace to register | ||
* @param initialValue the initial value of the namespace | ||
*/ | ||
export function registerContext<T extends {} = {}>(namespace: string, initialValue: T = nothing) { | ||
contextStore.setState((state) => { | ||
if (namespace in state) { | ||
throw new Error( | ||
`Attempted to re-register namespace ${namespace} in the app context. Each namespace must be unregistered before the name can be registered again.`, | ||
); | ||
} | ||
|
||
state[namespace] = initialValue === nothing ? {} : initialValue; | ||
return state; | ||
}); | ||
} | ||
|
||
/** | ||
* Used by caller to unregister a namespace in the application context. Unregistering a namespace | ||
* will remove the namespace and all associated data. | ||
*/ | ||
export function unregisterContext(namespace: string) { | ||
contextStore.setState((state) => { | ||
if (namespace in state) { | ||
delete state[namespace]; | ||
} | ||
return state; | ||
}); | ||
} | ||
|
||
export function getContext<T extends {} = {}>(namespace: string): Readonly<T> | null; | ||
/** | ||
* Returns an _immutable_ version of the state of the namespace as it is currently | ||
* | ||
* @typeParam T The type of the value stored in the namespace | ||
* @typeParam U The return type of this hook which is mostly relevant when using a selector | ||
* @param namespace The namespace to load properties from | ||
* @param selector An optional function which extracts the relevant part of the state | ||
*/ | ||
export function getContext<T extends {} = {}, U extends {} = T>( | ||
namespace: string, | ||
selector: (state: Readonly<T>) => U = (state) => state as unknown as U, | ||
): Readonly<U> | null { | ||
const state = contextStore.getState(); | ||
if (namespace in state) { | ||
return Object.freeze(Object.assign({}, (selector ? selector(state[namespace] as T) : state[namespace]) as U)); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* Updates a namespace in the global context. If the namespace does not exist, it is registered. | ||
*/ | ||
export function updateContext<T extends {} = {}>(namespace: string, update: (state: T) => T) { | ||
contextStore.setState((state) => { | ||
if (!(namespace in state)) { | ||
state[namespace] = {}; | ||
} | ||
|
||
state[namespace] = update(state[namespace] as T); | ||
return state; | ||
}); | ||
} | ||
|
||
export type ContextCallback<T extends {} = {}> = (state: Readonly<T> | null | undefined) => void; | ||
|
||
/** | ||
* Subscribes to updates of a given namespace. Note that the returned object is immutable. | ||
* | ||
* @param namespace the namespace to subscribe to | ||
* @param callback a function invoked with the current context whenever | ||
* @returns A function to unsubscribe from the context | ||
*/ | ||
export function subscribeToContext<T extends {} = {}>(namespace: string, callback: ContextCallback<T>) { | ||
let previous = getContext<T>(namespace); | ||
|
||
return contextStore.subscribe((state) => { | ||
let current: Readonly<T> | null | undefined = namespace in state ? (state[namespace] as T) : null; | ||
|
||
if (current !== previous) { | ||
previous = current; | ||
callback(Object.freeze(Object.assign({}, current))); | ||
} | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './context'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './context'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"compilerOptions": { | ||
"esModuleInterop": true, | ||
"module": "esnext", | ||
"target": "es2015", | ||
"allowSyntheticDefaultImports": true, | ||
"jsx": "react", | ||
"strictNullChecks": true, | ||
"moduleResolution": "node", | ||
"declaration": true, | ||
"declarationDir": "dist", | ||
"emitDeclarationOnly": true, | ||
"lib": [ | ||
"dom", | ||
"es5", | ||
"scripthost", | ||
"es2015", | ||
"es2015.promise", | ||
"es2016.array.include", | ||
"es2018", | ||
"esnext" | ||
] | ||
}, | ||
"include": ["src/**/*"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); | ||
const { resolve, basename } = require('path'); | ||
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); | ||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); | ||
|
||
const { browser, peerDependencies } = require('./package.json'); | ||
|
||
module.exports = (env) => ({ | ||
entry: [resolve(__dirname, 'src/index.ts')], | ||
output: { | ||
filename: basename(browser), | ||
path: resolve(__dirname, 'dist'), | ||
library: { type: 'system' }, | ||
}, | ||
devtool: 'source-map', | ||
module: { | ||
rules: [ | ||
{ | ||
test: /\.m?(js|ts|tsx)$/, | ||
exclude: /node_modules/, | ||
use: 'swc-loader', | ||
}, | ||
], | ||
}, | ||
externals: Object.keys(peerDependencies || {}), | ||
resolve: { | ||
extensions: ['.ts', '.js', '.tsx', '.jsx'], | ||
}, | ||
plugins: [ | ||
new CleanWebpackPlugin(), | ||
new ForkTsCheckerWebpackPlugin(), | ||
new BundleAnalyzerPlugin({ | ||
analyzerMode: env && env.analyze ? 'static' : 'disabled', | ||
}), | ||
], | ||
devServer: { | ||
disableHostCheck: true, | ||
headers: { | ||
'Access-Control-Allow-Origin': '*', | ||
}, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -274,5 +274,3 @@ function loadScript( | |
} | ||
} | ||
} | ||
|
||
function closureScope() {} |
Oops, something went wrong.