@grasp-labs/ds-microfrontends-integration 0.23.0 → 0.24.0-beta.20260225075929.9b4e423

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/README.md CHANGED
@@ -106,9 +106,42 @@ import "@grasp-labs/ds-microfrontends-integration/styles.css";
106
106
 
107
107
  ### Microfrontend Configuration (`/mf-common`)
108
108
 
109
- Standardized Module Federation configuration for remote microfrontend applications.
109
+ The platform uses a **host / remote** architecture powered by [Module Federation](https://module-federation.io/). A central host application dynamically discovers and mounts independent microfrontend remotes at runtime. For this to work, every remote must follow a shared contract — exposing the same entry points, using compatible shared dependencies, and providing a navigation config so the host can build its sidebar and routing.
110
110
 
111
- #### Basic Setup
111
+ `mf-common` enforces this contract. It provides a pre-configured Vite plugin, shared dependency definitions, and type-safe navigation primitives so that each remote is wired up correctly with minimal boilerplate.
112
+
113
+ #### The Contract
114
+
115
+ Every microfrontend must expose two modules:
116
+
117
+ | Expose key | Default path | What the host expects |
118
+ | ---------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------- |
119
+ | `"."` | `"./src/App"` | A **React component** (not a full app with `ReactDOM.render`). The host mounts it inside its own router |
120
+ | `"./navigationConfig"` | `"./src/navigationConfig"` | A `ComposableNavigationConfig` object. The host reads it to build sidebar entries and route definitions |
121
+
122
+ #### Navigation Item Types
123
+
124
+ Each entry in the navigation config is a `NavigationItem` — either a **route** or a **category**:
125
+
126
+ | Type | Description |
127
+ | ---------- | -------------------------------------------------------------------------- |
128
+ | `visible` | A route that appears in the sidebar (default when `type` is omitted) |
129
+ | `hidden` | A route that is routable but does not appear in the sidebar |
130
+ | `category` | A non-routable group with `children` — used to create nested sidebar menus |
131
+
132
+ Every item has a `label` (display fallback), an optional `labelKey` (i18n key in the `navigation` namespace), and an `icon`.
133
+
134
+ #### Available Utilities
135
+
136
+ - `dsFederation(name, overrides?)` — Vite plugin that wraps Module Federation with the standard config
137
+ - `createModuleFederationConfig(name, overrides?)` — generates the raw Module Federation options if you need more control
138
+ - `createMicrofrontendsBase(name)` — returns the deployment base path (`microfrontends/<name>/`), used to set Vite's `base` in production
139
+ - `defineNavigation(config)` — defines a navigation config and extracts its page routes in one step, returning `{ navigationConfig, pageRoutes }`
140
+ - `extractPaths(config)` — recursively flattens a navigation config into a type-safe `{ KEY: path }` map
141
+ - `COMMON_SHARED_DEPS` — shared dependency definitions (React, React Router, this package) configured as singletons
142
+ - Types: `RouteConfig`, `CategoryConfig`, `NavigationItem`, `ComposableNavigationConfig`, `MicrofrontendExposes`
143
+
144
+ #### Vite Setup
112
145
 
113
146
  ```typescript
114
147
  // vite.config.ts
@@ -128,14 +161,9 @@ export default defineConfig(({ mode }) => ({
128
161
  }));
129
162
  ```
130
163
 
131
- **Default exposes:**
132
-
133
- - `"."` → `"./src/App"` - Your main React component (not a full app with ReactDOM.render, just a component)
134
- - `"./navigationConfig"` → `"./src/navigationConfig"` - Navigation configuration object of type `NavigationConfig`
135
-
136
- #### App Component Example
164
+ #### App Component
137
165
 
138
- Your `src/App.tsx` should export a React component with Routes, not render it:
166
+ Your `src/App.tsx` should export a React component with `<Routes>`, not render it — the host handles mounting:
139
167
 
140
168
  ```tsx
141
169
  // src/App.tsx
@@ -157,14 +185,16 @@ function App() {
157
185
  export default App;
158
186
  ```
159
187
 
160
- #### Navigation Configuration Example
188
+ #### Navigation Configuration
189
+
190
+ Use `defineNavigation` to define your navigation config and extract route paths in one step:
161
191
 
162
192
  ```typescript
163
193
  // src/navigationConfig.ts
164
- import type { NavigationConfig } from "@grasp-labs/ds-microfrontends-integration/mf-common";
194
+ import { defineNavigation } from "@grasp-labs/ds-microfrontends-integration/mf-common";
165
195
 
166
- export const navigationConfig = {
167
- INDEX: {
196
+ const { navigationConfig, pageRoutes: PageRoutes } = defineNavigation({
197
+ HOME: {
168
198
  label: "Home",
169
199
  path: "/",
170
200
  icon: "database",
@@ -181,21 +211,17 @@ export const navigationConfig = {
181
211
  path: "/internal",
182
212
  type: "hidden",
183
213
  },
184
- } satisfies NavigationConfig;
185
- ```
214
+ });
186
215
 
187
- **What it provides:**
216
+ export { PageRoutes };
217
+ // PageRoutes.HOME === "/"
218
+ // PageRoutes.SETTINGS === "/settings"
219
+ // PageRoutes.INTERNAL === "/internal"
188
220
 
189
- - Pre-configured Module Federation settings with standard shared dependencies (React, React Router, etc.)
190
- - Automatic public path configuration for microfrontend deployment
191
- - Type-safe navigation and route configuration helpers
192
-
193
- **Available utilities:**
221
+ export default navigationConfig;
222
+ ```
194
223
 
195
- - `dsFederation(name, overrides?)` - Vite plugin for Module Federation
196
- - `createModuleFederationConfig(name, overrides?)` - Generate config object
197
- - `COMMON_SHARED_DEPS` - Shared dependency configuration
198
- - Types: `RouteConfig`, `NavigationConfig`, `MicrofrontendExposes`
224
+ For nested configs with categories, route paths are recursively flattened into a single map.
199
225
 
200
226
  #### Shared Dependencies
201
227
 
@@ -203,10 +229,10 @@ Your microfrontend project must install compatible versions of these shared depe
203
229
 
204
230
  | Dependency | Purpose |
205
231
  | ------------------------------------------- | ------------------------------------------------------------------------------------ |
206
- | `react` | UI library - singleton ensures one React instance across all microfrontends |
207
- | `react-dom` | React DOM renderer - must match React version |
208
- | `react-router` | Routing library - singleton required for React context to work across microfrontends |
209
- | `@grasp-labs/ds-microfrontends-integration` | This package - singleton required for React context to work across microfrontends |
232
+ | `react` | UI library singleton ensures one React instance across all microfrontends |
233
+ | `react-dom` | React DOM renderer must match React version |
234
+ | `react-router` | Routing library singleton required for React context to work across microfrontends |
235
+ | `@grasp-labs/ds-microfrontends-integration` | This package singleton required for React context to work across microfrontends |
210
236
 
211
237
  These dependencies are configured as singletons to prevent multiple instances and ensure compatibility across the microfrontend architecture.
212
238
 
@@ -1,8 +1,8 @@
1
- import { Control } from 'react-hook-form';
1
+ import { Control, FieldValues, Path } from 'react-hook-form';
2
2
  import { FieldDescriptor } from 'src/lib/schema';
3
- type ArrayFieldProps = {
4
- descriptor: FieldDescriptor;
5
- control: Control;
3
+ type ArrayFieldProps<T extends FieldValues = FieldValues> = {
4
+ descriptor: FieldDescriptor<Path<T>>;
5
+ control: Control<T>;
6
6
  };
7
- export declare function ArrayField({ descriptor, control }: ArrayFieldProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function ArrayField<T extends FieldValues = FieldValues>({ descriptor, control, }: ArrayFieldProps<T>): import("react/jsx-runtime").JSX.Element;
8
8
  export {};
@@ -1,8 +1,8 @@
1
- import { Control } from 'react-hook-form';
1
+ import { Control, FieldValues, Path } from 'react-hook-form';
2
2
  import { FieldDescriptor } from 'src/lib/schema';
3
- type BooleanFieldProps = {
4
- descriptor: FieldDescriptor;
5
- control: Control;
3
+ type BooleanFieldProps<T extends FieldValues = FieldValues> = {
4
+ descriptor: FieldDescriptor<Path<T>>;
5
+ control: Control<T>;
6
6
  };
7
- export declare function BooleanField({ descriptor, control }: BooleanFieldProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function BooleanField<T extends FieldValues = FieldValues>({ descriptor, control, }: BooleanFieldProps<T>): import("react/jsx-runtime").JSX.Element;
8
8
  export {};
@@ -1,8 +1,8 @@
1
+ import { Control, FieldValues, Path } from 'react-hook-form';
1
2
  import { FieldDescriptor } from 'src/lib/schema';
2
- import { Control } from 'react-hook-form';
3
- type DateFieldProps = {
4
- descriptor: FieldDescriptor;
5
- control: Control;
3
+ type DateFieldProps<T extends FieldValues = FieldValues> = {
4
+ descriptor: FieldDescriptor<Path<T>>;
5
+ control: Control<T>;
6
6
  };
7
- export declare function DateField({ descriptor, control }: DateFieldProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function DateField<T extends FieldValues = FieldValues>({ descriptor, control, }: DateFieldProps<T>): import("react/jsx-runtime").JSX.Element;
8
8
  export {};
@@ -1,8 +1,8 @@
1
- import { Control } from 'react-hook-form';
1
+ import { Control, FieldValues, Path } from 'react-hook-form';
2
2
  import { FieldDescriptor } from 'src/lib/schema';
3
- type EnumFieldProps = {
4
- descriptor: FieldDescriptor;
5
- control: Control;
3
+ type EnumFieldProps<T extends FieldValues = FieldValues> = {
4
+ descriptor: FieldDescriptor<Path<T>>;
5
+ control: Control<T>;
6
6
  };
7
- export declare function EnumField({ descriptor, control }: EnumFieldProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function EnumField<T extends FieldValues = FieldValues>({ descriptor, control, }: EnumFieldProps<T>): import("react/jsx-runtime").JSX.Element;
8
8
  export {};
@@ -1,8 +1,8 @@
1
- import { Control } from 'react-hook-form';
1
+ import { Control, FieldValues, Path } from 'react-hook-form';
2
2
  import { FieldDescriptor } from 'src/lib/schema';
3
- type JsonFieldProps = {
4
- descriptor: FieldDescriptor;
5
- control: Control;
3
+ type JsonFieldProps<T extends FieldValues = FieldValues> = {
4
+ descriptor: FieldDescriptor<Path<T>>;
5
+ control: Control<T>;
6
6
  };
7
- export declare function JsonField({ descriptor, control }: JsonFieldProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function JsonField<T extends FieldValues = FieldValues>({ descriptor, control, }: JsonFieldProps<T>): import("react/jsx-runtime").JSX.Element;
8
8
  export {};
@@ -1,8 +1,8 @@
1
- import { Control } from 'react-hook-form';
1
+ import { Control, FieldValues, Path } from 'react-hook-form';
2
2
  import { FieldDescriptor } from 'src/lib/schema';
3
- type NumberFieldProps = {
4
- descriptor: FieldDescriptor;
5
- control: Control;
3
+ type NumberFieldProps<T extends FieldValues = FieldValues> = {
4
+ descriptor: FieldDescriptor<Path<T>>;
5
+ control: Control<T>;
6
6
  };
7
- export declare function NumberField({ descriptor, control }: NumberFieldProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function NumberField<T extends FieldValues = FieldValues>({ descriptor, control, }: NumberFieldProps<T>): import("react/jsx-runtime").JSX.Element;
8
8
  export {};
@@ -1,11 +1,11 @@
1
- import { Control, FieldErrors } from 'react-hook-form';
1
+ import { Control, FieldErrors, FieldValues } from 'react-hook-form';
2
2
  import { Schema } from 'src/types/Schema';
3
- type SchemaFieldsProps = {
4
- control: Control;
5
- errors: FieldErrors;
3
+ type SchemaFieldsProps<T extends FieldValues = FieldValues> = {
4
+ control: Control<T>;
5
+ errors: FieldErrors<T>;
6
6
  schema: Schema | null;
7
7
  prefix?: string;
8
8
  fieldDirection?: "horizontal" | "vertical";
9
9
  };
10
- export declare function SchemaFields({ control, errors, schema, prefix, fieldDirection, }: SchemaFieldsProps): import("react/jsx-runtime").JSX.Element;
10
+ export declare function SchemaFields<T extends FieldValues = FieldValues>({ control, errors, schema, prefix, fieldDirection, }: SchemaFieldsProps<T>): import("react/jsx-runtime").JSX.Element;
11
11
  export {};
@@ -1,8 +1,8 @@
1
- import { Control } from 'react-hook-form';
1
+ import { Control, FieldValues, Path } from 'react-hook-form';
2
2
  import { FieldDescriptor } from 'src/lib/schema';
3
- type TextFieldProps = {
4
- descriptor: FieldDescriptor;
5
- control: Control;
3
+ type TextFieldProps<T extends FieldValues = FieldValues> = {
4
+ descriptor: FieldDescriptor<Path<T>>;
5
+ control: Control<T>;
6
6
  };
7
- export declare function TextField({ descriptor, control }: TextFieldProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function TextField<T extends FieldValues = FieldValues>({ descriptor, control, }: TextFieldProps<T>): import("react/jsx-runtime").JSX.Element;
8
8
  export {};
@@ -1,8 +1,8 @@
1
- import { Control } from 'react-hook-form';
1
+ import { Control, FieldValues, Path } from 'react-hook-form';
2
2
  import { FieldDescriptor } from 'src/lib/schema';
3
- type VaultFieldProps = {
4
- descriptor: FieldDescriptor;
5
- control: Control;
3
+ type VaultFieldProps<T extends FieldValues = FieldValues> = {
4
+ descriptor: FieldDescriptor<Path<T>>;
5
+ control: Control<T>;
6
6
  };
7
- export declare function VaultField({ descriptor, control }: VaultFieldProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function VaultField<T extends FieldValues = FieldValues>({ descriptor, control, }: VaultFieldProps<T>): import("react/jsx-runtime").JSX.Element;
8
8
  export {};
@@ -1,3 +1,3 @@
1
1
  import { Schema } from 'src/types/Schema';
2
2
  export declare function getDefaultValue(fieldSchema: Schema, rootSchema?: Schema, initialValue?: unknown): unknown;
3
- export declare function buildDefaultValues(schema: Schema | null, rootSchema?: Schema, initialValues?: Record<string, unknown>): Record<string, unknown> | undefined;
3
+ export declare function buildDefaultValues<T extends Record<string, unknown>>(schema: Schema | null, rootSchema?: Schema, initialValues?: T): T | undefined;
@@ -3,8 +3,8 @@ import { FieldDescriptor } from './types';
3
3
  /**
4
4
  * Create a field descriptor from a schema
5
5
  */
6
- export declare function createFieldDescriptor(key: string, schema: Schema, prefix?: string, rootSchema?: Schema, requiredFields?: string[]): FieldDescriptor;
6
+ export declare function createFieldDescriptor<TFieldName extends string = string>(key: string, schema: Schema, prefix?: string, rootSchema?: Schema, requiredFields?: string[]): FieldDescriptor<TFieldName>;
7
7
  /**
8
8
  * Parse schema fields into field descriptors
9
9
  */
10
- export declare function parseSchemaFields(schema: Schema | null, prefix?: string, rootSchema?: Schema): FieldDescriptor[];
10
+ export declare function parseSchemaFields<TFieldName extends string = string>(schema: Schema | null, prefix?: string, rootSchema?: Schema): FieldDescriptor<TFieldName>[];
@@ -1,4 +1,4 @@
1
1
  import { FieldValues, Resolver } from 'react-hook-form';
2
- import { z } from 'zod';
3
2
  import { CustomErrorHandler } from 'src/types';
4
- export declare const getSchemaResolver: (schema?: z.core.JSONSchema.JSONSchema, errorHandler?: CustomErrorHandler) => Resolver<FieldValues> | undefined;
3
+ import { z } from 'zod';
4
+ export declare const getSchemaResolver: <T extends FieldValues = FieldValues>(schema?: z.core.JSONSchema.JSONSchema, errorHandler?: CustomErrorHandler) => Resolver<T> | undefined;
@@ -15,9 +15,9 @@ export type Constraints = {
15
15
  maxDate?: Date;
16
16
  locale?: unknown;
17
17
  };
18
- export type FieldDescriptor = {
18
+ export type FieldDescriptor<TFieldName extends string = string> = {
19
19
  key: string;
20
- fieldName: string;
20
+ fieldName: TFieldName;
21
21
  schema: Schema;
22
22
  type: FieldType;
23
23
  label: string;
@@ -1,5 +1,6 @@
1
1
  import { IconName } from '@grasp-labs/ds-react-components';
2
2
  import { ModuleFederationOptions } from '@module-federation/vite/lib/utils/normalizeModuleFederationOptions';
3
+ import { ExtractPaths } from './types/Navigation';
3
4
  export type BaseItem = {
4
5
  /**
5
6
  * Display label used as fallback when translation is not available
@@ -18,16 +19,46 @@ export type RouteConfig = BaseItem & {
18
19
  };
19
20
  export type CategoryConfig = BaseItem & {
20
21
  type: "category";
21
- children: Record<string, RouteConfig | CategoryConfig>;
22
+ children: Record<string, NavigationItem>;
22
23
  };
23
24
  export type NavigationItem = RouteConfig | CategoryConfig;
24
25
  /**
25
26
  * INDEX is a mandatory route representing the root of the microfrontend, used as the parent navigation item in the sidebar
27
+ * @deprecated Use ComposableNavigationConfig for new microfrontends
26
28
  */
27
29
  export type IndexNavigationConfig = {
28
30
  INDEX: RouteConfig;
29
31
  };
32
+ /**
33
+ * @deprecated Use ComposableNavigationConfig for new microfrontends
34
+ */
30
35
  export type NavigationConfig<TKeys extends string = string> = IndexNavigationConfig & Record<TKeys, NavigationItem>;
36
+ /**
37
+ * Composable navigation configuration where each entry is a standalone NavigationItem.
38
+ */
39
+ export type ComposableNavigationConfig = Record<string, NavigationItem>;
40
+ /**
41
+ * Extracts a flat `{ KEY: path }` map from a valid navigation config.
42
+ *
43
+ * @example
44
+ * const PageRoutes = extractPaths(navigationConfig);
45
+ * // PageRoutes.DASHBOARD === "/dashboard"
46
+ */
47
+ export declare const extractPaths: <const T extends ComposableNavigationConfig>(config: T) => ExtractPaths<T>;
48
+ /**
49
+ * Defines a navigation config and extracts its page routes in one step.
50
+ *
51
+ * @example
52
+ * const { navigationConfig, pageRoutes } = defineNavigation({
53
+ * INDEX: { label: "Home", labelKey: "home", path: "/", icon: "home" },
54
+ * });
55
+ * export const PageRoutes = pageRoutes;
56
+ * export default navigationConfig;
57
+ */
58
+ export declare const defineNavigation: <const T extends ComposableNavigationConfig>(config: T) => {
59
+ navigationConfig: T;
60
+ pageRoutes: ExtractPaths<T>;
61
+ };
31
62
  export type MicrofrontendExposes = {
32
63
  ".": string;
33
64
  "./navigationConfig": string;
package/dist/mf-common.js CHANGED
@@ -1,45 +1,56 @@
1
- import { federation as o } from "@module-federation/vite";
2
- const i = { react: "19.2.3", "react-dom": "19.2.3", "react-router": "7.12.0" }, n = {
3
- peerDependencies: i
4
- }, s = (e, r = {}) => {
5
- const t = a(e);
1
+ import { federation as s } from "@module-federation/vite";
2
+ const c = { react: "19.2.3", "react-dom": "19.2.3", "react-router": "7.12.0" }, o = {
3
+ peerDependencies: c
4
+ }, i = (e) => Object.entries(e).reduce((r, [n, t]) => t.type === "category" ? {
5
+ ...r,
6
+ ...i(t.children)
7
+ } : {
8
+ ...r,
9
+ [n]: t.path
10
+ }, {}), p = (e) => ({
11
+ navigationConfig: e,
12
+ pageRoutes: i(e)
13
+ }), a = (e, r = {}) => {
14
+ const n = u(e);
6
15
  return {
7
16
  name: e,
8
17
  manifest: !0,
9
18
  filename: "remoteEntry.js",
10
- getPublicPath: `function() {return "${t}"}`,
19
+ getPublicPath: `function() {return "${n}"}`,
11
20
  exposes: {
12
21
  ".": "./src/App",
13
22
  "./navigationConfig": "./src/navigationConfig"
14
23
  },
15
24
  shared: {
16
- ...c
25
+ ...d
17
26
  },
18
27
  ...r
19
28
  };
20
- }, c = {
29
+ }, d = {
21
30
  react: {
22
31
  singleton: !0,
23
- requiredVersion: n.peerDependencies.react
32
+ requiredVersion: o.peerDependencies.react
24
33
  },
25
34
  "react-dom": {
26
35
  singleton: !0,
27
- requiredVersion: n.peerDependencies["react-dom"]
36
+ requiredVersion: o.peerDependencies["react-dom"]
28
37
  },
29
38
  "react-router": {
30
39
  singleton: !0,
31
- requiredVersion: n.peerDependencies["react-router"]
40
+ requiredVersion: o.peerDependencies["react-router"]
32
41
  },
33
42
  "@grasp-labs/ds-microfrontends-integration": {
34
43
  singleton: !0,
35
44
  requiredVersion: ">=0.17.0 <1.0.0"
36
45
  }
37
- }, a = (e) => `microfrontends/${e}/`, u = (e, r = {}) => o({
38
- ...s(e, r)
46
+ }, u = (e) => `microfrontends/${e}/`, f = (e, r = {}) => s({
47
+ ...a(e, r)
39
48
  });
40
49
  export {
41
- c as COMMON_SHARED_DEPS,
42
- a as createMicrofrontendsBase,
43
- s as createModuleFederationConfig,
44
- u as dsFederation
50
+ d as COMMON_SHARED_DEPS,
51
+ u as createMicrofrontendsBase,
52
+ a as createModuleFederationConfig,
53
+ p as defineNavigation,
54
+ f as dsFederation,
55
+ i as extractPaths
45
56
  };
@@ -0,0 +1,11 @@
1
+ import { CategoryConfig, NavigationItem, RouteConfig } from '../mf-common';
2
+ export type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
3
+ /**
4
+ * Recursively extracts a flat path map from a navigation config.
5
+ * For route items, extracts `{ KEY: path }`. For categories, recurses into children.
6
+ */
7
+ export type ExtractPaths<T extends Record<string, NavigationItem>> = {
8
+ [K in keyof T as T[K] extends CategoryConfig ? never : K]: T[K] extends RouteConfig ? T[K]["path"] : never;
9
+ } & UnionToIntersection<{
10
+ [K in keyof T]: T[K] extends CategoryConfig ? ExtractPaths<T[K]["children"]> : unknown;
11
+ }[keyof T]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grasp-labs/ds-microfrontends-integration",
3
- "version": "0.23.0",
3
+ "version": "0.24.0-beta.20260225075929.9b4e423",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",