@openmrs/esm-extensions 6.3.1-pre.2965 → 6.3.1-pre.2997

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,94 @@
1
+ import type { ConfigObject, ExtensionSlotConfigObject } from '@openmrs/esm-config';
2
+ import { type LifeCycles } from 'single-spa';
3
+ export interface ExtensionMeta {
4
+ [_: string]: any;
5
+ }
6
+ export interface ExtensionRegistration {
7
+ readonly name: string;
8
+ load(): Promise<{
9
+ default?: LifeCycles;
10
+ } & LifeCycles>;
11
+ readonly moduleName: string;
12
+ readonly meta: Readonly<ExtensionMeta>;
13
+ readonly order?: number;
14
+ readonly online?: boolean;
15
+ readonly offline?: boolean;
16
+ readonly privileges?: string | Array<string>;
17
+ readonly featureFlag?: string;
18
+ }
19
+ export interface ExtensionInfo extends ExtensionRegistration {
20
+ /**
21
+ * The instances where the extension has been rendered using `renderExtension`.
22
+ */
23
+ instances: Array<ExtensionInstance>;
24
+ }
25
+ export interface ExtensionInstance {
26
+ id: string;
27
+ slotName: string;
28
+ slotModuleName: string;
29
+ }
30
+ export interface ExtensionInternalStore {
31
+ /** Slots indexed by name */
32
+ slots: Record<string, ExtensionSlotInfo>;
33
+ /** Extensions indexed by name */
34
+ extensions: Record<string, ExtensionInfo>;
35
+ }
36
+ export interface ExtensionSlotInfo {
37
+ /**
38
+ * The module in which the extension slot exists. Undefined if the slot
39
+ * hasn't been registered yet (but it has been attached or assigned to
40
+ * an extension.
41
+ */
42
+ moduleName?: string;
43
+ /** The name under which the extension slot has been registered. */
44
+ name: string;
45
+ /**
46
+ * The set of extension IDs which have been attached to this slot using `attach`.
47
+ * However, not all of these extension IDs should be rendered.
48
+ * `assignedIds` is the set defining those.
49
+ */
50
+ attachedIds: Array<string>;
51
+ /** The configuration provided for this slot. `null` if not yet loaded. */
52
+ config: ExtensionSlotConfigObject | null;
53
+ }
54
+ export interface ExtensionStore {
55
+ slots: Record<string, ExtensionSlotState>;
56
+ }
57
+ export interface ExtensionSlotState {
58
+ moduleName?: string;
59
+ assignedExtensions: Array<AssignedExtension>;
60
+ }
61
+ export interface AssignedExtension {
62
+ readonly id: string;
63
+ readonly name: string;
64
+ readonly moduleName: string;
65
+ readonly meta: Readonly<ExtensionMeta>;
66
+ /** The extension's config. Note that this will be `null` until the slot is mounted. */
67
+ readonly config: Readonly<ConfigObject> | null;
68
+ readonly online?: boolean | object;
69
+ readonly offline?: boolean | object;
70
+ readonly featureFlag?: string;
71
+ }
72
+ /** @deprecated replaced with AssignedExtension */
73
+ export interface ConnectedExtension {
74
+ readonly id: string;
75
+ readonly name: string;
76
+ readonly moduleName: string;
77
+ readonly meta: Readonly<ExtensionMeta>;
78
+ /** The extension's config. Note that this will be `null` until the slot is mounted. */
79
+ readonly config: Readonly<ConfigObject> | null;
80
+ }
81
+ /**
82
+ * This gets the extension system's internal store. It is subject
83
+ * to change radically and without warning. It should not be used
84
+ * outside esm-core.
85
+ * @internal
86
+ */
87
+ export declare const getExtensionInternalStore: () => import("zustand").StoreApi<ExtensionInternalStore>;
88
+ /** @internal */
89
+ export declare function updateInternalExtensionStore(updater: (state: ExtensionInternalStore) => ExtensionInternalStore): void;
90
+ /**
91
+ * This returns a store that modules can use to get information about the
92
+ * state of the extension system.
93
+ */
94
+ export declare const getExtensionStore: () => import("zustand").StoreApi<ExtensionStore>;
package/dist/store.js ADDED
@@ -0,0 +1,53 @@
1
+ /** @module @category Extension */ import { isEqual } from "lodash-es";
2
+ import { configExtensionStore } from "@openmrs/esm-config";
3
+ import { createGlobalStore, getGlobalStore } from "@openmrs/esm-state";
4
+ const extensionInternalStore = createGlobalStore('extensionsInternal', {
5
+ slots: {},
6
+ extensions: {}
7
+ });
8
+ /**
9
+ * This gets the extension system's internal store. It is subject
10
+ * to change radically and without warning. It should not be used
11
+ * outside esm-core.
12
+ * @internal
13
+ */ export const getExtensionInternalStore = ()=>getGlobalStore('extensionsInternal', {
14
+ slots: {},
15
+ extensions: {}
16
+ });
17
+ /** @internal */ export function updateInternalExtensionStore(updater) {
18
+ const state = extensionInternalStore.getState();
19
+ const newState = updater(state);
20
+ if (newState !== state) {
21
+ extensionInternalStore.setState(newState);
22
+ }
23
+ }
24
+ /**
25
+ * This returns a store that modules can use to get information about the
26
+ * state of the extension system.
27
+ */ export const getExtensionStore = ()=>getGlobalStore('extensions', {
28
+ slots: {}
29
+ });
30
+ /**
31
+ * esm-config maintains its own store of the extension information it needs
32
+ * to generate extension configs. We keep it updated based on what's in
33
+ * `extensionStore`.
34
+ */ updateConfigExtensionStore(extensionInternalStore.getState());
35
+ extensionInternalStore.subscribe(updateConfigExtensionStore);
36
+ function updateConfigExtensionStore(extensionState) {
37
+ const configExtensionRecords = [];
38
+ for (let extensionInfo of Object.values(extensionState.extensions)){
39
+ for (let instance of extensionInfo.instances){
40
+ configExtensionRecords.push({
41
+ slotModuleName: instance.slotModuleName,
42
+ extensionModuleName: extensionInfo.moduleName,
43
+ slotName: instance.slotName,
44
+ extensionId: instance.id
45
+ });
46
+ }
47
+ }
48
+ if (!isEqual(configExtensionStore.getState().mountedExtensions, configExtensionRecords)) {
49
+ configExtensionStore.setState({
50
+ mountedExtensions: configExtensionRecords
51
+ });
52
+ }
53
+ }
@@ -0,0 +1,63 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type LifeCycles } from 'single-spa';
3
+ import { type WorkspaceGroupDefinition, type WorkspaceWindowState } from '@openmrs/esm-globals';
4
+ /** See [[WorkspaceDefinition]] for more information about these properties */
5
+ export interface WorkspaceRegistration {
6
+ name: string;
7
+ title: string;
8
+ titleNode?: ReactNode;
9
+ type: string;
10
+ canHide: boolean;
11
+ canMaximize: boolean;
12
+ width: 'narrow' | 'wider' | 'extra-wide';
13
+ preferredWindowSize: WorkspaceWindowState;
14
+ load: () => Promise<{
15
+ default?: LifeCycles;
16
+ } & LifeCycles>;
17
+ moduleName: string;
18
+ groups: Array<string>;
19
+ }
20
+ export type WorkspaceGroupRegistration = WorkspaceGroupDefinition & {
21
+ members: Array<string>;
22
+ };
23
+ /** See [[WorkspaceDefinition]] for more information about these properties */
24
+ export interface RegisterWorkspaceOptions {
25
+ name: string;
26
+ title: string;
27
+ type?: string;
28
+ canHide?: boolean;
29
+ canMaximize?: boolean;
30
+ width?: 'narrow' | 'wider' | 'extra-wide';
31
+ preferredWindowSize?: WorkspaceWindowState;
32
+ load: () => Promise<{
33
+ default?: LifeCycles;
34
+ } & LifeCycles>;
35
+ moduleName: string;
36
+ groups?: Array<string>;
37
+ }
38
+ /**
39
+ * Tells the workspace system about a workspace. This is used by the app shell
40
+ * to register workspaces defined in the `routes.json` file.
41
+ * @internal
42
+ */
43
+ export declare function registerWorkspace(workspace: RegisterWorkspaceOptions): void;
44
+ /**
45
+ * Tells the workspace system about a workspace group. This is used by the app shell
46
+ * to register workspace groups defined in the `routes.json` file.
47
+ * @internal
48
+ */
49
+ export declare function registerWorkspaceGroup(workspaceGroup: WorkspaceGroupRegistration): void;
50
+ /**
51
+ * This exists for compatibility with the old way of registering
52
+ * workspaces (as extensions).
53
+ *
54
+ * @param name of the workspace
55
+ */
56
+ export declare function getWorkspaceRegistration(name: string): WorkspaceRegistration;
57
+ /**
58
+ * This provides the workspace group registration and is also compatibile with the
59
+ * old way of registering workspace groups (as extensions), but isn't recommended.
60
+ *
61
+ * @param name of the workspace
62
+ */
63
+ export declare function getWorkspaceGroupRegistration(name: string): WorkspaceGroupRegistration;
@@ -0,0 +1,121 @@
1
+ import { getExtensionRegistration } from "./index.js";
2
+ import { createGlobalStore } from "@openmrs/esm-state";
3
+ import { translateFrom } from "@openmrs/esm-translations";
4
+ const workspaceRegistrationStore = createGlobalStore('workspaceRegistrations', {
5
+ workspaces: {}
6
+ });
7
+ const workspaceGroupStore = createGlobalStore('workspaceGroups', {
8
+ workspaceGroups: {}
9
+ });
10
+ /**
11
+ * Tells the workspace system about a workspace. This is used by the app shell
12
+ * to register workspaces defined in the `routes.json` file.
13
+ * @internal
14
+ */ export function registerWorkspace(workspace) {
15
+ workspaceRegistrationStore.setState((state)=>({
16
+ workspaces: {
17
+ ...state.workspaces,
18
+ [workspace.name]: {
19
+ ...workspace,
20
+ preferredWindowSize: workspace.preferredWindowSize ?? 'normal',
21
+ type: workspace.type ?? 'form',
22
+ canHide: workspace.canHide ?? false,
23
+ canMaximize: workspace.canMaximize ?? false,
24
+ width: workspace.width ?? 'narrow',
25
+ groups: workspace.groups ?? []
26
+ }
27
+ }
28
+ }));
29
+ }
30
+ /**
31
+ * Tells the workspace system about a workspace group. This is used by the app shell
32
+ * to register workspace groups defined in the `routes.json` file.
33
+ * @internal
34
+ */ export function registerWorkspaceGroup(workspaceGroup) {
35
+ workspaceGroupStore.setState((state)=>{
36
+ const group = state.workspaceGroups[workspaceGroup.name];
37
+ if (group) {
38
+ // This condition occurs when a workspace with a `groups` property is registered before
39
+ // the corresponding workspace group. In such cases, the workspace group is registered
40
+ // by the `attachWorkspaceToGroup` function.
41
+ return {
42
+ workspaceGroups: {
43
+ ...state.workspaceGroups,
44
+ [workspaceGroup.name]: {
45
+ ...group,
46
+ members: Array.from(new Set([
47
+ ...group.members,
48
+ ...workspaceGroup.members
49
+ ]))
50
+ }
51
+ }
52
+ };
53
+ } else {
54
+ return {
55
+ workspaceGroups: {
56
+ ...state.workspaceGroups,
57
+ [workspaceGroup.name]: {
58
+ name: workspaceGroup.name,
59
+ members: workspaceGroup.members
60
+ }
61
+ }
62
+ };
63
+ }
64
+ });
65
+ }
66
+ const workspaceExtensionWarningsIssued = new Set();
67
+ /**
68
+ * This exists for compatibility with the old way of registering
69
+ * workspaces (as extensions).
70
+ *
71
+ * @param name of the workspace
72
+ */ export function getWorkspaceRegistration(name) {
73
+ const registeredWorkspaces = workspaceRegistrationStore.getState().workspaces;
74
+ if (registeredWorkspaces[name]) {
75
+ return registeredWorkspaces[name];
76
+ } else {
77
+ const workspaceExtension = getExtensionRegistration(name);
78
+ if (workspaceExtension) {
79
+ if (!workspaceExtensionWarningsIssued.has(name)) {
80
+ console.warn(`The workspace '${name}' is registered as an extension. This is deprecated. Please register it in the "workspaces" section of the routes.json file.`);
81
+ workspaceExtensionWarningsIssued.add(name);
82
+ }
83
+ return {
84
+ name: workspaceExtension.name,
85
+ title: getTitleFromExtension(workspaceExtension),
86
+ moduleName: workspaceExtension.moduleName,
87
+ preferredWindowSize: workspaceExtension.meta?.screenSize ?? 'normal',
88
+ load: workspaceExtension.load,
89
+ type: workspaceExtension.meta?.type ?? 'form',
90
+ canHide: workspaceExtension.meta?.canHide ?? false,
91
+ canMaximize: workspaceExtension.meta?.canMaximize ?? false,
92
+ width: workspaceExtension.meta?.width ?? 'narrow',
93
+ groups: workspaceExtension.meta?.groups ?? []
94
+ };
95
+ } else {
96
+ throw new Error(`No workspace named '${name}' has been registered.`);
97
+ }
98
+ }
99
+ }
100
+ /**
101
+ * This provides the workspace group registration and is also compatibile with the
102
+ * old way of registering workspace groups (as extensions), but isn't recommended.
103
+ *
104
+ * @param name of the workspace
105
+ */ export function getWorkspaceGroupRegistration(name) {
106
+ const registeredWorkspaces = workspaceGroupStore.getState().workspaceGroups;
107
+ if (registeredWorkspaces[name]) {
108
+ return registeredWorkspaces[name];
109
+ } else {
110
+ throw new Error(`No workspace group named '${name}' has been registered.`);
111
+ }
112
+ }
113
+ function getTitleFromExtension(ext) {
114
+ const title = ext?.meta?.title;
115
+ if (typeof title === 'string') {
116
+ return title;
117
+ } else if (title && typeof title === 'object') {
118
+ return translateFrom(ext.moduleName, title.key, title.default);
119
+ }
120
+ return ext.name;
121
+ }
package/mock-jest.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { getGlobalStore } from '@openmrs/esm-state/mock';
2
+
3
+ export const attach = jest.fn();
4
+ export const detach = jest.fn();
5
+ export const detachAll = jest.fn();
6
+
7
+ export const switchTo = jest.fn();
8
+
9
+ export const getExtensionStore = () => getGlobalStore('extensions', { slots: {} });
10
+
11
+ export const getExtensionInternalStore = () =>
12
+ getGlobalStore('extensions-internal', {
13
+ slots: {},
14
+ extensions: {},
15
+ });
16
+
17
+ const mockExtensionRegistry = {};
18
+
19
+ export const getExtensionRegistration = jest.fn((name) => {
20
+ return mockExtensionRegistry[name];
21
+ });
22
+
23
+ export const registerExtension = jest.fn((ext) => {
24
+ mockExtensionRegistry[ext.name] = ext;
25
+ });
26
+
27
+ export function clearMockExtensionRegistry() {
28
+ Object.keys(mockExtensionRegistry).forEach((key) => {
29
+ delete mockExtensionRegistry[key];
30
+ });
31
+ }
package/mock.ts CHANGED
@@ -1,10 +1,12 @@
1
+ import { vi } from 'vitest';
1
2
  import { getGlobalStore } from '@openmrs/esm-state/mock';
3
+ import { type WorkspaceGroupRegistration, type WorkspaceRegistration } from '.';
2
4
 
3
- export const attach = jest.fn();
4
- export const detach = jest.fn();
5
- export const detachAll = jest.fn();
5
+ export const attach = vi.fn();
6
+ export const detach = vi.fn();
7
+ export const detachAll = vi.fn();
6
8
 
7
- export const switchTo = jest.fn();
9
+ export const switchTo = vi.fn();
8
10
 
9
11
  export const getExtensionStore = () => getGlobalStore('extensions', { slots: {} });
10
12
 
@@ -16,11 +18,11 @@ export const getExtensionInternalStore = () =>
16
18
 
17
19
  const mockExtensionRegistry = {};
18
20
 
19
- export const getExtensionRegistration = jest.fn((name) => {
21
+ export const getExtensionRegistration = vi.fn((name) => {
20
22
  return mockExtensionRegistry[name];
21
23
  });
22
24
 
23
- export const registerExtension = jest.fn((ext) => {
25
+ export const registerExtension = vi.fn((ext) => {
24
26
  mockExtensionRegistry[ext.name] = ext;
25
27
  });
26
28
 
package/package.json CHANGED
@@ -1,18 +1,32 @@
1
1
  {
2
2
  "name": "@openmrs/esm-extensions",
3
- "version": "6.3.1-pre.2965",
3
+ "version": "6.3.1-pre.2997",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Coordinates extensions and extension points in the OpenMRS Frontend",
6
- "browser": "dist/openmrs-esm-extensions.js",
7
- "main": "src/index.ts",
6
+ "type": "module",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./src/index.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./src/public": {
15
+ "types": "./src/public.ts",
16
+ "default": "./dist/public.js"
17
+ },
18
+ "./mock": {
19
+ "import": "./mock.ts",
20
+ "require": "./mock-jest.ts"
21
+ }
22
+ },
8
23
  "source": true,
9
24
  "scripts": {
10
- "test": "cross-env TZ=UTC jest --config jest.config.js --verbose false --passWithNoTests --color",
11
- "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color",
12
- "build": "webpack --mode=production",
13
- "build:development": "webpack --mode development",
14
- "analyze": "webpack --mode=production --env analyze=true",
15
- "typescript": "tsc",
25
+ "test": "cross-env TZ=UTC vitest run --passWithNoTests",
26
+ "test:watch": "cross-env TZ=UTC vitest watch --passWithNoTests",
27
+ "build": "rimraf dist && concurrently \"swc --strip-leading-paths src -d dist\" \"tsc --project tsconfig.build.json\"",
28
+ "build:development": "rimraf dist && concurrently \"swc --strip-leading-paths src -d dist\" \"tsc --project tsconfig.build.json\"",
29
+ "typescript": "tsc --project tsconfig.build.json",
16
30
  "lint": "eslint src --ext ts,tsx"
17
31
  },
18
32
  "keywords": [
@@ -37,6 +51,9 @@
37
51
  "publishConfig": {
38
52
  "access": "public"
39
53
  },
54
+ "dependencies": {
55
+ "lodash-es": "^4.17.21"
56
+ },
40
57
  "peerDependencies": {
41
58
  "@openmrs/esm-api": "6.x",
42
59
  "@openmrs/esm-config": "6.x",
@@ -47,16 +64,20 @@
47
64
  "single-spa": "6.x"
48
65
  },
49
66
  "devDependencies": {
50
- "@openmrs/esm-api": "6.3.1-pre.2965",
51
- "@openmrs/esm-config": "6.3.1-pre.2965",
52
- "@openmrs/esm-expression-evaluator": "6.3.1-pre.2965",
53
- "@openmrs/esm-feature-flags": "6.3.1-pre.2965",
54
- "@openmrs/esm-state": "6.3.1-pre.2965",
55
- "@openmrs/esm-utils": "6.3.1-pre.2965",
56
- "single-spa": "^6.0.3"
57
- },
58
- "dependencies": {
59
- "lodash-es": "^4.17.21"
67
+ "@openmrs/esm-api": "6.3.1-pre.2997",
68
+ "@openmrs/esm-config": "6.3.1-pre.2997",
69
+ "@openmrs/esm-expression-evaluator": "6.3.1-pre.2997",
70
+ "@openmrs/esm-feature-flags": "6.3.1-pre.2997",
71
+ "@openmrs/esm-state": "6.3.1-pre.2997",
72
+ "@openmrs/esm-utils": "6.3.1-pre.2997",
73
+ "@swc/cli": "^0.7.7",
74
+ "@swc/core": "^1.11.29",
75
+ "concurrently": "^9.1.2",
76
+ "cross-env": "^7.0.3",
77
+ "happy-dom": "^17.4.7",
78
+ "rimraf": "^6.0.1",
79
+ "single-spa": "^6.0.3",
80
+ "vitest": "^3.1.4"
60
81
  },
61
82
  "stableVersion": "6.3.0"
62
83
  }
@@ -1,7 +1,8 @@
1
+ import { describe, expect, it, vi } from 'vitest';
1
2
  import { createGlobalStore } from '@openmrs/esm-state';
2
3
  import { attach, registerExtensionSlot } from './extensions';
3
4
 
4
- jest.mock('@openmrs/esm-api', () => ({
5
+ vi.mock('@openmrs/esm-api', () => ({
5
6
  sessionStore: createGlobalStore('mock-session-store', {
6
7
  loaded: false,
7
8
  session: null,
package/src/modals.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { getExtensionRegistration } from '@openmrs/esm-extensions';
2
1
  import { createGlobalStore } from '@openmrs/esm-state';
3
2
  import type { LifeCycles } from 'single-spa';
3
+ import { getExtensionRegistration } from '.';
4
4
 
5
5
  /** @internal */
6
6
  export interface ModalRegistration {
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig.json",
3
+ "compilerOptions": {
4
+ "declarationDir": "dist",
5
+ "emitDeclarationOnly": true
6
+ },
7
+ "extends": "./tsconfig.json",
8
+ "exclude": ["**/*.test.*", "**/setup-tests.*"]
9
+ }
package/tsconfig.json CHANGED
@@ -1,25 +1,5 @@
1
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/**/*"]
2
+ "$schema": "https://json.schemastore.org/tsconfig.json",
3
+ "extends": "../tsconfig.json",
4
+ "include": ["src/**/*.ts*"]
25
5
  }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'happy-dom',
6
+ mockReset: true,
7
+ },
8
+ });