@arcote.tech/platform 0.7.0 → 0.7.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/package.json +5 -15
- package/src/index.ts +2 -0
- package/src/module-loader.ts +103 -36
- package/src/registry.ts +17 -24
- package/src/start-app.ts +17 -0
- package/src/types.ts +30 -6
- package/src/index.server.ts +0 -84
package/package.json
CHANGED
|
@@ -1,34 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcote.tech/platform",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.1",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Przemysław Krasiński [arcote.tech]",
|
|
7
7
|
"description": "Arc Platform — module system, router, layout, theme, i18n, platform app shell",
|
|
8
8
|
"main": "./src/index.ts",
|
|
9
9
|
"types": "./src/index.ts",
|
|
10
10
|
"exports": {
|
|
11
|
-
".":
|
|
12
|
-
"types": "./src/index.ts",
|
|
13
|
-
"bun": "./src/index.server.ts",
|
|
14
|
-
"node": "./src/index.server.ts",
|
|
15
|
-
"browser": "./src/index.ts",
|
|
16
|
-
"default": "./src/index.ts"
|
|
17
|
-
},
|
|
18
|
-
"./server": {
|
|
19
|
-
"types": "./src/index.server.ts",
|
|
20
|
-
"default": "./src/index.server.ts"
|
|
21
|
-
}
|
|
11
|
+
".": "./src/index.ts"
|
|
22
12
|
},
|
|
23
13
|
"scripts": {
|
|
24
14
|
"type-check": "tsc --noEmit"
|
|
25
15
|
},
|
|
26
16
|
"dependencies": {
|
|
27
|
-
"@arcote.tech/arc-ds": "^0.7.
|
|
28
|
-
"@arcote.tech/arc-react": "^0.7.
|
|
17
|
+
"@arcote.tech/arc-ds": "^0.7.1",
|
|
18
|
+
"@arcote.tech/arc-react": "^0.7.1"
|
|
29
19
|
},
|
|
30
20
|
"peerDependencies": {
|
|
31
|
-
"@arcote.tech/arc": "^0.7.
|
|
21
|
+
"@arcote.tech/arc": "^0.7.1",
|
|
32
22
|
"@lingui/core": "^5.0.0",
|
|
33
23
|
"@lingui/react": "^5.0.0",
|
|
34
24
|
"framer-motion": "^12.0.0",
|
package/src/index.ts
CHANGED
|
@@ -97,6 +97,7 @@ export { loadModules, reloadModules, syncModules, useModuleLoader } from "./modu
|
|
|
97
97
|
export type { ModuleLoaderState, ModuleManifest } from "./module-loader";
|
|
98
98
|
export { PlatformApp } from "./platform-app";
|
|
99
99
|
export type { PlatformAppProps } from "./platform-app";
|
|
100
|
+
export { startApp } from "./start-app";
|
|
100
101
|
export type { PlatformConfig, PlatformStorageConfig } from "./registry";
|
|
101
102
|
export {
|
|
102
103
|
PlatformModelProvider,
|
|
@@ -113,6 +114,7 @@ export type {
|
|
|
113
114
|
ArcLayoutComponent,
|
|
114
115
|
ArcModule,
|
|
115
116
|
BuildManifest,
|
|
117
|
+
BuildManifestGroup,
|
|
116
118
|
BuiltModule,
|
|
117
119
|
ContextElementFragment,
|
|
118
120
|
ModuleAccess,
|
package/src/module-loader.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { clearModules, getAllRegisteredModules, getContext, setActiveModules } from "./registry";
|
|
2
|
-
import type { BuildManifest,
|
|
2
|
+
import type { BuildManifest, BuildManifestGroup } from "./types";
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
|
|
5
5
|
/** @deprecated Use BuildManifest from "./types" */
|
|
@@ -8,35 +8,60 @@ export type ModuleManifest = BuildManifest;
|
|
|
8
8
|
export type ModuleLoaderState = "loading" | "ready" | "error";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* URL for a
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* Server-filtered descriptors carry a signed `mod.url` (chunk-aware HMAC) for
|
|
16
|
-
* non-public chunks. Otherwise the path is `/modules/<chunk>/<file>` — public
|
|
17
|
-
* chunks are served without a signature.
|
|
11
|
+
* URL for a token-group bundle. Server-filtered manifests carry a signed
|
|
12
|
+
* `group.url` (HMAC, 1h TTL); we use that directly. `bust` overrides the
|
|
13
|
+
* content-hash cache key during dev full-reloads.
|
|
18
14
|
*/
|
|
19
|
-
function
|
|
20
|
-
const base =
|
|
21
|
-
? `${baseUrl}${
|
|
22
|
-
: `${baseUrl}/
|
|
15
|
+
function groupUrl(baseUrl: string, group: BuildManifestGroup, bust?: string): string {
|
|
16
|
+
const base = group.url
|
|
17
|
+
? `${baseUrl}${group.url}`
|
|
18
|
+
: `${baseUrl}/browser/${group.file}`;
|
|
23
19
|
const busterKey = bust ? "t" : "v";
|
|
24
|
-
const busterVal = bust ??
|
|
20
|
+
const busterVal = bust ?? group.hash;
|
|
25
21
|
if (!busterVal) return base;
|
|
26
22
|
return `${base}${base.includes("?") ? "&" : "?"}${busterKey}=${busterVal}`;
|
|
27
23
|
}
|
|
28
24
|
|
|
29
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Decode a JWT payload (base64url) without verifying signature. Returns
|
|
27
|
+
* null if the token is malformed. Used purely to read the `exp` claim so
|
|
28
|
+
* we can drop expired tokens client-side before they hit the server.
|
|
29
|
+
*/
|
|
30
|
+
function decodeJwtPayload(jwt: string): { exp?: number } | null {
|
|
31
|
+
const parts = jwt.split(".");
|
|
32
|
+
if (parts.length !== 3) return null;
|
|
33
|
+
try {
|
|
34
|
+
const b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
35
|
+
const padded = b64 + "=".repeat((4 - (b64.length % 4)) % 4);
|
|
36
|
+
return JSON.parse(atob(padded));
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Read all persisted arc tokens from localStorage, dropping any whose `exp`
|
|
44
|
+
* claim is in the past. Stale tokens are removed from localStorage so they
|
|
45
|
+
* don't keep ghosting in subsequent reads.
|
|
46
|
+
*/
|
|
30
47
|
function getAllPersistedTokens(): Record<string, string> {
|
|
31
48
|
if (typeof localStorage === "undefined") return {};
|
|
32
49
|
const tokens: Record<string, string> = {};
|
|
50
|
+
const stale: string[] = [];
|
|
51
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
33
52
|
for (let i = 0; i < localStorage.length; i++) {
|
|
34
53
|
const key = localStorage.key(i);
|
|
35
|
-
if (key?.startsWith("arc:token:"))
|
|
36
|
-
|
|
37
|
-
|
|
54
|
+
if (!key?.startsWith("arc:token:")) continue;
|
|
55
|
+
const raw = localStorage.getItem(key);
|
|
56
|
+
if (!raw) continue;
|
|
57
|
+
const payload = decodeJwtPayload(raw);
|
|
58
|
+
if (payload?.exp && payload.exp < nowSec) {
|
|
59
|
+
stale.push(key);
|
|
60
|
+
continue;
|
|
38
61
|
}
|
|
62
|
+
tokens[key.slice(10)] = raw;
|
|
39
63
|
}
|
|
64
|
+
for (const key of stale) localStorage.removeItem(key);
|
|
40
65
|
return tokens;
|
|
41
66
|
}
|
|
42
67
|
|
|
@@ -70,8 +95,40 @@ async function fetchManifest(
|
|
|
70
95
|
return res.json();
|
|
71
96
|
}
|
|
72
97
|
|
|
73
|
-
/** Track hashes of
|
|
74
|
-
const
|
|
98
|
+
/** Track hashes of token-group bundles already imported so hot-swaps re-import on change. */
|
|
99
|
+
const importedGroupHashes = new Map<string, string>();
|
|
100
|
+
|
|
101
|
+
/** Names of every module ever observed inside a protected group. Used to compute
|
|
102
|
+
* the active-modules set: hide previously-loaded group members that the caller
|
|
103
|
+
* no longer has access to. Public modules (from the initial bundle) are never
|
|
104
|
+
* in this set, so they stay visible across token changes. */
|
|
105
|
+
const knownGroupModuleNames = new Set<string>();
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Attempt a dynamic import. If it fails (network error, evaluation error,
|
|
109
|
+
* stale signed URL), discard the token tied to this module's chunk and let
|
|
110
|
+
* the next reload refetch a clean manifest. Without this, a single stale
|
|
111
|
+
* token bricks the entire boot path because Promise.all rejects on first
|
|
112
|
+
* failure.
|
|
113
|
+
*/
|
|
114
|
+
async function safeImportGroup(
|
|
115
|
+
baseUrl: string,
|
|
116
|
+
groupName: string,
|
|
117
|
+
group: BuildManifestGroup,
|
|
118
|
+
bust?: string,
|
|
119
|
+
): Promise<void> {
|
|
120
|
+
try {
|
|
121
|
+
await import(/* @vite-ignore */ groupUrl(baseUrl, group, bust));
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.error(
|
|
124
|
+
`[arc] Failed to load group "${groupName}". Clearing token; refresh to retry without it.`,
|
|
125
|
+
err,
|
|
126
|
+
);
|
|
127
|
+
if (typeof localStorage !== "undefined") {
|
|
128
|
+
localStorage.removeItem(`arc:token:${groupName}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
75
132
|
|
|
76
133
|
/**
|
|
77
134
|
* Load all modules from manifest. Each module auto-registers via module().build().
|
|
@@ -83,9 +140,9 @@ export async function loadModules(
|
|
|
83
140
|
const manifest = await fetchManifest(baseUrl, tokens);
|
|
84
141
|
|
|
85
142
|
await Promise.all(
|
|
86
|
-
manifest.
|
|
87
|
-
|
|
88
|
-
return
|
|
143
|
+
Object.entries(manifest.groups).map(([name, group]) => {
|
|
144
|
+
importedGroupHashes.set(name, group.hash);
|
|
145
|
+
return safeImportGroup(baseUrl, name, group);
|
|
89
146
|
}),
|
|
90
147
|
);
|
|
91
148
|
|
|
@@ -104,25 +161,35 @@ export async function syncModules(
|
|
|
104
161
|
): Promise<BuildManifest> {
|
|
105
162
|
const manifest = await fetchManifest(baseUrl, tokens);
|
|
106
163
|
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const toImport = manifest.modules.filter((m) => {
|
|
111
|
-
const prevHash = importedModuleHashes.get(m.name);
|
|
112
|
-
return prevHash !== m.hash;
|
|
113
|
-
});
|
|
164
|
+
const toImport = Object.entries(manifest.groups).filter(
|
|
165
|
+
([name, g]) => importedGroupHashes.get(name) !== g.hash,
|
|
166
|
+
);
|
|
114
167
|
|
|
115
168
|
if (toImport.length > 0) {
|
|
116
169
|
await Promise.all(
|
|
117
|
-
toImport.map((
|
|
118
|
-
|
|
119
|
-
return
|
|
170
|
+
toImport.map(([name, group]) => {
|
|
171
|
+
importedGroupHashes.set(name, group.hash);
|
|
172
|
+
return safeImportGroup(baseUrl, name, group);
|
|
120
173
|
}),
|
|
121
174
|
);
|
|
122
175
|
}
|
|
123
176
|
|
|
124
|
-
//
|
|
125
|
-
|
|
177
|
+
// Compute active set: every registered module EXCEPT protected-group members
|
|
178
|
+
// not present in the current manifest (e.g. after logout). Public modules,
|
|
179
|
+
// never observed in a group, stay visible automatically.
|
|
180
|
+
const currentlyAllowed = new Set<string>();
|
|
181
|
+
for (const group of Object.values(manifest.groups)) {
|
|
182
|
+
for (const name of group.modules) {
|
|
183
|
+
currentlyAllowed.add(name);
|
|
184
|
+
knownGroupModuleNames.add(name);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const active = new Set<string>();
|
|
188
|
+
for (const m of getAllRegisteredModules()) {
|
|
189
|
+
if (knownGroupModuleNames.has(m.name) && !currentlyAllowed.has(m.name)) continue;
|
|
190
|
+
active.add(m.name);
|
|
191
|
+
}
|
|
192
|
+
setActiveModules(active);
|
|
126
193
|
|
|
127
194
|
return manifest;
|
|
128
195
|
}
|
|
@@ -142,8 +209,8 @@ export async function reloadModules(
|
|
|
142
209
|
clearModules();
|
|
143
210
|
|
|
144
211
|
await Promise.all(
|
|
145
|
-
manifest.
|
|
146
|
-
(
|
|
212
|
+
Object.entries(manifest.groups).map(([name, group]) =>
|
|
213
|
+
safeImportGroup(baseUrl, name, group, bust),
|
|
147
214
|
),
|
|
148
215
|
);
|
|
149
216
|
|
package/src/registry.ts
CHANGED
|
@@ -92,35 +92,28 @@ function notifyContext(): void {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/** Set the active Arc context (called by arc() or module().build()).
|
|
95
|
-
* If a context is already set, merges new elements into it
|
|
96
|
-
*
|
|
95
|
+
* If a context is already set, merges new elements into it.
|
|
96
|
+
*
|
|
97
|
+
* CRITICAL: must mutate the existing ArcContext in place (push to .elements,
|
|
98
|
+
* set in .elementMap) — NOT construct a new instance. Mutation is the
|
|
99
|
+
* contract relied on by PlatformApp, which captures `getContext()` into a
|
|
100
|
+
* ref at first render and assumes that reference stays valid as additional
|
|
101
|
+
* modules register later (e.g. token-gated chunks loaded after login). If
|
|
102
|
+
* we returned a fresh context object, the ref would go stale and lookups
|
|
103
|
+
* like `model.context.get("strategyConsultation")` would yield undefined
|
|
104
|
+
* for any module registered after the model was created. */
|
|
97
105
|
export function setContext(ctx: any): void {
|
|
98
106
|
if (currentContext && ctx && currentContext !== ctx) {
|
|
99
|
-
// Merge: append new elements that aren't already present
|
|
100
107
|
const existingNames = new Set(
|
|
101
108
|
currentContext.elements.map((e: any) => e.name ?? e.id ?? e),
|
|
102
109
|
);
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (typeof Ctor === "function" && Ctor !== Object) {
|
|
111
|
-
currentContext = new Ctor(allElements);
|
|
112
|
-
} else {
|
|
113
|
-
// Fallback: create context-like object with get() method
|
|
114
|
-
const elementMap = new Map(
|
|
115
|
-
allElements.map((e: any) => [e.name, e]),
|
|
116
|
-
);
|
|
117
|
-
currentContext = {
|
|
118
|
-
elements: allElements,
|
|
119
|
-
elementMap,
|
|
120
|
-
get(name: string) {
|
|
121
|
-
return elementMap.get(name);
|
|
122
|
-
},
|
|
123
|
-
};
|
|
110
|
+
for (const el of ctx.elements) {
|
|
111
|
+
const key = el.name ?? el.id ?? el;
|
|
112
|
+
if (existingNames.has(key)) continue;
|
|
113
|
+
existingNames.add(key);
|
|
114
|
+
currentContext.elements.push(el);
|
|
115
|
+
if (typeof el.name === "string" && currentContext.elementMap) {
|
|
116
|
+
currentContext.elementMap.set(el.name, el);
|
|
124
117
|
}
|
|
125
118
|
}
|
|
126
119
|
} else {
|
package/src/start-app.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import { PlatformApp } from "./platform-app";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mount the platform app into the DOM. Bundled inside the platform module so
|
|
7
|
+
* that the single Bun.build pass keeps react + react-dom in ONE shared chunk
|
|
8
|
+
* (the one platform lives in). If the host HTML entry imported react/react-dom
|
|
9
|
+
* directly, Bun would copy them into the entry chunk alongside the existing
|
|
10
|
+
* platform-chunk copy — producing two React instances and the classic
|
|
11
|
+
* "Invalid hook call" crash.
|
|
12
|
+
*/
|
|
13
|
+
export function startApp(elementId: string): void {
|
|
14
|
+
const el = document.getElementById(elementId);
|
|
15
|
+
if (!el) throw new Error(`startApp: #${elementId} not found`);
|
|
16
|
+
createRoot(el).render(createElement(PlatformApp));
|
|
17
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -115,13 +115,37 @@ export interface ModuleDescriptor {
|
|
|
115
115
|
readonly url?: string;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
/**
|
|
118
|
+
/** Per-group entry in the build manifest. */
|
|
119
|
+
export interface BuildManifestGroup {
|
|
120
|
+
/** Filename relative to `/browser/` static root. */
|
|
121
|
+
readonly file: string;
|
|
122
|
+
readonly hash: string;
|
|
123
|
+
/** Module names this group registers when loaded. */
|
|
124
|
+
readonly modules: readonly string[];
|
|
125
|
+
/** Signed URL — server fills this for protected groups when filtering per request. */
|
|
126
|
+
readonly url?: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Build manifest written to .arc/platform/manifest.json. */
|
|
119
130
|
export interface BuildManifest {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
readonly
|
|
131
|
+
/**
|
|
132
|
+
* Single bundle with all public-chunk modules + framework bootstrap.
|
|
133
|
+
* Loaded eagerly on every page. Filename is content-addressed.
|
|
134
|
+
*/
|
|
135
|
+
readonly initial: {
|
|
136
|
+
readonly file: string;
|
|
137
|
+
readonly hash: string;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Token-gated groups keyed by `token.name`. One bundle per group containing
|
|
141
|
+
* all that group's modules. Loaded lazily after the user obtains the token.
|
|
142
|
+
*/
|
|
143
|
+
readonly groups: Readonly<Record<string, BuildManifestGroup>>;
|
|
144
|
+
/**
|
|
145
|
+
* Shared chunks auto-emitted by Bun.build splitting (workspace ctx,
|
|
146
|
+
* framework, common deps). Public, unsigned (filename is content-hashed).
|
|
147
|
+
*/
|
|
148
|
+
readonly sharedChunks: readonly string[];
|
|
125
149
|
/** sha256 hex over styles.css (+ theme.css if present). */
|
|
126
150
|
readonly stylesHash: string;
|
|
127
151
|
readonly buildTime: string;
|
package/src/index.server.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
// Arc Platform — server entry (bun/node runtime)
|
|
2
|
-
//
|
|
3
|
-
// Subset of the public API that is safe to import in a server context:
|
|
4
|
-
// pure data + functions, no React, no DOM, no JSX runtime imports at top
|
|
5
|
-
// level. Used by:
|
|
6
|
-
// - access-extractor subprocess (PRE-bundle discovery of protectedBy rules)
|
|
7
|
-
// - server bundles per context package (resolve @arcote.tech/platform
|
|
8
|
-
// through Bun's `bun` export condition)
|
|
9
|
-
//
|
|
10
|
-
// Shares the same `registry` module instance with the browser entry — both
|
|
11
|
-
// reference the SAME ./registry file, so `module().build()` in user code
|
|
12
|
-
// and `getAllModuleAccess()` in extractor observe a single source of truth.
|
|
13
|
-
|
|
14
|
-
// Module system
|
|
15
|
-
export {
|
|
16
|
-
module,
|
|
17
|
-
page,
|
|
18
|
-
wrapper,
|
|
19
|
-
slot,
|
|
20
|
-
contextElement,
|
|
21
|
-
contextFragments,
|
|
22
|
-
} from "./arc";
|
|
23
|
-
/** @deprecated Use module() instead */
|
|
24
|
-
export { arc } from "./arc";
|
|
25
|
-
|
|
26
|
-
// Registry
|
|
27
|
-
export {
|
|
28
|
-
clearModules,
|
|
29
|
-
clearRegistry,
|
|
30
|
-
forceRegisterModule,
|
|
31
|
-
getAllFragments,
|
|
32
|
-
getAllModuleAccess,
|
|
33
|
-
getAllModules,
|
|
34
|
-
getAllRegisteredModules,
|
|
35
|
-
getContext,
|
|
36
|
-
getModuleAccess,
|
|
37
|
-
getContextElementFragments,
|
|
38
|
-
getDefaultLayout,
|
|
39
|
-
getModule,
|
|
40
|
-
getPageByPath,
|
|
41
|
-
getPlatformConfig,
|
|
42
|
-
getPageFragments,
|
|
43
|
-
getSlotFragments,
|
|
44
|
-
getVariantOverrides,
|
|
45
|
-
getWrapperFragments,
|
|
46
|
-
registerModule,
|
|
47
|
-
setContext,
|
|
48
|
-
setDefaultLayout,
|
|
49
|
-
setPlatformConfig,
|
|
50
|
-
setVariantOverrides,
|
|
51
|
-
subscribe,
|
|
52
|
-
subscribeContext,
|
|
53
|
-
unregisterModule,
|
|
54
|
-
} from "./registry";
|
|
55
|
-
|
|
56
|
-
export type { PlatformConfig, PlatformStorageConfig } from "./registry";
|
|
57
|
-
|
|
58
|
-
// Types
|
|
59
|
-
export type {
|
|
60
|
-
ArcComponent,
|
|
61
|
-
ArcFactory,
|
|
62
|
-
ArcFactoryMethods,
|
|
63
|
-
ArcFragment,
|
|
64
|
-
ArcLayoutComponent,
|
|
65
|
-
ArcModule,
|
|
66
|
-
BuildManifest,
|
|
67
|
-
BuiltModule,
|
|
68
|
-
ContextElementFragment,
|
|
69
|
-
ModuleAccess,
|
|
70
|
-
ModuleAccessRule,
|
|
71
|
-
ModuleDescriptor,
|
|
72
|
-
ExtractPages,
|
|
73
|
-
FragmentOrdering,
|
|
74
|
-
PageFragment,
|
|
75
|
-
PageOptions,
|
|
76
|
-
PageShellProps,
|
|
77
|
-
PublicArcFragment,
|
|
78
|
-
PublicPaths,
|
|
79
|
-
SlotFragment,
|
|
80
|
-
SlotId,
|
|
81
|
-
SlotOptions,
|
|
82
|
-
WrapperFragment,
|
|
83
|
-
WrapperOptions,
|
|
84
|
-
} from "./types";
|