@asteby/metacore-runtime-react 9.1.0 → 10.0.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/CHANGELOG.md +175 -0
- package/dist/addon-layout-context.d.ts +49 -0
- package/dist/addon-layout-context.d.ts.map +1 -0
- package/dist/addon-layout-context.js +94 -0
- package/dist/addon-loader.d.ts +14 -2
- package/dist/addon-loader.d.ts.map +1 -1
- package/dist/addon-loader.js +7 -1
- package/dist/dynamic-form-schema.d.ts +5 -0
- package/dist/dynamic-form-schema.d.ts.map +1 -1
- package/dist/dynamic-form-schema.js +34 -0
- package/dist/dynamic-form.d.ts.map +1 -1
- package/dist/dynamic-form.js +18 -2
- package/dist/dynamic-relation.d.ts.map +1 -1
- package/dist/dynamic-relation.js +59 -22
- package/dist/hotswap-reload-policy.d.ts +155 -0
- package/dist/hotswap-reload-policy.d.ts.map +1 -0
- package/dist/hotswap-reload-policy.js +227 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/manifest-hotswap-subscriber.d.ts +83 -0
- package/dist/manifest-hotswap-subscriber.d.ts.map +1 -0
- package/dist/manifest-hotswap-subscriber.js +104 -0
- package/dist/metadata-cache.d.ts +35 -0
- package/dist/metadata-cache.d.ts.map +1 -1
- package/dist/metadata-cache.js +55 -0
- package/dist/types.d.ts +20 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/use-options-resolver.d.ts +87 -0
- package/dist/use-options-resolver.d.ts.map +1 -0
- package/dist/use-options-resolver.js +147 -0
- package/dist/use-org-config-bridge.d.ts +28 -0
- package/dist/use-org-config-bridge.d.ts.map +1 -0
- package/dist/use-org-config-bridge.js +50 -0
- package/package.json +4 -4
- package/src/__tests__/hotswap-reload-policy.test.ts +249 -0
- package/src/__tests__/manifest-hotswap-subscriber.test.ts +179 -0
- package/src/__tests__/use-options-resolver.test.ts +127 -0
- package/src/__tests__/wasm-client-sri.test.ts +82 -0
- package/src/addon-layout-context.tsx +137 -0
- package/src/addon-loader.tsx +21 -1
- package/src/dynamic-form-schema.ts +36 -0
- package/src/dynamic-form.tsx +40 -2
- package/src/dynamic-relation.tsx +55 -20
- package/src/hotswap-reload-policy.ts +360 -0
- package/src/index.ts +43 -0
- package/src/manifest-hotswap-subscriber.ts +164 -0
- package/src/metadata-cache.ts +86 -0
- package/src/types.ts +24 -0
- package/src/use-options-resolver.ts +232 -0
- package/src/use-org-config-bridge.ts +60 -0
package/dist/dynamic-relation.js
CHANGED
|
@@ -10,6 +10,7 @@ import { Plus, Trash2, Pencil } from 'lucide-react';
|
|
|
10
10
|
import { useApi } from './api-context';
|
|
11
11
|
import { useMetadataCache } from './metadata-cache';
|
|
12
12
|
import { DynamicForm } from './dynamic-form';
|
|
13
|
+
import { useOptionsResolver } from './use-options-resolver';
|
|
13
14
|
import { buildCreatePayload, buildPivotAttachPayload, buildPivotRowIndex, buildRelationFilterParams, deriveRelationFormFields, diffSelection, extractSelectedTargetIds, pickOptionLabel, relationRowKey, } from './dynamic-relation-helpers';
|
|
14
15
|
export { buildCreatePayload, buildPivotAttachPayload, buildPivotRowIndex, buildRelationFilterParams, deriveRelationFormFields, diffSelection, extractSelectedTargetIds, pickOptionLabel, relationRowKey, } from './dynamic-relation-helpers';
|
|
15
16
|
const DEFAULT_STRINGS = {
|
|
@@ -140,33 +141,58 @@ function ManyToManyRelation({ kind, through, references, foreignKey, referencesK
|
|
|
140
141
|
const labels = { ...DEFAULT_STRINGS, ...(strings || {}) };
|
|
141
142
|
const refKey = referencesKey || `${references}_id`;
|
|
142
143
|
const pivotPath = pivotEndpoint || `/data/${through}`;
|
|
143
|
-
|
|
144
|
+
// referencesEndpoint is preserved as a legacy escape hatch — when set
|
|
145
|
+
// we keep the old `/data/<references>` raw fetch path (so apps that
|
|
146
|
+
// depend on a custom server route do not break). When unset we use
|
|
147
|
+
// the canonical `/api/options/:references` endpoint via
|
|
148
|
+
// useOptionsResolver, which is what the kernel auto-derives Ref to.
|
|
149
|
+
const useResolver = !referencesEndpoint;
|
|
150
|
+
const legacyTargetPath = referencesEndpoint || `/data/${references}`;
|
|
144
151
|
const cachedTargetMeta = getMetadata(references);
|
|
145
152
|
const [targetMeta, setTargetMeta] = useState(cachedTargetMeta || null);
|
|
146
153
|
const [targetRows, setTargetRows] = useState([]);
|
|
147
154
|
const [pivotRows, setPivotRows] = useState([]);
|
|
148
155
|
const [loading, setLoading] = useState(true);
|
|
149
156
|
const [syncing, setSyncing] = useState(false);
|
|
150
|
-
|
|
157
|
+
// Canonical path: SDK options resolver. Only fires when no legacy
|
|
158
|
+
// override is set. The hook is a no-op when `useResolver` is false.
|
|
159
|
+
const resolved = useOptionsResolver({
|
|
160
|
+
modelKey: '',
|
|
161
|
+
fieldKey: 'id',
|
|
162
|
+
ref: useResolver ? references : undefined,
|
|
163
|
+
enabled: useResolver,
|
|
164
|
+
});
|
|
165
|
+
const fetchPivotAndMeta = useCallback(async () => {
|
|
151
166
|
setLoading(true);
|
|
152
167
|
try {
|
|
153
168
|
const params = buildRelationFilterParams(foreignKey, parentId);
|
|
154
|
-
const
|
|
155
|
-
targetMeta ? Promise.resolve(null) : api.get(`/metadata/table/${references}`),
|
|
169
|
+
const tasks = [
|
|
156
170
|
api.get(pivotPath, { params }),
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
171
|
+
];
|
|
172
|
+
if (!targetMeta)
|
|
173
|
+
tasks.push(api.get(`/metadata/table/${references}`));
|
|
174
|
+
// Legacy fallback path: the resolver is disabled, fetch the
|
|
175
|
+
// target rows the old way so callers that depend on a custom
|
|
176
|
+
// route keep working.
|
|
177
|
+
if (!useResolver)
|
|
178
|
+
tasks.push(api.get(legacyTargetPath));
|
|
179
|
+
const results = await Promise.all(tasks);
|
|
180
|
+
const pivotRes = results[0];
|
|
181
|
+
if (pivotRes.data.success)
|
|
182
|
+
setPivotRows(pivotRes.data.data || []);
|
|
183
|
+
let cursor = 1;
|
|
184
|
+
if (!targetMeta) {
|
|
185
|
+
const metaRes = results[cursor++];
|
|
186
|
+
if (metaRes.data?.success) {
|
|
187
|
+
setTargetMeta(metaRes.data.data);
|
|
188
|
+
cacheMetadata(references, metaRes.data.data);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (!useResolver) {
|
|
192
|
+
const targetRes = results[cursor++];
|
|
193
|
+
if (targetRes.data.success)
|
|
194
|
+
setTargetRows(targetRes.data.data || []);
|
|
163
195
|
}
|
|
164
|
-
const pivotList = pivotRes.data;
|
|
165
|
-
if (pivotList.success)
|
|
166
|
-
setPivotRows(pivotList.data || []);
|
|
167
|
-
const targetList = targetRes.data;
|
|
168
|
-
if (targetList.success)
|
|
169
|
-
setTargetRows(targetList.data || []);
|
|
170
196
|
}
|
|
171
197
|
catch (err) {
|
|
172
198
|
console.error('DynamicRelation m2m fetch error', err);
|
|
@@ -174,16 +200,22 @@ function ManyToManyRelation({ kind, through, references, foreignKey, referencesK
|
|
|
174
200
|
finally {
|
|
175
201
|
setLoading(false);
|
|
176
202
|
}
|
|
177
|
-
}, [api, pivotPath,
|
|
178
|
-
useEffect(() => {
|
|
203
|
+
}, [api, pivotPath, foreignKey, parentId, references, targetMeta, cacheMetadata, useResolver, legacyTargetPath]);
|
|
204
|
+
useEffect(() => { fetchPivotAndMeta(); }, [fetchPivotAndMeta]);
|
|
179
205
|
const options = useMemo(() => {
|
|
206
|
+
if (useResolver) {
|
|
207
|
+
return resolved.options.map((o) => ({
|
|
208
|
+
value: String(o.id),
|
|
209
|
+
label: o.label,
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
180
212
|
return targetRows
|
|
181
213
|
.filter(r => r && r.id !== undefined && r.id !== null && r.id !== '')
|
|
182
214
|
.map(r => ({
|
|
183
215
|
value: String(r.id),
|
|
184
216
|
label: pickOptionLabel(r, displayKey, targetMeta?.columns),
|
|
185
217
|
}));
|
|
186
|
-
}, [targetRows, displayKey, targetMeta]);
|
|
218
|
+
}, [useResolver, resolved.options, targetRows, displayKey, targetMeta]);
|
|
187
219
|
const selectedIds = useMemo(() => extractSelectedTargetIds(pivotRows, refKey), [pivotRows, refKey]);
|
|
188
220
|
const pivotIndex = useMemo(() => buildPivotRowIndex(pivotRows, refKey), [pivotRows, refKey]);
|
|
189
221
|
const handleChange = useCallback(async (next) => {
|
|
@@ -212,7 +244,12 @@ function ManyToManyRelation({ kind, through, references, foreignKey, referencesK
|
|
|
212
244
|
if (!res.data?.success)
|
|
213
245
|
throw new Error('detach failed');
|
|
214
246
|
}
|
|
215
|
-
await
|
|
247
|
+
await fetchPivotAndMeta();
|
|
248
|
+
// Refresh resolver-driven options when active so newly attached
|
|
249
|
+
// targets reflect immediately. Refetching the pivot rows alone
|
|
250
|
+
// is enough when the resolver branch is off.
|
|
251
|
+
if (useResolver)
|
|
252
|
+
resolved.refetch();
|
|
216
253
|
onChange?.();
|
|
217
254
|
}
|
|
218
255
|
catch (err) {
|
|
@@ -221,6 +258,6 @@ function ManyToManyRelation({ kind, through, references, foreignKey, referencesK
|
|
|
221
258
|
finally {
|
|
222
259
|
setSyncing(false);
|
|
223
260
|
}
|
|
224
|
-
}, [api, canCreate, canDelete,
|
|
225
|
-
return (_jsxs("div", { className: className, "data-relation-kind": kind, "data-relation-through": through, "data-relation-references": references, children: [labels.title && (_jsx("div", { className: "pb-3", children: _jsx("h3", { className: "text-sm font-medium", children: labels.title }) })), loading ? (_jsx(Skeleton, { className: "h-10 w-full" })) : options.length === 0 ? (_jsx("div", { className: "text-center text-sm text-muted-foreground py-8 border rounded-md bg-muted/30", children: labels.emptyState })) : (_jsx(MultiSelect, { options: options, selected: selectedIds, onChange: handleChange, placeholder: labels.selectPlaceholder, searchPlaceholder: labels.selectSearchPlaceholder, emptyMessage: labels.selectEmpty }))] }));
|
|
261
|
+
}, [api, canCreate, canDelete, fetchPivotAndMeta, useResolver, resolved, foreignKey, onChange, parentId, pivotIndex, pivotPath, refKey, selectedIds, syncing]);
|
|
262
|
+
return (_jsxs("div", { className: className, "data-relation-kind": kind, "data-relation-through": through, "data-relation-references": references, children: [labels.title && (_jsx("div", { className: "pb-3", children: _jsx("h3", { className: "text-sm font-medium", children: labels.title }) })), (loading || (useResolver && resolved.loading)) ? (_jsx(Skeleton, { className: "h-10 w-full" })) : options.length === 0 ? (_jsx("div", { className: "text-center text-sm text-muted-foreground py-8 border rounded-md bg-muted/30", children: labels.emptyState })) : (_jsx(MultiSelect, { options: options, selected: selectedIds, onChange: handleChange, placeholder: labels.selectPlaceholder, searchPlaceholder: labels.selectSearchPlaceholder, emptyMessage: labels.selectEmpty }))] }));
|
|
226
263
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { type AddonManifestChangedMessage, type ManifestHotSwapClient, type WireHotSwapInvalidationOptions } from './manifest-hotswap-subscriber';
|
|
2
|
+
/**
|
|
3
|
+
* One of three strategies for reacting to an `ADDON_MANIFEST_CHANGED` event:
|
|
4
|
+
*
|
|
5
|
+
* * `"rekey"` — re-mount the addon route by flipping the key. Default.
|
|
6
|
+
* * `"page-reload"` — `window.location.reload()`. Opt-in.
|
|
7
|
+
* * `"manual"` — no automatic action; the host handles it via `onSwap`.
|
|
8
|
+
*/
|
|
9
|
+
export type HotSwapReloadStrategy = 'rekey' | 'page-reload' | 'manual';
|
|
10
|
+
/**
|
|
11
|
+
* Config for {@link useHotSwapReload}. `strategy` is the only required field
|
|
12
|
+
* — pass `{ strategy: "rekey" }` for the default behaviour or omit the
|
|
13
|
+
* config entirely.
|
|
14
|
+
*/
|
|
15
|
+
export interface HotSwapReloadConfig {
|
|
16
|
+
/** Reload policy. See {@link HotSwapReloadStrategy}. */
|
|
17
|
+
strategy?: HotSwapReloadStrategy;
|
|
18
|
+
/**
|
|
19
|
+
* Optional gate invoked **before** the reload action fires. Return
|
|
20
|
+
* `false` (or a Promise resolving to `false`) to cancel — useful for
|
|
21
|
+
* "unsaved changes" prompts on immersive addons. Receives the original
|
|
22
|
+
* `ADDON_MANIFEST_CHANGED` message so the prompt can name the addon.
|
|
23
|
+
*
|
|
24
|
+
* Runs for `"page-reload"` (cancels the `window.location.reload()`)
|
|
25
|
+
* and `"rekey"` (cancels the version bump, leaving the addon mounted
|
|
26
|
+
* with the old code — the host can re-trigger the swap later by
|
|
27
|
+
* re-calling the hook output's `reload()` method).
|
|
28
|
+
*
|
|
29
|
+
* Ignored for `"manual"` — the host owns the reload there.
|
|
30
|
+
*/
|
|
31
|
+
onBeforeReload?: (event: AddonManifestChangedMessage) => boolean | Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* Side-effect hook invoked after the policy has run (or after
|
|
34
|
+
* `onBeforeReload` returned `false`). Receives the message and the
|
|
35
|
+
* effective action that was taken: `"rekey"`, `"page-reload"`,
|
|
36
|
+
* `"cancelled"` or `"manual"`. Hosts wire telemetry / toasts here.
|
|
37
|
+
*/
|
|
38
|
+
onSwap?: (event: AddonManifestChangedMessage, action: 'rekey' | 'page-reload' | 'cancelled' | 'manual') => void;
|
|
39
|
+
/**
|
|
40
|
+
* Optional matcher forwarded to the underlying
|
|
41
|
+
* {@link useManifestHotSwapSubscriber} for cache invalidation.
|
|
42
|
+
*/
|
|
43
|
+
matcher?: WireHotSwapInvalidationOptions['matcher'];
|
|
44
|
+
}
|
|
45
|
+
export interface UseHotSwapReloadResult {
|
|
46
|
+
/**
|
|
47
|
+
* Reactive map `addonKey → hashShort`. Stable identity per render
|
|
48
|
+
* (only changes when a swap lands). Wire it into
|
|
49
|
+
* `<AddonRoute version={addonVersionMap[addonKey]} ... />` so React
|
|
50
|
+
* re-keys the subtree on hash change.
|
|
51
|
+
*
|
|
52
|
+
* Missing entries return `undefined`; the AddonRoute treats that as
|
|
53
|
+
* "no version pinned yet" and keeps a stable key.
|
|
54
|
+
*/
|
|
55
|
+
addonVersionMap: Record<string, string>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Subscribe to manifest hot-swap events and apply a reload policy.
|
|
59
|
+
*
|
|
60
|
+
* **Strategy = `"rekey"` (default):**
|
|
61
|
+
* maintains `addonVersionMap` so `<AddonRoute version=...>` re-keys
|
|
62
|
+
* the subtree on every swap. The federation loader picks the new hash
|
|
63
|
+
* up via {@link withVersionParam}, fetches a fresh `remoteEntry.js`,
|
|
64
|
+
* and registers a new container.
|
|
65
|
+
*
|
|
66
|
+
* **Strategy = `"page-reload"` (opt-in):**
|
|
67
|
+
* calls `onBeforeReload` (if supplied); if it resolves truthy,
|
|
68
|
+
* `window.location.reload()` fires. The `addonVersionMap` is still
|
|
69
|
+
* updated for callers that want to mirror it elsewhere.
|
|
70
|
+
*
|
|
71
|
+
* **Strategy = `"manual"`:**
|
|
72
|
+
* no automatic action. The `onSwap` callback fires with `"manual"`;
|
|
73
|
+
* the host decides what to do. `addonVersionMap` is updated so a
|
|
74
|
+
* later opt-in remount picks up the right hash.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* const ws = useWebSocket()
|
|
78
|
+
* useManifestHotSwapSubscriber(ws) // invalidates metadata cache
|
|
79
|
+
* const { addonVersionMap } = useHotSwapReload({ strategy: 'rekey' })
|
|
80
|
+
* // …in your router:
|
|
81
|
+
* <AddonRoute version={addonVersionMap[addonKey]}>
|
|
82
|
+
* <AddonLoader scope={addonKey} url={url} api={api} />
|
|
83
|
+
* </AddonRoute>
|
|
84
|
+
*/
|
|
85
|
+
/**
|
|
86
|
+
* Effect that {@link applyHotSwapReload} can take. Useful as a discriminator
|
|
87
|
+
* for tests and telemetry callbacks. `"noop"` is emitted when a malformed
|
|
88
|
+
* message is ignored (e.g. missing `addonKey`).
|
|
89
|
+
*/
|
|
90
|
+
export type HotSwapReloadAction = 'rekey' | 'page-reload' | 'cancelled' | 'manual' | 'noop';
|
|
91
|
+
export interface HotSwapReloadDeps {
|
|
92
|
+
/** Hash → versionMap setter. Receives an updater fn, à la React state. */
|
|
93
|
+
setVersionMap: (updater: (prev: Record<string, string>) => Record<string, string>) => void;
|
|
94
|
+
/** Defaults to `window.location.reload`. Overridable for tests / SSR. */
|
|
95
|
+
reload?: () => void;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Pure (testable) implementation of the swap handler. Decides the action
|
|
99
|
+
* given a message + config + deps, applies side effects via `deps`, and
|
|
100
|
+
* returns the action it took so callers can fire telemetry.
|
|
101
|
+
*
|
|
102
|
+
* Exported for unit tests; the React hook below composes it with React
|
|
103
|
+
* state. Hosts that want to drive the policy from a non-React context
|
|
104
|
+
* (e.g. a vanilla web component shell) can call this directly.
|
|
105
|
+
*/
|
|
106
|
+
export declare function applyHotSwapReload(message: AddonManifestChangedMessage, config: HotSwapReloadConfig, deps: HotSwapReloadDeps): Promise<HotSwapReloadAction>;
|
|
107
|
+
export declare function useHotSwapReload(client: ManifestHotSwapClient | undefined | null, config?: HotSwapReloadConfig): UseHotSwapReloadResult;
|
|
108
|
+
/**
|
|
109
|
+
* Append a `?v=<hash8>` query string to a `remoteEntry.js` URL so the
|
|
110
|
+
* browser treats it as a distinct resource and bypasses any HTTP / module
|
|
111
|
+
* cache. Idempotent — calling twice with the same hash returns the same
|
|
112
|
+
* URL. Preserves existing query params; replaces a previous `v=` entry if
|
|
113
|
+
* present so successive bumps don't accumulate stale parameters.
|
|
114
|
+
*
|
|
115
|
+
* Pure function (no `window` access) — safe to call in SSR.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* withVersionParam('/api/addons/pos/frontend/remoteEntry.js', 'abc123ef')
|
|
119
|
+
* // → '/api/addons/pos/frontend/remoteEntry.js?v=abc123ef'
|
|
120
|
+
*
|
|
121
|
+
* withVersionParam('/r.js?foo=1', 'abc123ef')
|
|
122
|
+
* // → '/r.js?foo=1&v=abc123ef'
|
|
123
|
+
*
|
|
124
|
+
* withVersionParam('/r.js?v=oldhash', 'abc123ef')
|
|
125
|
+
* // → '/r.js?v=abc123ef'
|
|
126
|
+
*/
|
|
127
|
+
export declare function withVersionParam(url: string, hash: string | undefined): string;
|
|
128
|
+
/**
|
|
129
|
+
* Remove the federation container previously registered on `window[scope]`.
|
|
130
|
+
* Hosts call this from `onSwap` before letting the addon route re-mount
|
|
131
|
+
* so the next `remoteEntry.js` injection creates a fresh container instead
|
|
132
|
+
* of short-circuiting on the cached one.
|
|
133
|
+
*
|
|
134
|
+
* Best-effort: if `window` is undefined (SSR) or the scope was never
|
|
135
|
+
* registered, this is a no-op. Returns `true` if a container was actually
|
|
136
|
+
* removed, `false` otherwise — useful for telemetry.
|
|
137
|
+
*
|
|
138
|
+
* **Caveat:** some federation runtimes wrap the container in a Proxy
|
|
139
|
+
* whose internal state survives `delete`. If you hit `Container already
|
|
140
|
+
* registered` after calling this, the federation runtime is holding the
|
|
141
|
+
* reference internally and the only reliable swap is `"page-reload"`.
|
|
142
|
+
*/
|
|
143
|
+
export declare function clearFederationContainer(scope: string): boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Normalise a manifest hash for cache-busting. Accepts the full kernel
|
|
146
|
+
* format (`sha256:abc...`), a bare hex digest, or `undefined`. Returns
|
|
147
|
+
* an 8-character lowercase prefix that's short enough to keep URLs
|
|
148
|
+
* readable while remaining collision-resistant across realistic addon
|
|
149
|
+
* versioning timelines.
|
|
150
|
+
*
|
|
151
|
+
* Exported for tests; hosts that want the full hash for their own
|
|
152
|
+
* telemetry should read `message.payload.newHash` directly.
|
|
153
|
+
*/
|
|
154
|
+
export declare function shortenHash(hash: string | undefined): string | undefined;
|
|
155
|
+
//# sourceMappingURL=hotswap-reload-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hotswap-reload-policy.d.ts","sourceRoot":"","sources":["../src/hotswap-reload-policy.ts"],"names":[],"mappings":"AA+DA,OAAO,EAEH,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,EACtC,MAAM,+BAA+B,CAAA;AAEtC;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,aAAa,GAAG,QAAQ,CAAA;AAEtE;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAChC,wDAAwD;IACxD,QAAQ,CAAC,EAAE,qBAAqB,CAAA;IAChC;;;;;;;;;;;;OAYG;IACH,cAAc,CAAC,EAAE,CACb,KAAK,EAAE,2BAA2B,KACjC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CACL,KAAK,EAAE,2BAA2B,EAClC,MAAM,EAAE,OAAO,GAAG,aAAa,GAAG,WAAW,GAAG,QAAQ,KACvD,IAAI,CAAA;IACT;;;OAGG;IACH,OAAO,CAAC,EAAE,8BAA8B,CAAC,SAAS,CAAC,CAAA;CACtD;AAED,MAAM,WAAW,sBAAsB;IACnC;;;;;;;;OAQG;IACH,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GACzB,OAAO,GACP,aAAa,GACb,WAAW,GACX,QAAQ,GACR,MAAM,CAAA;AAEZ,MAAM,WAAW,iBAAiB;IAC9B,0EAA0E;IAC1E,aAAa,EAAE,CACX,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAChE,IAAI,CAAA;IACT,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACpC,OAAO,EAAE,2BAA2B,EACpC,MAAM,EAAE,mBAAmB,EAC3B,IAAI,EAAE,iBAAiB,GACxB,OAAO,CAAC,mBAAmB,CAAC,CA6C9B;AAED,wBAAgB,gBAAgB,CAC5B,MAAM,EAAE,qBAAqB,GAAG,SAAS,GAAG,IAAI,EAChD,MAAM,GAAE,mBAAwB,GACjC,sBAAsB,CA2BxB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAiB9E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAa/D;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAOxE"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// hotswap-reload-policy — closes the last piece of RFC-0001 D4 ("zero-polling
|
|
2
|
+
// hot-swap") on the client. The {@link useManifestHotSwapSubscriber} hook
|
|
3
|
+
// already invalidates the metadata cache when the kernel announces a manifest
|
|
4
|
+
// change, but the federation container of an already-mounted addon keeps the
|
|
5
|
+
// old code in memory. This module picks one of three policies for forcing the
|
|
6
|
+
// new code to take effect:
|
|
7
|
+
//
|
|
8
|
+
// 1. `"rekey"` — the default, **recommended for most apps**. Maintains a
|
|
9
|
+
// reactive map `addonKey → hashShort` that the host wires into
|
|
10
|
+
// `<AddonRoute version={...} />`. When a swap arrives, the hash flips,
|
|
11
|
+
// React unmounts and remounts the addon subtree, which causes the
|
|
12
|
+
// federation loader to re-fetch `remoteEntry.js` (cache-busted via
|
|
13
|
+
// {@link withVersionParam}) and re-evaluate the exposed module. State
|
|
14
|
+
// inside the addon is lost — **intentional**, because the code version
|
|
15
|
+
// changed and stale closures over old props/state would be a footgun.
|
|
16
|
+
//
|
|
17
|
+
// 2. `"page-reload"` — `window.location.reload()`. Opt-in escape hatch
|
|
18
|
+
// for immersive addons with critical state (POS with an order in
|
|
19
|
+
// progress, kitchen-display with partial confirmations). Pair with
|
|
20
|
+
// `onBeforeReload` to surface an "unsaved changes" prompt before
|
|
21
|
+
// blowing the page away. Returning `false` from `onBeforeReload`
|
|
22
|
+
// cancels the reload.
|
|
23
|
+
//
|
|
24
|
+
// 3. `"manual"` — the hook only invokes the host-provided `onSwap`
|
|
25
|
+
// callback. The host decides what to do (e.g. show a toast: "New
|
|
26
|
+
// version available — reload when ready"). No automatic remount, no
|
|
27
|
+
// reload. The `addonVersionMap` is still updated so a host that wires
|
|
28
|
+
// `<AddonRoute version=...>` later in the lifecycle picks up the new
|
|
29
|
+
// hash on demand.
|
|
30
|
+
//
|
|
31
|
+
// ## Wiring example
|
|
32
|
+
//
|
|
33
|
+
// ```tsx
|
|
34
|
+
// // Host shell (4-5 line wire-up):
|
|
35
|
+
// const ws = useWebSocket()
|
|
36
|
+
// useManifestHotSwapSubscriber(ws) // invalidates metadata
|
|
37
|
+
// const { addonVersionMap } = useHotSwapReload({ strategy: "rekey" })
|
|
38
|
+
// // …in your router:
|
|
39
|
+
// <AddonRoute version={addonVersionMap[addonKey]} shell={renderShell}>
|
|
40
|
+
// <AddonLoader scope={addonKey} url={remoteEntryUrl} api={api} />
|
|
41
|
+
// </AddonRoute>
|
|
42
|
+
// ```
|
|
43
|
+
//
|
|
44
|
+
// ## Federation runtime caveat
|
|
45
|
+
//
|
|
46
|
+
// The `"rekey"` strategy re-fetches `remoteEntry.js` with a `?v=<hash8>` query
|
|
47
|
+
// suffix (see {@link withVersionParam}). For the new container to replace the
|
|
48
|
+
// old one, the federation loader **must** `delete window[Container]` before
|
|
49
|
+
// loading the new script — otherwise the cached container object short-
|
|
50
|
+
// circuits the loader and you get `Container already registered` style errors
|
|
51
|
+
// or, worse, the old code silently keeps running. This module exports
|
|
52
|
+
// {@link clearFederationContainer} for that purpose; the host's federation
|
|
53
|
+
// loader should call it from its `onSwap` hook.
|
|
54
|
+
//
|
|
55
|
+
// We deliberately keep the `delete window[Container]` side-effect OUT of this
|
|
56
|
+
// module's default behaviour. Some federation runtimes (vite-plugin-federation
|
|
57
|
+
// in dev, webpack 5 with `runtime: false`) wrap the container in a `Proxy`
|
|
58
|
+
// that mutates internal state on every access; blindly deleting it from
|
|
59
|
+
// here would race against any unmounting consumer that still holds a
|
|
60
|
+
// reference. Hosts that hit `Container already registered` should call
|
|
61
|
+
// `clearFederationContainer(scope)` from `onSwap` as documented below.
|
|
62
|
+
import { useMemo, useRef, useState } from 'react';
|
|
63
|
+
import { useManifestHotSwapSubscriber, } from './manifest-hotswap-subscriber';
|
|
64
|
+
/**
|
|
65
|
+
* Pure (testable) implementation of the swap handler. Decides the action
|
|
66
|
+
* given a message + config + deps, applies side effects via `deps`, and
|
|
67
|
+
* returns the action it took so callers can fire telemetry.
|
|
68
|
+
*
|
|
69
|
+
* Exported for unit tests; the React hook below composes it with React
|
|
70
|
+
* state. Hosts that want to drive the policy from a non-React context
|
|
71
|
+
* (e.g. a vanilla web component shell) can call this directly.
|
|
72
|
+
*/
|
|
73
|
+
export async function applyHotSwapReload(message, config, deps) {
|
|
74
|
+
const strategy = config.strategy ?? 'rekey';
|
|
75
|
+
const addonKey = message.payload?.addonKey;
|
|
76
|
+
if (!addonKey)
|
|
77
|
+
return 'noop';
|
|
78
|
+
const shortHash = shortenHash(message.payload?.newHash);
|
|
79
|
+
if (strategy === 'manual') {
|
|
80
|
+
if (shortHash) {
|
|
81
|
+
deps.setVersionMap((m) => ({ ...m, [addonKey]: shortHash }));
|
|
82
|
+
}
|
|
83
|
+
config.onSwap?.(message, 'manual');
|
|
84
|
+
return 'manual';
|
|
85
|
+
}
|
|
86
|
+
if (config.onBeforeReload) {
|
|
87
|
+
const proceed = await Promise.resolve(config.onBeforeReload(message));
|
|
88
|
+
if (!proceed) {
|
|
89
|
+
config.onSwap?.(message, 'cancelled');
|
|
90
|
+
return 'cancelled';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (strategy === 'page-reload') {
|
|
94
|
+
config.onSwap?.(message, 'page-reload');
|
|
95
|
+
const reload = deps.reload ??
|
|
96
|
+
(typeof window !== 'undefined'
|
|
97
|
+
? () => window.location.reload()
|
|
98
|
+
: undefined);
|
|
99
|
+
if (reload) {
|
|
100
|
+
// Defer so any setState before us commits before we tear down.
|
|
101
|
+
queueMicrotask(reload);
|
|
102
|
+
}
|
|
103
|
+
return 'page-reload';
|
|
104
|
+
}
|
|
105
|
+
// strategy === "rekey"
|
|
106
|
+
if (shortHash) {
|
|
107
|
+
deps.setVersionMap((m) => {
|
|
108
|
+
if (m[addonKey] === shortHash)
|
|
109
|
+
return m;
|
|
110
|
+
return { ...m, [addonKey]: shortHash };
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
config.onSwap?.(message, 'rekey');
|
|
114
|
+
return 'rekey';
|
|
115
|
+
}
|
|
116
|
+
export function useHotSwapReload(client, config = {}) {
|
|
117
|
+
const [addonVersionMap, setAddonVersionMap] = useState({});
|
|
118
|
+
// Keep config behind a ref so changing callbacks between renders does
|
|
119
|
+
// not re-subscribe to the WebSocket / tear down listeners.
|
|
120
|
+
const configRef = useRef(config);
|
|
121
|
+
configRef.current = config;
|
|
122
|
+
// Stable handler for the underlying subscriber — reads the latest
|
|
123
|
+
// config out of the ref every time the WS emits.
|
|
124
|
+
const handleSwap = useMemo(() => (message) => {
|
|
125
|
+
void applyHotSwapReload(message, configRef.current, {
|
|
126
|
+
setVersionMap: setAddonVersionMap,
|
|
127
|
+
});
|
|
128
|
+
}, []);
|
|
129
|
+
useManifestHotSwapSubscriber(client, {
|
|
130
|
+
matcher: config.matcher,
|
|
131
|
+
onSwap: handleSwap,
|
|
132
|
+
});
|
|
133
|
+
return { addonVersionMap };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Append a `?v=<hash8>` query string to a `remoteEntry.js` URL so the
|
|
137
|
+
* browser treats it as a distinct resource and bypasses any HTTP / module
|
|
138
|
+
* cache. Idempotent — calling twice with the same hash returns the same
|
|
139
|
+
* URL. Preserves existing query params; replaces a previous `v=` entry if
|
|
140
|
+
* present so successive bumps don't accumulate stale parameters.
|
|
141
|
+
*
|
|
142
|
+
* Pure function (no `window` access) — safe to call in SSR.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* withVersionParam('/api/addons/pos/frontend/remoteEntry.js', 'abc123ef')
|
|
146
|
+
* // → '/api/addons/pos/frontend/remoteEntry.js?v=abc123ef'
|
|
147
|
+
*
|
|
148
|
+
* withVersionParam('/r.js?foo=1', 'abc123ef')
|
|
149
|
+
* // → '/r.js?foo=1&v=abc123ef'
|
|
150
|
+
*
|
|
151
|
+
* withVersionParam('/r.js?v=oldhash', 'abc123ef')
|
|
152
|
+
* // → '/r.js?v=abc123ef'
|
|
153
|
+
*/
|
|
154
|
+
export function withVersionParam(url, hash) {
|
|
155
|
+
if (!hash)
|
|
156
|
+
return url;
|
|
157
|
+
const short = shortenHash(hash);
|
|
158
|
+
if (!short)
|
|
159
|
+
return url;
|
|
160
|
+
const hashIdx = url.indexOf('#');
|
|
161
|
+
const fragment = hashIdx >= 0 ? url.slice(hashIdx) : '';
|
|
162
|
+
const base = hashIdx >= 0 ? url.slice(0, hashIdx) : url;
|
|
163
|
+
const qIdx = base.indexOf('?');
|
|
164
|
+
if (qIdx < 0)
|
|
165
|
+
return `${base}?v=${short}${fragment}`;
|
|
166
|
+
const head = base.slice(0, qIdx);
|
|
167
|
+
const query = base.slice(qIdx + 1);
|
|
168
|
+
// Drop any previous v= entry and re-append.
|
|
169
|
+
const parts = query
|
|
170
|
+
.split('&')
|
|
171
|
+
.filter((p) => p.length > 0 && !p.startsWith('v='));
|
|
172
|
+
parts.push(`v=${short}`);
|
|
173
|
+
return `${head}?${parts.join('&')}${fragment}`;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Remove the federation container previously registered on `window[scope]`.
|
|
177
|
+
* Hosts call this from `onSwap` before letting the addon route re-mount
|
|
178
|
+
* so the next `remoteEntry.js` injection creates a fresh container instead
|
|
179
|
+
* of short-circuiting on the cached one.
|
|
180
|
+
*
|
|
181
|
+
* Best-effort: if `window` is undefined (SSR) or the scope was never
|
|
182
|
+
* registered, this is a no-op. Returns `true` if a container was actually
|
|
183
|
+
* removed, `false` otherwise — useful for telemetry.
|
|
184
|
+
*
|
|
185
|
+
* **Caveat:** some federation runtimes wrap the container in a Proxy
|
|
186
|
+
* whose internal state survives `delete`. If you hit `Container already
|
|
187
|
+
* registered` after calling this, the federation runtime is holding the
|
|
188
|
+
* reference internally and the only reliable swap is `"page-reload"`.
|
|
189
|
+
*/
|
|
190
|
+
export function clearFederationContainer(scope) {
|
|
191
|
+
if (typeof window === 'undefined')
|
|
192
|
+
return false;
|
|
193
|
+
if (!(scope in window))
|
|
194
|
+
return false;
|
|
195
|
+
try {
|
|
196
|
+
delete window[scope];
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// Some browsers refuse to delete non-configurable globals. Set
|
|
201
|
+
// to undefined as a fallback so the loader's `if (!window[scope])`
|
|
202
|
+
// check still triggers a re-inject.
|
|
203
|
+
;
|
|
204
|
+
window[scope] = undefined;
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Normalise a manifest hash for cache-busting. Accepts the full kernel
|
|
210
|
+
* format (`sha256:abc...`), a bare hex digest, or `undefined`. Returns
|
|
211
|
+
* an 8-character lowercase prefix that's short enough to keep URLs
|
|
212
|
+
* readable while remaining collision-resistant across realistic addon
|
|
213
|
+
* versioning timelines.
|
|
214
|
+
*
|
|
215
|
+
* Exported for tests; hosts that want the full hash for their own
|
|
216
|
+
* telemetry should read `message.payload.newHash` directly.
|
|
217
|
+
*/
|
|
218
|
+
export function shortenHash(hash) {
|
|
219
|
+
if (!hash)
|
|
220
|
+
return undefined;
|
|
221
|
+
const colonIdx = hash.indexOf(':');
|
|
222
|
+
const digest = colonIdx >= 0 ? hash.slice(colonIdx + 1) : hash;
|
|
223
|
+
const trimmed = digest.trim();
|
|
224
|
+
if (!trimmed)
|
|
225
|
+
return undefined;
|
|
226
|
+
return trimmed.slice(0, 8).toLowerCase();
|
|
227
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,12 +4,15 @@ export * from './dynamic-table';
|
|
|
4
4
|
export * from './dynamic-form';
|
|
5
5
|
export { ActionModalDispatcher, type ActionModalProps, } from './action-modal-dispatcher';
|
|
6
6
|
export * from './addon-loader';
|
|
7
|
+
export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareAddonLayout, type AddonLayout, type AddonLayoutProviderProps, } from './addon-layout-context';
|
|
7
8
|
export * from './slot';
|
|
8
9
|
export * from './capability-gate';
|
|
9
10
|
export * from './navigation-builder';
|
|
10
11
|
export * from './i18n-provider';
|
|
11
12
|
export * from './api-context';
|
|
12
13
|
export * from './metadata-cache';
|
|
14
|
+
export { ADDON_MANIFEST_CHANGED_TYPE, wireHotSwapInvalidation, useManifestHotSwapSubscriber, type AddonManifestChangedMessage, type ManifestHotSwapClient, type WireHotSwapInvalidationOptions, } from './manifest-hotswap-subscriber';
|
|
15
|
+
export { useHotSwapReload, applyHotSwapReload, withVersionParam, clearFederationContainer, shortenHash, type HotSwapReloadStrategy, type HotSwapReloadConfig, type HotSwapReloadAction, type HotSwapReloadDeps, type UseHotSwapReloadResult, } from './hotswap-reload-policy';
|
|
13
16
|
export * from './dynamic-icon';
|
|
14
17
|
export type { ColumnFilterConfig, FilterOption as DynamicColumnFilterOption, GetDynamicColumns, DynamicIconComponent, } from './dynamic-columns-shim';
|
|
15
18
|
export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, type DynamicColumnsHelpers, } from './dynamic-columns';
|
|
@@ -20,4 +23,7 @@ export { DynamicCRUDPage, type DynamicCRUDPageProps, type DynamicCRUDPageStrings
|
|
|
20
23
|
export { DynamicRelation, type DynamicRelationProps, type DynamicRelationStrings, type DynamicRelationKind, buildRelationFilterParams, buildCreatePayload, deriveRelationFormFields, relationRowKey, } from './dynamic-relation';
|
|
21
24
|
export { registerModelExtension, getModelExtension, clearModelExtensions, type ModelExtension, type ModelExtensionProps, } from './model-extension-registry';
|
|
22
25
|
export { isColumnVisibleInTable, getSearchableColumnKeys, } from './column-visibility';
|
|
26
|
+
export { useOptionsResolver, projectOption, type ResolvedOption, type OptionsMeta, type UseOptionsResolverArgs, type UseOptionsResolverResult, } from './use-options-resolver';
|
|
27
|
+
export { setOrgConfigBridge, getOrgConfigBridge, resolveValidatorToken, type OrgConfigBridge, } from './use-org-config-bridge';
|
|
28
|
+
export { registerValidator } from './dynamic-form-schema';
|
|
23
29
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,wBAAwB,EACxB,4BAA4B,EAC5B,KAAK,qBAAqB,GAC7B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC9B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACH,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GAC3B,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACH,sBAAsB,EACtB,uBAAuB,GAC1B,MAAM,qBAAqB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,GACtC,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACH,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EACX,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC9B,MAAM,yBAAyB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,wBAAwB,EACxB,4BAA4B,EAC5B,KAAK,qBAAqB,GAC7B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC9B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACH,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GAC3B,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACH,sBAAsB,EACtB,uBAAuB,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,kBAAkB,EAClB,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -9,12 +9,15 @@ export * from './dynamic-table';
|
|
|
9
9
|
export * from './dynamic-form';
|
|
10
10
|
export { ActionModalDispatcher, } from './action-modal-dispatcher';
|
|
11
11
|
export * from './addon-loader';
|
|
12
|
+
export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareAddonLayout, } from './addon-layout-context';
|
|
12
13
|
export * from './slot';
|
|
13
14
|
export * from './capability-gate';
|
|
14
15
|
export * from './navigation-builder';
|
|
15
16
|
export * from './i18n-provider';
|
|
16
17
|
export * from './api-context';
|
|
17
18
|
export * from './metadata-cache';
|
|
19
|
+
export { ADDON_MANIFEST_CHANGED_TYPE, wireHotSwapInvalidation, useManifestHotSwapSubscriber, } from './manifest-hotswap-subscriber';
|
|
20
|
+
export { useHotSwapReload, applyHotSwapReload, withVersionParam, clearFederationContainer, shortenHash, } from './hotswap-reload-policy';
|
|
18
21
|
export * from './dynamic-icon';
|
|
19
22
|
export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, } from './dynamic-columns';
|
|
20
23
|
export { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
@@ -24,3 +27,6 @@ export { DynamicCRUDPage, } from './dynamic-crud-page';
|
|
|
24
27
|
export { DynamicRelation, buildRelationFilterParams, buildCreatePayload, deriveRelationFormFields, relationRowKey, } from './dynamic-relation';
|
|
25
28
|
export { registerModelExtension, getModelExtension, clearModelExtensions, } from './model-extension-registry';
|
|
26
29
|
export { isColumnVisibleInTable, getSearchableColumnKeys, } from './column-visibility';
|
|
30
|
+
export { useOptionsResolver, projectOption, } from './use-options-resolver';
|
|
31
|
+
export { setOrgConfigBridge, getOrgConfigBridge, resolveValidatorToken, } from './use-org-config-bridge';
|
|
32
|
+
export { registerValidator } from './dynamic-form-schema';
|