@mfeflow/contracts 0.0.0-stage → 0.2.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sanjeev Sharma
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,15 @@
1
- # Temporary Holding Version
1
+ # @mfeflow/contracts
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Part of [mfeflow](https://github.com/mfeflow/mfeflow), the React micro-frontend boilerplate and platform kit.
4
+
5
+ Types, validators and JSON Schemas for `mfe.json`, `mfeflow.json` and the remote registry. Pre-1.0: minor versions may change the API.
6
+
7
+ ```sh
8
+ pnpm add @mfeflow/contracts
9
+ ```
10
+
11
+ Bucket: **stateless**. Pure types and functions; no sharing config is needed.
12
+
13
+ Docs: [mfeflow.github.io/mfeflow](https://mfeflow.github.io/mfeflow/).
14
+
15
+ MIT licensed.
@@ -0,0 +1,48 @@
1
+ /** What federating costs on one route, against the same pages built as one app. */
2
+ export interface CostModel {
3
+ /** Largest contentful paint the shell may add, in milliseconds. */
4
+ lcpDeltaMs: number;
5
+ /** JS the shell adds over the one-app build, in KB (1 KB = 1024 bytes). */
6
+ shellKb: number;
7
+ /** JS each remote on the route adds, in KB. */
8
+ remoteKb: number;
9
+ }
10
+ export declare const COST_MODEL: CostModel;
11
+ export interface CostSample {
12
+ /** Bytes of script the shell page loads from its own files (not `/mfe/<name>/`). */
13
+ shellBytes: number;
14
+ /** Bytes of script the one-app build loads on the same route. */
15
+ monolithBytes: number;
16
+ /** Bytes of script each remote on the route loads. */
17
+ remoteBytes: Record<string, number>;
18
+ lcpDeltaMs: number;
19
+ }
20
+ export interface CostLine {
21
+ budget: number;
22
+ actual: number;
23
+ pass: boolean;
24
+ }
25
+ export interface CostVerdict {
26
+ pass: boolean;
27
+ lcp: CostLine;
28
+ shell: CostLine;
29
+ remotes: (CostLine & {
30
+ name: string;
31
+ })[];
32
+ }
33
+ /** Rounds to one decimal, the precision sizes are judged and shown at. */
34
+ export declare const round1: (value: number) => number;
35
+ /** Judges one measured route against the cost model. Sizes come back in KB, one decimal. */
36
+ export declare function judgeCost(sample: CostSample, model?: CostModel): CostVerdict;
37
+ /**
38
+ * Splits the scripts a shell page loaded into the shell's own and each remote's, by where
39
+ * they were served from: remotes live under `/mfe/<name>/` on the shell's origin.
40
+ */
41
+ export declare function splitScripts(requests: readonly {
42
+ path: string;
43
+ type: string;
44
+ bytes: number;
45
+ }[]): {
46
+ shellBytes: number;
47
+ remoteBytes: Record<string, number>;
48
+ };
package/dist/docs.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /** Where the documentation site lives. The Rspress `base`, sitemap and llms.txt origin all derive from it. */
2
+ export declare const DOCS_URL = "https://mfeflow.github.io/mfeflow";
3
+ /** Where the JSON Schemas are published, for `$schema` in editors. */
4
+ export declare const SCHEMA_URLS: {
5
+ readonly mfe: "https://mfeflow.github.io/mfeflow/schema/mfe.json";
6
+ readonly project: "https://mfeflow.github.io/mfeflow/schema/mfeflow.json";
7
+ readonly feature: "https://mfeflow.github.io/mfeflow/schema/feature.json";
8
+ readonly registryEntry: "https://mfeflow.github.io/mfeflow/schema/registry-entry.json";
9
+ readonly registryIndex: "https://mfeflow.github.io/mfeflow/schema/registry-index.json";
10
+ };
11
+ export type SchemaName = keyof typeof SCHEMA_URLS;
12
+ /** The docs page for an error code such as `E102`. */
13
+ export declare function errorDocsUrl(code: string): string;
@@ -0,0 +1,11 @@
1
+ /** The remote entry types Module Federation loads (`RemoteEntryType` in its sdk, plus `esm`). */
2
+ export declare const ENTRY_TYPES: readonly string[];
3
+ /**
4
+ * A global Module Federation can name a remote entry's container: a JS-style identifier that may
5
+ * also hold `-`, as the `globalName` in `mf-manifest.json` does for a remote like `my-app`.
6
+ */
7
+ export declare const GLOBAL_NAME: RegExp;
8
+ /** The registry remote fields the shell and the edge read; everything else is dropped. */
9
+ export declare const REGISTRY_REMOTE_FIELDS: readonly string[];
10
+ /** A copy of `remote` with only the whitelisted fields that are set. */
11
+ export declare function pickRegistryFields<T extends object>(remote: T): T;
package/dist/entry.js ADDED
@@ -0,0 +1,38 @@
1
+ const ENTRY_TYPES = [
2
+ 'var',
3
+ 'module',
4
+ 'esm',
5
+ 'assign',
6
+ 'assign-properties',
7
+ 'this',
8
+ 'window',
9
+ 'self',
10
+ 'global',
11
+ 'commonjs',
12
+ 'commonjs2',
13
+ 'commonjs-module',
14
+ 'commonjs-static',
15
+ 'amd',
16
+ 'amd-require',
17
+ 'umd',
18
+ 'umd2',
19
+ 'jsonp',
20
+ 'system'
21
+ ];
22
+ const GLOBAL_NAME = /^[A-Za-z_$][\w$-]*$/;
23
+ const REGISTRY_REMOTE_FIELDS = [
24
+ 'name',
25
+ 'entry',
26
+ 'remoteEntry',
27
+ 'type',
28
+ 'entryGlobalName',
29
+ 'version',
30
+ 'owner',
31
+ 'routes'
32
+ ];
33
+ function pickRegistryFields(remote) {
34
+ const out = {};
35
+ for (const [key, value] of Object.entries(remote))if (void 0 !== value && REGISTRY_REMOTE_FIELDS.includes(key)) out[key] = value;
36
+ return out;
37
+ }
38
+ export { ENTRY_TYPES, GLOBAL_NAME, REGISTRY_REMOTE_FIELDS, pickRegistryFields };
@@ -0,0 +1,186 @@
1
+ import { type Issue } from './validate.js';
2
+ /** The built-in transform ops. A public, versioned API (decision 0005). */
3
+ export declare const TRANSFORM_OPS: readonly ['add-import', 'wrap-provider', 'add-shared', 'add-boot-step', 'add-env-key', 'add-route', 'add-workflow-step'];
4
+ export type TransformOpName = (typeof TRANSFORM_OPS)[number];
5
+ /** Where a feature part lands: the project root, the shell, every remote, or one app. */
6
+ export type FeatureTarget = string;
7
+ export type PromptValue = string | boolean;
8
+ /**
9
+ * Applies a part only when every entry holds. A plain name is one of the feature's own options and
10
+ * must have this value, for example `{ "validator": "zod" }`. `feature:<name>` asks whether that
11
+ * feature is installed (or being added in the same command), for example `{ "feature:env-t3": true }`.
12
+ */
13
+ export type FeatureCondition = Record<string, PromptValue>;
14
+ /** The validators `add-env-key` can write a schema for. */
15
+ export type EnvValidator = 'zod' | 'valibot' | 'arktype';
16
+ export interface FeaturePrompt {
17
+ name: string;
18
+ type: 'boolean' | 'string' | 'select';
19
+ message?: string;
20
+ default?: PromptValue;
21
+ choices?: string[];
22
+ }
23
+ export interface FeatureFile {
24
+ /**
25
+ * Destination, relative to the target. `{{app.name}}` and the other template values are filled
26
+ * in per app, so a feature can write one file for each remote.
27
+ */
28
+ to: string;
29
+ target?: FeatureTarget;
30
+ /**
31
+ * Resolve `to` from the project root instead of the target app, for a file about the app that
32
+ * lives elsewhere, like `packages/contracts/src/routes/{{app.name}}.ts`. First-party only.
33
+ */
34
+ atRoot?: boolean;
35
+ when?: FeatureCondition;
36
+ /** Source file inside a local feature folder. Remote features cannot use it. */
37
+ from?: string;
38
+ /** Inline content. The only source a remote feature may use. */
39
+ content?: string;
40
+ /** Render with eta (local features) or `{{option}}` substitution (remote features). */
41
+ template?: boolean;
42
+ }
43
+ export interface FeatureDependency {
44
+ /** An npm package name. `{{scope}}/contracts` is the project's own package of that name. */
45
+ name: string;
46
+ range: string;
47
+ dev?: boolean;
48
+ target?: FeatureTarget;
49
+ when?: FeatureCondition;
50
+ }
51
+ export interface FeatureScript {
52
+ name: string;
53
+ command: string;
54
+ target?: FeatureTarget;
55
+ when?: FeatureCondition;
56
+ }
57
+ export type Literal = string | number | boolean | null;
58
+ interface TransformBase {
59
+ /** File to edit, relative to the target. */
60
+ file: string;
61
+ target?: FeatureTarget;
62
+ when?: FeatureCondition;
63
+ }
64
+ export interface AddImportOp extends TransformBase {
65
+ op: 'add-import';
66
+ from: string;
67
+ named?: string[];
68
+ default?: string;
69
+ namespace?: string;
70
+ typeOnly?: boolean;
71
+ }
72
+ export interface WrapProviderOp extends TransformBase {
73
+ op: 'wrap-provider';
74
+ component: string;
75
+ from: string;
76
+ props?: Record<string, Literal>;
77
+ /** Import `component` as the default export of `from`. Default: a named import. */
78
+ defaultImport?: boolean;
79
+ /** The component to wrap the output of. Defaults to `Providers`. */
80
+ function?: string;
81
+ }
82
+ export interface AddSharedOp extends TransformBase {
83
+ op: 'add-shared';
84
+ name: string;
85
+ /** Name of the array holding the singletons. Defaults to `singletons`. */
86
+ list?: string;
87
+ }
88
+ export interface AddBootStepOp extends TransformBase {
89
+ op: 'add-boot-step';
90
+ call: string;
91
+ from: string;
92
+ args?: Literal[];
93
+ await?: boolean;
94
+ /** Import `call` as the default export of `from`. Default: a named import. */
95
+ defaultImport?: boolean;
96
+ /** Insert before the step calling this function. Default: before the render. */
97
+ before?: string;
98
+ after?: string;
99
+ function?: string;
100
+ }
101
+ /**
102
+ * Adds a typed env key (decision 0020). `file` is the TypeScript file holding
103
+ * the schema: `env.ts` for `server`, `src/config/schema.ts` for `runtime`.
104
+ */
105
+ export interface AddEnvKeyOp extends TransformBase {
106
+ op: 'add-env-key';
107
+ key: string;
108
+ scope: 'server' | 'runtime';
109
+ /**
110
+ * The schema as source text, for example `z.string().url()`. A map gives one per validator; the
111
+ * one matching the validator the schema file imports is used.
112
+ */
113
+ schema: string | Partial<Record<EnvValidator, string>>;
114
+ /** Placeholder written to `.env.example` (a list is joined with commas) or the runtime config files. Default: empty string. */
115
+ value?: Literal | string[];
116
+ /** The schema object's variable name for `runtime`. Defaults to `runtimeConfigSchema`. */
117
+ object?: string;
118
+ /** `server`: the dotenv example file to add a line to. Defaults to `.env.example`. */
119
+ example?: string;
120
+ /** `runtime`: the `runtime-config.<env>.json` files to add the key to. */
121
+ configFiles?: string[];
122
+ }
123
+ export interface AddRouteOp extends TransformBase {
124
+ op: 'add-route';
125
+ path: string;
126
+ component: string;
127
+ from: string;
128
+ /** Import `component` as the default export of `from`. Default: a named import. */
129
+ defaultImport?: boolean;
130
+ /** Name of the routes array. Defaults to `routes`. */
131
+ routes?: string;
132
+ }
133
+ export interface WorkflowStep {
134
+ name: string;
135
+ id?: string;
136
+ uses?: string;
137
+ run?: string;
138
+ with?: Record<string, Literal>;
139
+ env?: Record<string, string>;
140
+ if?: string;
141
+ }
142
+ export interface AddWorkflowStepOp extends TransformBase {
143
+ op: 'add-workflow-step';
144
+ job: string;
145
+ step: WorkflowStep;
146
+ /** Insert after the step with this name. Default: at the end. */
147
+ after?: string;
148
+ }
149
+ export type TransformOp = AddImportOp | WrapProviderOp | AddSharedOp | AddBootStepOp | AddEnvKeyOp | AddRouteOp | AddWorkflowStepOp;
150
+ /** First-party only. Declared here so `upgrade` can read it; never run for third parties. */
151
+ export interface FeatureMigration {
152
+ from: string;
153
+ to: string;
154
+ module: string;
155
+ }
156
+ /** `feature.json`: one installable feature, declarative. */
157
+ export interface FeatureManifest {
158
+ $schema?: string;
159
+ name: string;
160
+ version: string;
161
+ description?: string;
162
+ prompts?: FeaturePrompt[];
163
+ requires?: string[];
164
+ conflicts?: string[];
165
+ files?: FeatureFile[];
166
+ dependencies?: FeatureDependency[];
167
+ scripts?: FeatureScript[];
168
+ transforms?: TransformOp[];
169
+ migrations?: FeatureMigration[];
170
+ }
171
+ /** Prefix of a `when` key that tests whether a feature is installed. */
172
+ export declare const FEATURE_CONDITION = "feature:";
173
+ export declare const FEATURE_NAME: RegExp;
174
+ export declare const IDENT: RegExp;
175
+ export declare const TARGET: RegExp;
176
+ export declare const ENV_KEY: RegExp;
177
+ export declare const JOB_ID: RegExp;
178
+ export declare const SCRIPT_NAME: RegExp;
179
+ export declare const PACKAGE: RegExp;
180
+ /** A package in the project's own scope: `{{scope}}/contracts`. */
181
+ export declare const SCOPED_PACKAGE: RegExp;
182
+ /** What a first-party dependency name may be, for the JSON schema. */
183
+ export declare const DEPENDENCY_NAME: RegExp;
184
+ export declare function validateFeatureManifest(value: unknown): Issue[];
185
+ export declare function isFeatureManifest(value: unknown): value is FeatureManifest;
186
+ export {};
@@ -0,0 +1,39 @@
1
+ import type { RegistryIndex } from './registry.js';
2
+ /** A remote module the current route renders, e.g. `{ remote: 'cart', expose: './MiniCart' }`. */
3
+ export interface RouteNeed {
4
+ remote: string;
5
+ expose: string;
6
+ }
7
+ export interface InjectionOptions {
8
+ /**
9
+ * Whether the shell keeps Module Federation's manifest loader. Default false, like the
10
+ * production shell preset: a manifest-only remote then gets no hint, because the shell
11
+ * never fetches its manifest.
12
+ */
13
+ snapshot?: boolean;
14
+ }
15
+ /**
16
+ * The HTML an edge function or server adds to the shell's `index.html` for one
17
+ * route: the registry as `window.__MFE_REGISTRY__` (so the shell fetches no
18
+ * index) and preload hints for the files each remote the route renders needs.
19
+ * Everything the runtime reads from the fetched index is inlined, so both
20
+ * paths register the same remotes; only the per-expose `exposes` file lists
21
+ * stay out of the page.
22
+ *
23
+ * Hints match how the browser will request each file, or it downloads twice:
24
+ * classic scripts and styles are `rel="preload"` without `crossorigin`; the
25
+ * files of a `module`/`esm` remote are `rel="modulepreload"` (CORS anonymous
26
+ * by default, as `import()` fetches them); a remote with no `remoteEntry`
27
+ * (manifest only) gets its manifest as `preload as="fetch" crossorigin`,
28
+ * matching Module Federation's `fetch()`, but only when `snapshot` is set.
29
+ * Nothing here injects a `<script src>` for a remote entry: it needs the
30
+ * shell's runtime first. A remote the registry does not know gets no hints.
31
+ */
32
+ export declare function registryInjection(index: RegistryIndex, needs: readonly RouteNeed[], options?: InjectionOptions): string;
33
+ /**
34
+ * Adds `snippet` to the page's `<head>`: right after the `<meta charset>` when
35
+ * there is one (the browser only looks for the charset in the first 1024
36
+ * bytes, so nothing may push it back), else right after the opening tag.
37
+ * Throws if the page has no head.
38
+ */
39
+ export declare function injectIntoHead(html: string, snippet: string): string;
@@ -0,0 +1,10 @@
1
+ export { isSafeRelativePath, type Issue } from './validate.js';
2
+ export { ANALYTICS_EVENT, EVENT_NAME, EXPOSE, isMfeManifest, MFE_NAME, ROUTE, validateMfeManifest, type MfeManifest, } from './mfe-manifest.js';
3
+ export { assetsFromManifest, type RemoteAssets } from './manifest-assets.js';
4
+ export { injectIntoHead, registryInjection, type InjectionOptions, type RouteNeed, } from './html-injection.js';
5
+ export { isRegistryEntry, isRegistryIndex, validateRegistryEntry, validateRegistryIndex, type RegistryEntry, type RegistryIndex, type RegistryIndexRemote, } from './registry.js';
6
+ export { FILE_HASH, isMfeflowProject, validateMfeflowProject, type MfeflowProject, type ProjectBudget, } from './project.js';
7
+ export { COST_MODEL, judgeCost, round1, splitScripts, type CostLine, type CostModel, type CostSample, type CostVerdict, } from './cost-model.js';
8
+ export { FEATURE_CONDITION, FEATURE_NAME, isFeatureManifest, TRANSFORM_OPS, validateFeatureManifest, type AddBootStepOp, type AddEnvKeyOp, type AddImportOp, type AddRouteOp, type AddSharedOp, type AddWorkflowStepOp, type EnvValidator, type FeatureCondition, type FeatureDependency, type FeatureFile, type FeatureManifest, type FeatureMigration, type FeaturePrompt, type FeatureScript, type FeatureTarget, type Literal, type PromptValue, type TransformOp, type TransformOpName, type WorkflowStep, type WrapProviderOp, } from './feature.js';
9
+ export { DOCS_URL, SCHEMA_URLS, errorDocsUrl, type SchemaName } from './docs.js';
10
+ export { JSON_SCHEMAS } from './json-schema.js';