@stealthscale/sdk-host 0.1.0 → 0.3.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/README.md +274 -59
- package/dist/app.d.ts +79 -0
- package/dist/app.js +3 -0
- package/dist/{create-routes-CkgZV-dU.js → create-routes-D4iCBYZe.js} +1244 -468
- package/dist/cyclonedx/bom.json +1 -1
- package/dist/deploy.d.ts +98 -0
- package/dist/deploy.js +254 -0
- package/dist/document-CeEtZo2E.js +38 -0
- package/dist/frame-alSrG57-.js +2499 -0
- package/dist/index.d.ts +36 -97
- package/dist/index.js +2 -2
- package/dist/options-DBsArZKT.d.ts +91 -0
- package/dist/{page-7ScZC_LI.js → page-DzeycJUh.js} +2 -2
- package/dist/releases.d.ts +131 -0
- package/dist/releases.js +404 -0
- package/dist/render-CGRJqVed.js +238 -0
- package/dist/render-DRndY-TD.d.ts +54 -0
- package/dist/standalone/app.d.ts +3 -0
- package/dist/standalone/app.js +214 -343
- package/dist/standalone.js +1 -1
- package/locales/en/host.json +46 -0
- package/package.json +59 -49
package/README.md
CHANGED
|
@@ -12,51 +12,218 @@ A product creates one host per page in the browser and one per request on a serv
|
|
|
12
12
|
pnpm add @stealthscale/sdk-host
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
The package peers on `react`, `react-dom`, `@tanstack/react-router-ssr-query`,
|
|
15
|
+
The package peers on `react`, `react-dom`, `lucide-react`, `@tanstack/react-router-ssr-query`,
|
|
16
16
|
`@stealthscale/sdk-plugin`, the providers it reads and the component packages it renders:
|
|
17
17
|
|
|
18
18
|
- `provider-data`, `provider-router`, `provider-i18n`, `provider-hotkeys`, `provider-form`,
|
|
19
|
-
`settings` and `hooks`
|
|
20
|
-
- `component-
|
|
21
|
-
`component-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
`./
|
|
25
|
-
|
|
19
|
+
`provider-viewport`, `settings` and `hooks`
|
|
20
|
+
- `component-a11y`, `component-actions`, `component-feedback`, `component-forms`,
|
|
21
|
+
`component-layout`, `component-media`, `component-modals`, `component-navigation`,
|
|
22
|
+
`component-screen` and `component-typography`
|
|
23
|
+
|
|
24
|
+
The `./openfeature` entry peers on `@openfeature/web-sdk` as well, which is optional. The `./app`,
|
|
25
|
+
`./releases` and `./standalone/app` entries peer on `provider-color-mode`, `provider-locale` and
|
|
26
|
+
`provider-shell`, which are optional for the other entries. The `./deploy` entry imports
|
|
27
|
+
`@stealthscale/sdk-core` alone. The host's words are in the `host` catalogue namespace,
|
|
26
28
|
`locales/<language>/host.json`.
|
|
27
29
|
|
|
28
|
-
##
|
|
30
|
+
## Render a product
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
`renderProduct` from `@stealthscale/sdk-host/app` renders a product from its definition. It creates
|
|
33
|
+
the host over the product's sources and the router with the host's frame at its root, and renders
|
|
34
|
+
both inside the product's `Shell`.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { catalogues } from "virtual:i18n";
|
|
31
38
|
import { product } from "virtual:product";
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
import { renderProduct } from "@stealthscale/sdk-host/app";
|
|
41
|
+
|
|
42
|
+
await renderProduct({ catalogues, product, themes: ["ink", "pine"] });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
| Option | Where left out | States |
|
|
46
|
+
| ------------ | ------------------------------ | ----------------------------------------------------- |
|
|
47
|
+
| `product` | Required | The resolved product, from `virtual:product` |
|
|
48
|
+
| `catalogues` | Every key renders as itself | The catalogues, from `virtual:i18n` |
|
|
49
|
+
| `themes` | The application's one theme | The themes the product offers, the first until a pick |
|
|
50
|
+
| `glyphs` | Lucide's icons | The glyphs the settings sections' forms render |
|
|
51
|
+
| `basepath` | The site's root | The base the product is served at, such as `/docs/` |
|
|
52
|
+
| `element` | The element with the id `root` | The element the product renders into |
|
|
53
|
+
| `refused` | None | The releases the page refused before it rendered |
|
|
54
|
+
|
|
55
|
+
- The definition's `sources` imports the module that exports the host's `session`, `access`,
|
|
56
|
+
`flags`, `data` and `store`. Without a session nobody is signed in, and without a store a person's
|
|
57
|
+
choices are kept in the page's local storage.
|
|
58
|
+
- The definition's `frame` states the frame's layout and options, or imports the product's own
|
|
59
|
+
frame, which the router then renders at its root.
|
|
60
|
+
- `renderProduct` waits for `host.ready()`, renders into a React root that takes `onCaughtError`,
|
|
61
|
+
and resolves with that root.
|
|
62
|
+
- The site root opens the first section the person may open. Without one it sends a person who is
|
|
63
|
+
not signed in to the sign-in route, and renders the not-found page otherwise. A plugin's route at
|
|
64
|
+
`/` takes the site root's place.
|
|
65
|
+
- The host reports each entry of `refused` as `plugin-refused` when the page starts.
|
|
66
|
+
|
|
67
|
+
## Load releases at run time
|
|
68
|
+
|
|
69
|
+
`renderDeployed` from `@stealthscale/sdk-host/releases` loads the plugins that the page's deployment
|
|
70
|
+
lists, then renders the product with them through `renderProduct`. The page that
|
|
71
|
+
`product.app({ runtime: true })` of `@stealthscale/vite-config-product` generates calls it.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { catalogues } from "virtual:i18n";
|
|
75
|
+
import { definition, product } from "virtual:product";
|
|
76
|
+
|
|
77
|
+
import { renderDeployed } from "@stealthscale/sdk-host/releases";
|
|
78
|
+
|
|
79
|
+
await renderDeployed({ catalogues, definition, product, themes: ["ink"] });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`renderDeployed` takes the options of `renderProduct` except `refused`, and `definition`, the
|
|
83
|
+
product's definition from `virtual:product`.
|
|
84
|
+
|
|
85
|
+
- It reads the deployment from the document's `script` element with the id `stealth-deployment`. A
|
|
86
|
+
document without that element renders the build's product.
|
|
87
|
+
- It registers the page's preloader with `registerPreloader` of `@stealthscale/sdk-core`. The
|
|
88
|
+
preloader runs when a release imports one of its modules lazily. It appends a module preload to
|
|
89
|
+
the document for each module that the imported module needs.
|
|
90
|
+
- Before any request, it refuses a release whose requirements the page's versions do not meet. It
|
|
91
|
+
also refuses a release without a catalogue in the page's fallback language, and a release whose id
|
|
92
|
+
names a namespace of the page's catalogues.
|
|
93
|
+
- It reads each release's words in the fallback language from the deployment and imports the
|
|
94
|
+
release's `manifest.js`. When the deployment lists a release without words, the loader fetches
|
|
95
|
+
them with their integrity while the page imports the manifest. The page fetches the release's
|
|
96
|
+
other languages when i18next requests them.
|
|
97
|
+
- It adds the releases to the definition's plugins one at a time and resolves each step. A release
|
|
98
|
+
whose step reports a problem is refused, and the other releases render.
|
|
99
|
+
- The host reports each refused release as `plugin-refused`. Its `reason` is `catalogue`,
|
|
100
|
+
`deployment`, `manifest`, `requires` or `resolution`, and its `detail` names the fault.
|
|
101
|
+
|
|
102
|
+
The loader is an entry of its own, so a product that does not load a release does not download it.
|
|
103
|
+
|
|
104
|
+
A page that a server rendered loads its releases with `deployedProduct` from the same entry before
|
|
105
|
+
it hydrates. Create the host with the `product`, the `catalogues` and the `refused` releases that it
|
|
106
|
+
returns.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { deployedProduct } from "@stealthscale/sdk-host/releases";
|
|
110
|
+
|
|
111
|
+
const deployed = await deployedProduct({ catalogues, definition, document, product });
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- The product's `releases` contains the ids of the releases in the document. The list includes the
|
|
115
|
+
refused releases, and it is empty where the page cannot read the deployment.
|
|
116
|
+
|
|
117
|
+
## Write a deployment's document
|
|
118
|
+
|
|
119
|
+
`deployDocument` from `@stealthscale/sdk-host/deploy` writes the parts of a product's document that
|
|
120
|
+
depend on its deployment. `documentWith` writes them into the document of the product's build.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { deployDocument, documentWith } from "@stealthscale/sdk-host/deploy";
|
|
124
|
+
|
|
125
|
+
const deployed = await deployDocument(deployment, provided);
|
|
126
|
+
const html = documentWith(built, deployed);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- `deployment` lists each release by the URL of its directory, the integrity of its `plugin.json`
|
|
130
|
+
and the options the product installs it with. `provided` is the build's `.product/provided.json`.
|
|
131
|
+
- The step fetches each `plugin.json`, checks its digest against the deployment and checks each
|
|
132
|
+
member. It leaves out a release that fails, and lists it in `refused` with its base and the fault.
|
|
133
|
+
- For each release whose requirements the provided versions meet, the step fetches the release's
|
|
134
|
+
catalogue in the page's fallback language. It checks the catalogue's digest against `plugin.json`
|
|
135
|
+
and writes the words into the deployment. When the deployment lists a release without words, the
|
|
136
|
+
page fetches the words itself.
|
|
137
|
+
- `importMap` maps the provided specifiers and lists the integrity of every file of every release.
|
|
138
|
+
`csp` is a `script-src` policy with the page's origin, each release's origin and the import map's
|
|
139
|
+
digest. `deployment` is the deployment that the page reads. It contains each release's description
|
|
140
|
+
and words, and the modules of the provided specifiers that the releases import.
|
|
141
|
+
- `preloads` lists each module once. It contains the `manifest.js` of each loaded release, the
|
|
142
|
+
release modules and provided entries that the manifest imports, and the chunks that those entries
|
|
143
|
+
import. The preload of a release's module has the module's integrity.
|
|
144
|
+
- `releases` contains the ids of the releases in the document. A server passes them to the host as
|
|
145
|
+
the product's `releases` (see "Server rendering").
|
|
146
|
+
- `documentWith` puts the policy as a `meta` element, the import map, a module preload per entry of
|
|
147
|
+
`preloads` and the deployment's `script` element in place of the build's import map. It throws for
|
|
148
|
+
a document without an import map.
|
|
149
|
+
- A server that writes its own document writes `importMap`, the preloads and `deployment` into it,
|
|
150
|
+
and sends `csp` as a header. `DEPLOYMENT_ELEMENT` is the id of the deployment's element.
|
|
151
|
+
- The step uses `fetch` and Web Crypto alone, so it runs in Node and in any runtime that has both.
|
|
152
|
+
`fetching` replaces `fetch`.
|
|
153
|
+
|
|
154
|
+
## The product's frame
|
|
155
|
+
|
|
156
|
+
`ProductFrame` renders the frame the product's definition states, from data alone. `renderProduct`
|
|
157
|
+
and `createProductRouter` render it at the router's root.
|
|
158
|
+
|
|
159
|
+
| Layout | Main menu | Sidebar |
|
|
160
|
+
| ------------------- | -------------------------------------------- | --------------------------------------------- |
|
|
161
|
+
| `sidebar` (default) | The sidebar's first block | The main menu, then every plugin's navigation |
|
|
162
|
+
| `stacked` | Links in the bar | None |
|
|
163
|
+
| `split` | Links in the bar | The current section's navigation |
|
|
164
|
+
| `rail` | Icons in a rail, and a dock on a narrow page | The current section's navigation |
|
|
165
|
+
|
|
166
|
+
- The sections are the main menu's top-level entries. The current section contains the address on
|
|
167
|
+
screen, or belongs to the plugin of the page on screen, so a plugin's settings pages keep its
|
|
168
|
+
section current.
|
|
169
|
+
- A section's navigation is a block per menu of the section's plugin, the plugin's extensions in the
|
|
170
|
+
`navigation` region, and a block of its settings pages. A menu nests an entry under the entry of
|
|
171
|
+
its route's parent.
|
|
172
|
+
- An entry with an icon renders lucide's icon from the entry's data. A top-level entry without one
|
|
173
|
+
renders the initials of its text.
|
|
174
|
+
- A region nobody fills renders no part of the shell. The `aside` and `panel` regions render in
|
|
175
|
+
panels at the end side, each a complementary landmark named in the host's words.
|
|
176
|
+
- The bar renders the sidebar's opener, the brand, the `header` region, a search that opens the
|
|
177
|
+
palette, the switchers and the panel's opener in one toolbar. The `userMenu` region renders after
|
|
178
|
+
the toolbar, or at the sidebar's foot.
|
|
179
|
+
- The rail, the sidebar, the aside and the panel keep a person's open state and width under
|
|
180
|
+
`stealth.<productId>.<subject>.frame.<panel>`. A panel over a narrow page starts closed whatever
|
|
181
|
+
the stored state.
|
|
182
|
+
- A skip link leads to the page, and one leads to each panel beside the page.
|
|
183
|
+
- A part with a `mode` renders in that colour mode, such as a dark sidebar on a light page.
|
|
184
|
+
|
|
185
|
+
## Compose the application yourself
|
|
186
|
+
|
|
187
|
+
A product that renders on a server creates the host and the router itself. `createProductRouter`
|
|
188
|
+
from `@stealthscale/sdk-host/app` returns the router with the frame and the site root:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
const router = createProductRouter(host, product, {
|
|
192
|
+
history: createMemoryHistory({ initialEntries: [request.url] }),
|
|
193
|
+
shell: Document,
|
|
41
194
|
});
|
|
42
|
-
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
| Option | Where left out | States |
|
|
198
|
+
| ---------- | ---------------- | ------------------------------------------------------------------------------------- |
|
|
199
|
+
| `basepath` | The site's root | The base the product is served at |
|
|
200
|
+
| `frame` | The host's frame | The root route's component, for a product with a frame of its own |
|
|
201
|
+
| `history` | The browser's | The history the router reads its address from |
|
|
202
|
+
| `shell` | No document | The document around the frame, the root route's `shellComponent`, for a server render |
|
|
203
|
+
|
|
204
|
+
`GLYPHS` from the same entry contains the form glyphs `renderProduct` passes to the host, for a
|
|
205
|
+
product that calls `createHost` itself.
|
|
43
206
|
|
|
207
|
+
A product that creates the host itself renders the same pieces `renderProduct` renders:
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
import { product } from "virtual:product";
|
|
211
|
+
|
|
212
|
+
const store = localStore();
|
|
44
213
|
const host = createHost({
|
|
45
214
|
data: { changes: changesSubscription, transport: gatewayTransport(gateway) },
|
|
46
215
|
flags: openFeatureFlags(),
|
|
216
|
+
glyphs: GLYPHS,
|
|
47
217
|
product,
|
|
48
218
|
session: identitySession,
|
|
49
|
-
store
|
|
50
|
-
});
|
|
51
|
-
const router = createRouter({
|
|
52
|
-
...routerOptions({ data: host.data, host, routes: routeMap(tree) }),
|
|
53
|
-
routeTree: tree,
|
|
219
|
+
store,
|
|
54
220
|
});
|
|
221
|
+
const router = createProductRouter(host, product);
|
|
55
222
|
|
|
56
223
|
await host.ready();
|
|
57
224
|
|
|
58
|
-
createRoot(element).render(
|
|
59
|
-
<Shell app={product.productId} catalogues={catalogues}>
|
|
225
|
+
createRoot(element, { onCaughtError }).render(
|
|
226
|
+
<Shell app={product.productId} catalogues={catalogues} locales={product.locales} store={store}>
|
|
60
227
|
<HostProvider host={host} router={router}>
|
|
61
228
|
<RouterProvider router={router} />
|
|
62
229
|
</HostProvider>
|
|
@@ -64,15 +231,19 @@ createRoot(element).render(
|
|
|
64
231
|
);
|
|
65
232
|
```
|
|
66
233
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
234
|
+
A frame of the product's own renders `HostRoot` around its shell and `HostContent` where the page
|
|
235
|
+
goes, and the definition's `frame` names it, or `createProductRouter` takes it as `frame`.
|
|
236
|
+
|
|
237
|
+
| Piece | Created | Renders or keeps |
|
|
238
|
+
| ---------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
239
|
+
| `createHostRoutes` | Once per route tree | The plugins' routes, the settings route, its index and a route per settings page |
|
|
240
|
+
| `createHost` | Once per page, once per request | The stores, the event bus, the data client, the toaster, the quarantine, the reports |
|
|
241
|
+
| `HostRoot` | The frame's outermost part | The `layout` region around the frame, the `overlay` region, the palette and the toasts |
|
|
242
|
+
| `HostNotFound` | The root's not-found component | The not-found page, with the reason a plugin's page is not found |
|
|
243
|
+
| `HostProvider` | Once per render tree | The host's context, `DataProvider` and the `root` region around the product's tree |
|
|
244
|
+
| `HostContent` | In the frame, where the page goes | The `content` region around the matched page |
|
|
245
|
+
| `setupHostIntegration` | Once per router | The host's state in a server render, and the browser's host started from it |
|
|
246
|
+
| `onCaughtError` | Passed to `createRoot` or `hydrateRoot` | Every error a boundary catches, written with `console.error`, except the router's not-found |
|
|
76
247
|
|
|
77
248
|
- Build the tree once. A plugin that turns off makes its routes not found, and the router keeps the
|
|
78
249
|
tree it was built from.
|
|
@@ -84,19 +255,20 @@ createRoot(element).render(
|
|
|
84
255
|
|
|
85
256
|
## Create a host
|
|
86
257
|
|
|
87
|
-
| Option | Where left out | States
|
|
88
|
-
| ----------------- | ------------------------------------------------- |
|
|
89
|
-
| `product` | Required | The resolved product with its plugins' manifests, from `virtual:product`
|
|
90
|
-
| `session` | Required | The `SessionSource` the session comes from
|
|
91
|
-
| `store` | Required | The `SettingStore` a person's switches, settings and placements are kept in
|
|
92
|
-
| `access` | Every decision is the tenant-wide one | The `AccessSource` of decisions on single resources
|
|
93
|
-
| `data` | A transport over the contracts' samples | The transport and the changes subscription the data client runs on
|
|
94
|
-
| `flags` | Every flag has the product's value or its default | The `FlagSource` of flag values per session
|
|
95
|
-
| `flagsTimeout` | 1,000 ms | How long `ready` waits for the flag source's first `identify`
|
|
96
|
-
| `glyphs` | No glyphs | The glyphs the settings sections' forms render, such as a checked box's mark
|
|
97
|
-
| `overrides` | On outside a production build | Whether the tab's flag overrides are read and written
|
|
98
|
-
| `quarantineAfter` | 3 | Failed renders in a row after which a route or an extension is quarantined
|
|
99
|
-
| `
|
|
258
|
+
| Option | Where left out | States |
|
|
259
|
+
| ----------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
260
|
+
| `product` | Required | The resolved product with its plugins' manifests, from `virtual:product` |
|
|
261
|
+
| `session` | Required | The `SessionSource` the session comes from |
|
|
262
|
+
| `store` | Required | The `SettingStore` a person's switches, settings and placements are kept in |
|
|
263
|
+
| `access` | Every decision is the tenant-wide one | The `AccessSource` of decisions on single resources |
|
|
264
|
+
| `data` | A transport over the contracts' samples | The transport and the changes subscription the data client runs on |
|
|
265
|
+
| `flags` | Every flag has the product's value or its default | The `FlagSource` of flag values per session |
|
|
266
|
+
| `flagsTimeout` | 1,000 ms | How long `ready` waits for the flag source's first `identify` |
|
|
267
|
+
| `glyphs` | No glyphs | The glyphs the settings sections' forms render, such as a checked box's mark |
|
|
268
|
+
| `overrides` | On outside a production build | Whether the tab's flag overrides are read and written, as `useFlagActions().overridable` reports |
|
|
269
|
+
| `quarantineAfter` | 3 | Failed renders in a row after which a route or an extension is quarantined |
|
|
270
|
+
| `refused` | None | The releases the page refused, which the host reports as `plugin-refused` when it is created |
|
|
271
|
+
| `report` | The console | The function that receives every `HostReport` entry |
|
|
100
272
|
|
|
101
273
|
- `createHost` throws where a manifest lacks code for a declared name, and names the plugin and the
|
|
102
274
|
name.
|
|
@@ -114,25 +286,44 @@ in one `compileRoutes` call:
|
|
|
114
286
|
|
|
115
287
|
- A plugin route loads its page with the modules of the plugins in its `loads`, loads its declared
|
|
116
288
|
data through the data client, and validates its search with its contract's validator.
|
|
289
|
+
- Where a condition requires a signed-in person and nobody is signed in, the route redirects to the
|
|
290
|
+
product's sign-in route, with the entered address as the `redirect` search. The redirect comes
|
|
291
|
+
before the plugin's availability, which can read the entitlements of a session that does not exist
|
|
292
|
+
yet.
|
|
117
293
|
- Its condition is evaluated against the host in the router's context. A route whose plugin is off,
|
|
118
294
|
stopped by its kill switch or quarantined is not found, with `NotFoundData` that states the
|
|
119
295
|
reason: `PluginUnavailable` or `PageQuarantined`.
|
|
120
|
-
-
|
|
121
|
-
|
|
296
|
+
- Each plugin route renders the root's `notFoundComponent` in its own place, inside the frame. A
|
|
297
|
+
page that throws the router's `notFound()` while it renders, for parameters no record matches,
|
|
298
|
+
renders it there and counts no failure. React reports every error a boundary catches, so a root
|
|
299
|
+
without `onCaughtError` writes that not-found to the console.
|
|
122
300
|
- A page that throws renders its manifest's fallback, or the host's error page. Each error counts
|
|
123
301
|
once towards the route's quarantine.
|
|
302
|
+
- A page whose module failed to import renders the host's error page with a button that reloads the
|
|
303
|
+
page, in place of its fallback, and does not count towards the quarantine.
|
|
124
304
|
|
|
125
305
|
`HostNotFound` renders the not-found page. For a plugin a switch turned off it offers a button that
|
|
126
306
|
turns the plugin on, and for a quarantined page a retry. Where the page replaced the page a person
|
|
127
|
-
was on, focus moves to its heading.
|
|
307
|
+
was on and focus was in that page, focus moves to its heading. A control outside the page keeps
|
|
308
|
+
focus, and a server-rendered not-found page takes none while it hydrates.
|
|
309
|
+
|
|
310
|
+
Where the page's document contains a deployment, a plain not-found page renders the loading page on
|
|
311
|
+
a server and while it hydrates. Once the page hydrated, the host loads the address again with the
|
|
312
|
+
router. A release's page that matches the address then renders without taking focus, because it is
|
|
313
|
+
the first page of the load. The not-found page renders where the routes do not match the address.
|
|
128
314
|
|
|
129
|
-
## The frame
|
|
315
|
+
## The parts of every frame
|
|
130
316
|
|
|
131
317
|
- `HostProvider` renders the host's context, `DataProvider` and the `root` region around the
|
|
132
318
|
product's tree. It invalidates the router after a change a route condition reads, emits
|
|
133
319
|
`host/navigated`, and titles the document with the product's name.
|
|
134
320
|
- `HostRoot` renders the frame inside the `layout` region, then the `overlay` region with the
|
|
135
321
|
command palette and the toast region, and binds the commands' keys. `Mod+K` opens the palette.
|
|
322
|
+
- The palette lists each command a key could run under its plugin's name, and the main menu's pages
|
|
323
|
+
under `Go to`. While a query matches them, it also lists the entries of every other menu and the
|
|
324
|
+
pages of every plugin's page index. A settings page is listed under the settings menu's heading,
|
|
325
|
+
every other row under its plugin's name. The palette imports the indexes the first time it opens,
|
|
326
|
+
and reports an index that fails as `index-failed`.
|
|
136
327
|
- `HostContent` renders the matched page inside the `content` region. Render it in the frame where
|
|
137
328
|
the page belongs.
|
|
138
329
|
- `HostProvider` and `HostRoot` each render a last boundary. An error outside every plugin renders a
|
|
@@ -154,13 +345,15 @@ Pass `glyphs` to `createHost`, or the sections' forms render no marks.
|
|
|
154
345
|
## A chunk that no longer exists
|
|
155
346
|
|
|
156
347
|
A page loaded before a deployment requests chunks that the deployment replaced. The host reloads the
|
|
157
|
-
page once per
|
|
348
|
+
page once per key. The key is the product's `version`, followed by `+` and the deployment's id on a
|
|
349
|
+
page that loads releases:
|
|
158
350
|
|
|
159
351
|
- A plugin's module that fails to import starts the recovery, and so does Vite's `vite:preloadError`
|
|
160
352
|
while `HostProvider` is mounted. The import does not settle, so no error renders while the page
|
|
161
353
|
reloads.
|
|
162
|
-
- The
|
|
163
|
-
|
|
354
|
+
- The key is recorded under `stealth.<productId>.reloaded` in session storage. A second failure for
|
|
355
|
+
the same key rejects with `PluginImportError`, whose `cause` is the import's error, and the
|
|
356
|
+
route's error component renders it without a second reload.
|
|
164
357
|
- A server, and a page whose storage the browser refuses, render the error without a reload.
|
|
165
358
|
|
|
166
359
|
Each plugin's first import is measured as `stealth:load:<plugin id>`.
|
|
@@ -181,10 +374,26 @@ setupHostIntegration({ host, router });
|
|
|
181
374
|
server's flags until `HostProvider` mounts, and takes the decisions where the browser's subject is
|
|
182
375
|
the server's, so the first render matches the server's.
|
|
183
376
|
- `HostProvider`'s first effect applies the tab's overrides and the browser's flag source.
|
|
377
|
+
- A boundary that hydrates after that effect, such as the router's matches once a lazy page loads,
|
|
378
|
+
reads the server's flags and the availability they give as React's server snapshot. It hydrates
|
|
379
|
+
against the server's HTML, and React renders it again with the browser's values.
|
|
184
380
|
|
|
185
381
|
Create a host per request on a server, with `constantSession` and a setting store over the request's
|
|
186
382
|
cookies.
|
|
187
383
|
|
|
384
|
+
A product that loads releases renders their pages in the browser alone. Its server passes the
|
|
385
|
+
`releases` that `deployDocument` returned as the product's `releases`, and the page loads them with
|
|
386
|
+
`deployedProduct` before it hydrates:
|
|
387
|
+
|
|
388
|
+
- Inside a server snapshot the availability store withholds the plugin of each release. Each such
|
|
389
|
+
plugin is off with the reason `unavailable`. The menus, the settings sections and
|
|
390
|
+
`usePluginStatuses` read the store, so the frame hydrates without the releases' entries, sections
|
|
391
|
+
and rows. React renders them once the page hydrated.
|
|
392
|
+
- The server sends `csp` as a header with a nonce of the response in `script-src`, and sets the
|
|
393
|
+
nonce as the router's `ssr.nonce`. It writes the import map as the first child of the document's
|
|
394
|
+
head, because React writes every `link` of the head before the head's inline scripts.
|
|
395
|
+
- `examples/app-plugins` serves the notes release this way.
|
|
396
|
+
|
|
188
397
|
## A plugin on its own
|
|
189
398
|
|
|
190
399
|
`@stealthscale/sdk-host/standalone` builds the product a plugin runs in on its own, for its tests
|
|
@@ -223,16 +432,21 @@ into the element with the id `root`, or `element`, and resolves with its React r
|
|
|
223
432
|
| `themes` | The application's one theme | The themes the page offers |
|
|
224
433
|
|
|
225
434
|
The page opens at the plugin's first menu entry, else at the first route a sample fills. It renders
|
|
226
|
-
|
|
227
|
-
panel at the bottom-end corner, with one group of controls each:
|
|
435
|
+
the product's frame in the `sidebar` layout, and the development panel over it. The panel is a
|
|
436
|
+
floating panel at the bottom-end corner, with one group of controls each:
|
|
228
437
|
|
|
229
438
|
- Page: every route a sample fills, by its id.
|
|
230
|
-
- Session: signed in, and a switch per declared permission and entitlement.
|
|
439
|
+
- Session: signed in, and a switch per declared permission and entitlement. Signing out writes
|
|
440
|
+
`NOBODY`, whose permission and entitlement lists are empty. The permission and entitlement
|
|
441
|
+
switches are disabled until somebody signs in. Signing in again restores the session they showed
|
|
442
|
+
before.
|
|
231
443
|
- Checks on one resource: Allow, Deny and Pending.
|
|
232
444
|
- Feature flags: a switch per boolean flag and a picker per experiment, through `flags.override`.
|
|
445
|
+
The group is left out where no plugin declares a flag.
|
|
233
446
|
- Plugin: the plugin's switch, and its kill switch.
|
|
234
447
|
- Operations: a picker per query and mutation: the sample, the sample after 2 seconds, or a refusal
|
|
235
|
-
of each kind a `DataError` states. A query's pick loads the page's data again.
|
|
448
|
+
of each kind a `DataError` states. A query's pick loads the page's data again. The group is left
|
|
449
|
+
out where the product declares neither a query nor a mutation.
|
|
236
450
|
- Display: the language, the theme and the color mode.
|
|
237
451
|
|
|
238
452
|
## OpenFeature
|
|
@@ -242,6 +456,7 @@ the OpenFeature client of a domain, `stealth.host` by default.
|
|
|
242
456
|
|
|
243
457
|
```ts
|
|
244
458
|
import { OpenFeature } from "@openfeature/web-sdk";
|
|
459
|
+
|
|
245
460
|
import { openFeatureFlags } from "@stealthscale/sdk-host/openfeature";
|
|
246
461
|
|
|
247
462
|
await OpenFeature.setProviderAndWait("stealth.host", provider);
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { n as renderProduct, t as ProductRenderOptions } from "./render-DRndY-TD.js";
|
|
2
|
+
import { t as Host } from "./options-DBsArZKT.js";
|
|
3
|
+
import { Product, ResolvedProduct } from "@stealthscale/sdk-core";
|
|
4
|
+
import { HostStores } from "@stealthscale/sdk-plugin";
|
|
5
|
+
import { AnyRoute, AnyRouter, RouterHistory } from "@stealthscale/provider-router";
|
|
6
|
+
import { FunctionComponent, ReactElement, ReactNode } from "react";
|
|
7
|
+
import { FormGlyphs } from "@stealthscale/component-forms/form";
|
|
8
|
+
//#region src/app/frame.d.ts
|
|
9
|
+
/**
|
|
10
|
+
* Renders the shell inside the host's root, around the page the router matched.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* The frame reads the resolved frame, the main menu's sections and the filled regions, so it
|
|
14
|
+
* renders a part only while the layout calls for it and a plugin fills it. The skip links come
|
|
15
|
+
* first, then the bar across the top, the body, and the bars at the foot. The `stacked` and
|
|
16
|
+
* `split` layouts list the sections in the bar.
|
|
17
|
+
* @returns The host's root around the shell.
|
|
18
|
+
* @throws {@link Error} Where the product renders its own frame.
|
|
19
|
+
*/
|
|
20
|
+
export declare function ProductFrame(): ReactElement;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/app/glyphs.d.ts
|
|
23
|
+
/**
|
|
24
|
+
* The marks of the settings forms: a check in a checked box and beside a picked option, a select's
|
|
25
|
+
* chevron, a number field's steppers, and the mark before an error.
|
|
26
|
+
*/
|
|
27
|
+
export declare const GLYPHS: FormGlyphs;
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/app/tree.d.ts
|
|
30
|
+
/**
|
|
31
|
+
* Describes the props of the document around the frame.
|
|
32
|
+
*/
|
|
33
|
+
interface ProductShellProps {
|
|
34
|
+
/**
|
|
35
|
+
* The frame.
|
|
36
|
+
*/
|
|
37
|
+
readonly children: ReactNode;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Renders the document around the frame, for a page a server renders and its hydration.
|
|
41
|
+
*/
|
|
42
|
+
type ProductShell = (props: ProductShellProps) => ReactNode;
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/app/router.d.ts
|
|
45
|
+
/**
|
|
46
|
+
* Lists what a product's router is created with beside the host and the product.
|
|
47
|
+
*/
|
|
48
|
+
export interface ProductRouterOptions {
|
|
49
|
+
/**
|
|
50
|
+
* The base the product is served at, such as `/docs/`. The site's root where left out.
|
|
51
|
+
*/
|
|
52
|
+
readonly basepath?: string | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* The component of the root route, for a product that renders its own frame. The host's frame
|
|
55
|
+
* where left out.
|
|
56
|
+
*/
|
|
57
|
+
readonly frame?: FunctionComponent | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* The history the router reads its address from, such as a memory history on a server. The
|
|
60
|
+
* browser's where left out.
|
|
61
|
+
*/
|
|
62
|
+
readonly history?: RouterHistory | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* The document around the frame, the root route's `shellComponent`, for a page a server renders
|
|
65
|
+
* and its hydration. The frame renders alone where left out.
|
|
66
|
+
*/
|
|
67
|
+
readonly shell?: ProductShell | undefined;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Returns a product's router: the frame at the root, the site root, and the plugins' routes, with
|
|
71
|
+
* the host and its data client in the router's context.
|
|
72
|
+
*
|
|
73
|
+
* @param host - The host the router's context contains.
|
|
74
|
+
* @param product - The product, whose routes the host compiles.
|
|
75
|
+
* @param options - The base path, the frame, the history and the document around the frame.
|
|
76
|
+
*/
|
|
77
|
+
export declare function createProductRouter(host: Host, product: Product, options?: ProductRouterOptions): AnyRouter;
|
|
78
|
+
//#endregion
|
|
79
|
+
export { ProductRenderOptions, renderProduct };
|
package/dist/app.js
ADDED