@asteby/metacore-runtime-react 15.0.0 → 16.0.1
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/CHANGELOG.md +42 -0
- package/dist/addon-loader.d.ts.map +1 -1
- package/dist/addon-loader.js +37 -3
- package/package.json +4 -4
- package/src/addon-loader.tsx +45 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 16.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- cde025c: Fix intermittent `#RUNTIME-009` ("Please call createInstance first") addon load
|
|
8
|
+
errors.
|
|
9
|
+
|
|
10
|
+
When an addon mounts before `@module-federation/vite`'s asynchronous runtime
|
|
11
|
+
init lands at host boot, `registerRemotes`/`loadRemote` throw RUNTIME-009. The
|
|
12
|
+
runtime is on its way — it's a boot race — so `AddonLoader` now treats that
|
|
13
|
+
specific error as transient and retries with a short backoff (~10 × 60ms) until
|
|
14
|
+
the host's federation runtime is ready, instead of surfacing a dead "Addon load
|
|
15
|
+
error" to the user. Genuine failures (bad URL, missing export, 404) still
|
|
16
|
+
rethrow immediately.
|
|
17
|
+
|
|
18
|
+
## 16.0.0
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- 5f864d9: Make declarative dynamic-table option badges and relation chips feel alive.
|
|
23
|
+
|
|
24
|
+
Options/select/status badges that ship without an explicit `color` from the
|
|
25
|
+
backend now get a deterministic, cohesive color derived from the option value
|
|
26
|
+
(fallback label) instead of rendering as dead gray text. Same value always maps
|
|
27
|
+
to the same hue, and equal words share a color.
|
|
28
|
+
- `@asteby/metacore-ui/lib` adds `optionColor(key)` (curated 16-hue Tailwind-500
|
|
29
|
+
palette, FNV-1a hash, light/dark safe), plus `optionColorBadgeStyles`,
|
|
30
|
+
`relationChipStyles`, and the exported `OPTION_PALETTE`.
|
|
31
|
+
- `OptionBadge` uses the explicit `color` when present, otherwise derives one via
|
|
32
|
+
`optionColor`, and renders the option's lucide `icon` before the label.
|
|
33
|
+
- `RelationCell` (resolved FK chips for category/brand/supplier/…) now gets a
|
|
34
|
+
subtle deterministic color keyed on the related label — kept lighter than enum
|
|
35
|
+
badges (soft tint, no heavy fill) so the two remain distinguishable.
|
|
36
|
+
|
|
37
|
+
All colors come from hex-derived inline styles, so they render correctly
|
|
38
|
+
regardless of the host's tailwind safelist.
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- Updated dependencies [5f864d9]
|
|
43
|
+
- @asteby/metacore-ui@2.4.0
|
|
44
|
+
|
|
3
45
|
## 15.0.0
|
|
4
46
|
|
|
5
47
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"addon-loader.d.ts","sourceRoot":"","sources":["../src/addon-loader.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAGjE,MAAM,WAAW,gBAAgB;IAC7B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAA;IACb,+EAA+E;IAC/E,GAAG,EAAE,MAAM,CAAA;IACX,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,+DAA+D;IAC/D,GAAG,EAAE,QAAQ,CAAA;IACb,wCAAwC;IACxC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;IAC9B;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC7B;
|
|
1
|
+
{"version":3,"file":"addon-loader.d.ts","sourceRoot":"","sources":["../src/addon-loader.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAGjE,MAAM,WAAW,gBAAgB;IAC7B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAA;IACb,+EAA+E;IAC/E,GAAG,EAAE,MAAM,CAAA;IACX,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,+DAA+D;IAC/D,GAAG,EAAE,QAAQ,CAAA;IACb,wCAAwC;IACxC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;IAC9B;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC7B;AAoFD,wBAAgB,WAAW,CAAC,EACxB,KAAK,EACL,GAAG,EACH,MAAqB,EACrB,GAAG,EACH,QAAe,EACf,OAAO,EACP,OAAO,EACP,MAAM,EACN,QAAQ,GACX,EAAE,gBAAgB,2CA8ClB"}
|
package/dist/addon-loader.js
CHANGED
|
@@ -25,22 +25,56 @@ function remoteId(scope, module) {
|
|
|
25
25
|
const expose = module.replace(/^\.\//, '');
|
|
26
26
|
return `${scope}/${expose}`;
|
|
27
27
|
}
|
|
28
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
29
|
+
// `@module-federation/vite` initialises the shared federation runtime instance
|
|
30
|
+
// asynchronously at host boot (it injects an init call into the entry). If an
|
|
31
|
+
// addon mounts before that init resolves — a real race on slow first paints,
|
|
32
|
+
// route preloads, or HMR — `registerRemotes`/`loadRemote` throw
|
|
33
|
+
// `[ Federation Runtime ]: Please call createInstance first. #RUNTIME-009`.
|
|
34
|
+
// It's transient: the runtime IS coming, we just raced it. So we treat
|
|
35
|
+
// RUNTIME-009 specifically as retryable and back off briefly until the host's
|
|
36
|
+
// init lands, instead of surfacing a dead "Addon load error" to the user.
|
|
37
|
+
function isRuntimeNotReady(e) {
|
|
38
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
39
|
+
return msg.includes('#RUNTIME-009') || msg.includes('call createInstance');
|
|
40
|
+
}
|
|
41
|
+
// Retry an operation that may hit the boot race above. ~10 attempts × 60ms ≈
|
|
42
|
+
// 600ms worst case — generous for the host init, imperceptible in the common
|
|
43
|
+
// case (first attempt succeeds). Non-RUNTIME-009 errors (bad URL, 404, no
|
|
44
|
+
// export) rethrow immediately so genuine failures still surface fast.
|
|
45
|
+
async function withRuntimeReady(op) {
|
|
46
|
+
const maxAttempts = 10;
|
|
47
|
+
for (let attempt = 1;; attempt++) {
|
|
48
|
+
try {
|
|
49
|
+
return await op();
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
if (!isRuntimeNotReady(e) || attempt >= maxAttempts)
|
|
53
|
+
throw e;
|
|
54
|
+
await sleep(60);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
28
58
|
async function loadAddon(scope, url, module) {
|
|
29
59
|
// Register the remote container as an ES module. `type: 'module'` matches
|
|
30
60
|
// the `@module-federation/vite` remote (remoteEntry.js is an ESM bundle).
|
|
31
61
|
// The `url` already carries the `?v=` cache-bust the host computed, so the
|
|
32
62
|
// browser refetches a fresh remoteEntry when the addon version changes.
|
|
63
|
+
//
|
|
64
|
+
// Both calls are wrapped in `withRuntimeReady` because EITHER can throw
|
|
65
|
+
// RUNTIME-009 when an addon mounts ahead of the host's federation init —
|
|
66
|
+
// registration is what actually touches the (maybe-uninitialised) runtime.
|
|
33
67
|
if (!registered.has(scope)) {
|
|
34
|
-
registerRemotes([{ name: scope, entry: url, type: 'module' }],
|
|
68
|
+
await withRuntimeReady(() => registerRemotes([{ name: scope, entry: url, type: 'module' }],
|
|
35
69
|
// `force: true` so a re-registration with a new `?v=` URL (addon
|
|
36
70
|
// hot-swap / version bump) overwrites the stale entry + cache.
|
|
37
|
-
{ force: true });
|
|
71
|
+
{ force: true }));
|
|
38
72
|
registered.add(scope);
|
|
39
73
|
}
|
|
40
74
|
// loadRemote("<scope>/<expose>") returns the exposed module namespace (or
|
|
41
75
|
// null if it can't be resolved). No manual share-scope init — the host's
|
|
42
76
|
// federation runtime already initialised it.
|
|
43
|
-
return loadRemote(remoteId(scope, module));
|
|
77
|
+
return withRuntimeReady(() => loadRemote(remoteId(scope, module)));
|
|
44
78
|
}
|
|
45
79
|
export function AddonLoader({ scope, url, module = './register', api, fallback = null, onReady, onError, layout, children, }) {
|
|
46
80
|
const [status, setStatus] = useState('loading');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asteby/metacore-runtime-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "16.0.1",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"date-fns": ">=3",
|
|
35
35
|
"react-day-picker": ">=8",
|
|
36
36
|
"@asteby/metacore-sdk": "^3.1.0",
|
|
37
|
-
"@asteby/metacore-ui": "^2.
|
|
37
|
+
"@asteby/metacore-ui": "^2.4.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependenciesMeta": {
|
|
40
40
|
"@tanstack/react-router": {
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"typescript": "^6.0.0",
|
|
62
62
|
"vitest": "^4.0.0",
|
|
63
63
|
"zustand": "^5.0.0",
|
|
64
|
-
"@asteby/metacore-
|
|
65
|
-
"@asteby/metacore-
|
|
64
|
+
"@asteby/metacore-ui": "2.4.0",
|
|
65
|
+
"@asteby/metacore-sdk": "3.1.0"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"build": "tsc -p tsconfig.json",
|
package/src/addon-loader.tsx
CHANGED
|
@@ -64,6 +64,37 @@ function remoteId(scope: string, module: string): string {
|
|
|
64
64
|
return `${scope}/${expose}`
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
|
68
|
+
|
|
69
|
+
// `@module-federation/vite` initialises the shared federation runtime instance
|
|
70
|
+
// asynchronously at host boot (it injects an init call into the entry). If an
|
|
71
|
+
// addon mounts before that init resolves — a real race on slow first paints,
|
|
72
|
+
// route preloads, or HMR — `registerRemotes`/`loadRemote` throw
|
|
73
|
+
// `[ Federation Runtime ]: Please call createInstance first. #RUNTIME-009`.
|
|
74
|
+
// It's transient: the runtime IS coming, we just raced it. So we treat
|
|
75
|
+
// RUNTIME-009 specifically as retryable and back off briefly until the host's
|
|
76
|
+
// init lands, instead of surfacing a dead "Addon load error" to the user.
|
|
77
|
+
function isRuntimeNotReady(e: unknown): boolean {
|
|
78
|
+
const msg = e instanceof Error ? e.message : String(e)
|
|
79
|
+
return msg.includes('#RUNTIME-009') || msg.includes('call createInstance')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Retry an operation that may hit the boot race above. ~10 attempts × 60ms ≈
|
|
83
|
+
// 600ms worst case — generous for the host init, imperceptible in the common
|
|
84
|
+
// case (first attempt succeeds). Non-RUNTIME-009 errors (bad URL, 404, no
|
|
85
|
+
// export) rethrow immediately so genuine failures still surface fast.
|
|
86
|
+
async function withRuntimeReady<T>(op: () => T | Promise<T>): Promise<T> {
|
|
87
|
+
const maxAttempts = 10
|
|
88
|
+
for (let attempt = 1; ; attempt++) {
|
|
89
|
+
try {
|
|
90
|
+
return await op()
|
|
91
|
+
} catch (e) {
|
|
92
|
+
if (!isRuntimeNotReady(e) || attempt >= maxAttempts) throw e
|
|
93
|
+
await sleep(60)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
67
98
|
async function loadAddon(
|
|
68
99
|
scope: string,
|
|
69
100
|
url: string,
|
|
@@ -73,19 +104,27 @@ async function loadAddon(
|
|
|
73
104
|
// the `@module-federation/vite` remote (remoteEntry.js is an ESM bundle).
|
|
74
105
|
// The `url` already carries the `?v=` cache-bust the host computed, so the
|
|
75
106
|
// browser refetches a fresh remoteEntry when the addon version changes.
|
|
107
|
+
//
|
|
108
|
+
// Both calls are wrapped in `withRuntimeReady` because EITHER can throw
|
|
109
|
+
// RUNTIME-009 when an addon mounts ahead of the host's federation init —
|
|
110
|
+
// registration is what actually touches the (maybe-uninitialised) runtime.
|
|
76
111
|
if (!registered.has(scope)) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
112
|
+
await withRuntimeReady(() =>
|
|
113
|
+
registerRemotes(
|
|
114
|
+
[{ name: scope, entry: url, type: 'module' }],
|
|
115
|
+
// `force: true` so a re-registration with a new `?v=` URL (addon
|
|
116
|
+
// hot-swap / version bump) overwrites the stale entry + cache.
|
|
117
|
+
{ force: true },
|
|
118
|
+
),
|
|
82
119
|
)
|
|
83
120
|
registered.add(scope)
|
|
84
121
|
}
|
|
85
122
|
// loadRemote("<scope>/<expose>") returns the exposed module namespace (or
|
|
86
123
|
// null if it can't be resolved). No manual share-scope init — the host's
|
|
87
124
|
// federation runtime already initialised it.
|
|
88
|
-
return
|
|
125
|
+
return withRuntimeReady(() =>
|
|
126
|
+
loadRemote<AddonRegisterModule>(remoteId(scope, module)),
|
|
127
|
+
)
|
|
89
128
|
}
|
|
90
129
|
|
|
91
130
|
export function AddonLoader({
|