@plitzi/sdk-server 0.38.4 → 0.38.6
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 +350 -0
- package/README.md +126 -17
- package/dist/actions.d.ts +8 -5
- package/dist/actions.js +3 -1
- package/dist/adapters/jsonAdapters.d.ts +1 -1
- package/dist/adapters/jsonAdapters.js +16 -3
- package/dist/core/configSeam.js +20 -4
- package/dist/core/createServer.js +3 -2
- package/dist/core/handlers/socialRouteHandlers.js +6 -1
- package/dist/core/http/stages/devReload.d.ts +11 -0
- package/dist/core/http/stages/devReload.js +17 -0
- package/dist/core/server/baseServer.d.ts +2 -0
- package/dist/core/server/baseServer.js +3 -0
- package/dist/core/server/pageServer.js +18 -0
- package/dist/core/services/registry.js +2 -0
- package/dist/core/services/rsc.js +1 -1
- package/dist/core/services/spaceDocument.d.ts +2 -0
- package/dist/core/services/spaceDocument.js +11 -0
- package/dist/functions-api.d.ts +6 -1
- package/dist/functionsRunner.d.ts +5 -0
- package/dist/functionsRunner.js +2 -1
- package/dist/helpers/hydrationPayload.d.ts +25 -4
- package/dist/helpers/hydrationPayload.js +36 -30
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/modules/actions/index.d.ts +11 -2
- package/dist/modules/actions/index.js +29 -7
- package/dist/modules/actions/runtime/fileKv.d.ts +28 -0
- package/dist/modules/actions/runtime/fileKv.js +95 -0
- package/dist/modules/actions/runtime/fileKv.test.d.ts +1 -0
- package/dist/modules/actions/runtime/memoryKv.d.ts +23 -0
- package/dist/modules/actions/runtime/memoryKv.js +39 -24
- package/dist/modules/actions/runtime/namespaceKv.d.ts +2 -0
- package/dist/modules/actions/runtime/namespaceKv.js +5 -3
- package/dist/modules/actions/runtime/renderResolver.d.ts +3 -2
- package/dist/modules/actions/runtime/renderResolver.js +13 -9
- package/dist/modules/actions/runtime/renderShare.d.ts +7 -24
- package/dist/modules/actions/runtime/renderShare.js +47 -13
- package/dist/modules/actions/runtime/runAction.js +15 -1
- package/dist/modules/actions/runtime/scope.js +16 -1
- package/dist/modules/actions/runtime/signing.js +4 -1
- package/dist/modules/actions/runtime/taskContext.d.ts +7 -1
- package/dist/modules/actions/runtime/taskContext.js +6 -2
- package/dist/modules/actions/tasks/registry.d.ts +3 -1
- package/dist/modules/actions/tasks/registry.js +15 -1
- package/dist/modules/actions/types.d.ts +15 -2
- package/dist/modules/functions/config.d.ts +6 -0
- package/dist/modules/functions/context.d.ts +4 -2
- package/dist/modules/functions/context.js +43 -21
- package/dist/modules/functions/load.d.ts +8 -1
- package/dist/modules/functions/load.js +8 -4
- package/dist/modules/functions/manifest.d.ts +4 -1
- package/dist/modules/functions/manifest.js +14 -2
- package/dist/modules/functions/native.d.ts +2 -1
- package/dist/modules/functions/native.js +2 -2
- package/dist/modules/functions/plugins.test.d.ts +1 -0
- package/dist/modules/functions/routes.d.ts +8 -3
- package/dist/modules/functions/routes.js +41 -21
- package/dist/modules/functions/scope.d.ts +30 -0
- package/dist/modules/functions/scope.js +28 -0
- package/dist/modules/functions/space.d.ts +10 -5
- package/dist/modules/functions/space.js +37 -15
- package/dist/modules/rsc/connectorRscData.d.ts +3 -1
- package/dist/modules/rsc/connectorRscData.js +8 -4
- package/dist/modules/rsc/handler.d.ts +3 -16
- package/dist/modules/rsc/handler.js +10 -4
- package/dist/modules/rsc/publicFileResolver.d.ts +15 -12
- package/dist/modules/rsc/publicFileResolver.js +56 -24
- package/dist/modules/rsc/resolveRscData.d.ts +3 -1
- package/dist/modules/rsc/resolveRscData.js +8 -5
- package/dist/modules/runtime/bundle.d.ts +5 -0
- package/dist/modules/runtime/bundle.js +20 -5
- package/dist/modules/sqlite/kv.d.ts +34 -0
- package/dist/modules/sqlite/kv.js +99 -0
- package/dist/modules/sqlite/kv.test.d.ts +1 -0
- package/dist/modules/ssr/Component.d.ts +2 -1
- package/dist/modules/ssr/Component.js +2 -1
- package/dist/modules/ssr/prepareRender.js +48 -6
- package/dist/modules/ssr/spaceDocument.d.ts +25 -0
- package/dist/modules/ssr/spaceDocument.js +72 -0
- package/dist/modules/ssr/spaceDocument.test.d.ts +1 -0
- package/dist/modules/ssr/testing/offlineData.d.ts +5 -0
- package/dist/modules/ssr/views/template.ejs +122 -20
- package/dist/plugins/compile.js +6 -21
- package/dist/plugins/compile.test.d.ts +1 -0
- package/dist/plugins/copy.d.ts +5 -0
- package/dist/plugins/copy.js +16 -1
- package/dist/plugins/copy.test.d.ts +1 -0
- package/dist/plugins/manager.d.ts +13 -0
- package/dist/plugins/manager.js +40 -11
- package/dist/plugins/watch.d.ts +12 -0
- package/dist/plugins/watch.js +79 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +2 -2
- package/dist/sqlite.d.ts +17 -0
- package/dist/sqlite.js +2 -0
- package/package.json +8 -3
- package/skills/plitzi-authoring/CHEATSHEET.md +4 -2
- package/skills/plitzi-authoring/SKILL.md +20 -19
- package/skills/plitzi-authoring/recipes/plugin.ts +1 -1
- package/skills/plitzi-authoring/recipes/server-data.ts +3 -3
- package/skills/plitzi-authoring/recipes/show-data.ts +3 -2
- package/skills/plitzi-authoring/recipes/typed-data.ts +1 -1
- package/skills/plitzi-authoring/reference/authoring-errors.md +8 -2
- package/skills/plitzi-authoring/reference/colours-and-motion.md +3 -1
- package/skills/plitzi-authoring/reference/data-and-visibility.md +24 -22
- package/skills/plitzi-authoring/reference/drawing.md +24 -0
- package/skills/plitzi-authoring/reference/efficiency.md +19 -2
- package/skills/plitzi-authoring/reference/flows.md +3 -0
- package/skills/plitzi-authoring/reference/layouts.md +8 -23
- package/skills/plitzi-authoring/reference/performance.md +2 -2
- package/skills/plitzi-authoring/reference/plugins.md +51 -38
- package/skills/plitzi-authoring/reference/review-checklist.md +2 -0
- package/skills/plitzi-authoring/reference/structure.md +32 -1
- package/skills/plitzi-authoring/reference/templates.md +5 -2
- package/skills/plitzi-authoring/reference/testing.md +9 -2
- package/skills/plitzi-authoring/reference/typed-sources.md +3 -1
- package/skills/plitzi-authoring/reference/validation.md +1 -1
package/README.md
CHANGED
|
@@ -4,8 +4,9 @@ The page server for Plitzi spaces: server-side rendering, React Server Component
|
|
|
4
4
|
HTTP kernel they run on. Ships as an HTTP/2 server by default, with support for HTTP/1.1 and HTTP/3.
|
|
5
5
|
|
|
6
6
|
This package serves **pages**. The AI surface — the MCP server, its tool engine and the draft-preview
|
|
7
|
-
endpoint — lives in [`@plitzi/sdk-mcp`](../mcp/README.md), which
|
|
8
|
-
|
|
7
|
+
endpoint — lives in [`@plitzi/sdk-mcp`](../mcp/README.md), which depends on this one's narrow entries (the kernel,
|
|
8
|
+
OAuth, and the renderer for draft preview alone), never the other way round. A deployment that only renders pages
|
|
9
|
+
never installs it, and never loads it.
|
|
9
10
|
|
|
10
11
|
## Installation
|
|
11
12
|
|
|
@@ -163,12 +164,16 @@ runtime, and its files into `public/` — a draft, or any published snapshot (`-
|
|
|
163
164
|
|
|
164
165
|
## Configuration
|
|
165
166
|
|
|
167
|
+
`createServer` takes a `ServerConfig` (exported, as a type, from `@plitzi/sdk-server`) — what a project types its own
|
|
168
|
+
options with: a `plitzi create` project keeps them in `src/config/serverOptions.ts`, typed from it less what its `main.ts`
|
|
169
|
+
wires itself.
|
|
170
|
+
|
|
166
171
|
| Option | Type | Default | Description |
|
|
167
172
|
|---|---|---|---|
|
|
168
173
|
| `httpVersion` | `1 \| 2 \| 3` | `2` with `tls`, else `1` | HTTP protocol version. Falls back to the nearest available lower version. |
|
|
169
174
|
| `tls` | `{ key, cert, minVersion? }` | — | TLS key and certificate. Required for versions 2 and 3; optional for version 1. |
|
|
170
175
|
| `devMode` | `boolean` | `false` | Enables development mode: appends `?dev` to esm.sh CDN URLs for React, and activates per-request timing metrics (see [Dev metrics](#dev-metrics)). Off, the process must run with `NODE_ENV=production` or React renders with its development build; the server says so once, at `error`. |
|
|
171
|
-
| `devReload` | `boolean` | `false` | Every page this server renders listens for `server.reloadPages()` and loads again — for a project that re-authors its space on save (what `plitzi create` writes into `main.ts`). Not turned on by `devMode`: each open page holds a connection for it. |
|
|
176
|
+
| `devReload` | `boolean` | `false` | Every page this server renders listens for `server.reloadPages()` and loads again — for a project that re-authors its space on save (what `plitzi create` writes into `main.ts`) — and loads again too when it reconnects to a server that restarted (a new process), so a change to the server's code reaches the open page. With `devMode` too, a plugin whose source changes is built again and swapped where it is drawn on every open page — the rest of the page, its state included, stays as it was; a plugin whose declaration changed loads the page again. Not turned on by `devMode`: each open page holds a connection for it. |
|
|
172
177
|
| `assetVersion` | `string` | — | Cache-buster appended as `?v=<assetVersion>` to all default SDK asset URLs. Compute from file mtime or package version at startup. |
|
|
173
178
|
| `cacheTtlMs` | `number` | `300000` | TTL in milliseconds for the SSR render cache. Set to `0` to disable. |
|
|
174
179
|
| `flags` | `Record<string, boolean> \| ({ spaceId, environment }) => Record<string, boolean>` | — | This deployment's say over the spaces' feature flags: above what a space declares, below the SDK's `flags` prop and a tester's dev tools, and only for flags the space declares. A function answers per space. Server actions and RSC resolve with it too. See `docs/en/feature-flags.md` in the workspace. |
|
|
@@ -182,6 +187,7 @@ runtime, and its files into `public/` — a draft, or any published snapshot (`-
|
|
|
182
187
|
| `autoLoadSchemaPlugins` | `boolean` | `true` | Auto-download and cache plugins declared in the schema's `offlineData.plugins` list. A type the deployment registers itself (`plugins`, named in `pluginNames`) is never looked for there: its own build is the one rendered. Set to `false` to manage plugin loading manually. |
|
|
183
188
|
| `allowPrivatePluginHosts` | `boolean` | `false` | Read a schema plugin from a private address (`localhost`, this server's own network). A plugin's address is typed by whoever edits a space, so leave it off anywhere but a development machine whose bucket is local. |
|
|
184
189
|
| `publicDir` | `string` | — | Absolute path to a directory served at the root URL level (e.g. `robots.txt`, `favicon.png`). Files are checked before `static` prefix routes. **Everything in it is public:** served to anyone who asks, with no check. |
|
|
190
|
+
| `dataDir` | `string` | — | The project's own data, never served: a provider resolved on the server (`runtime: 'server'`) whose `query` is `/data/<file>` reads `<dataDir>/<file>`. See [Project data](#project-data). |
|
|
185
191
|
| `static` | `Record<string, string>` | — | URL prefix → filesystem path mappings for static file serving. |
|
|
186
192
|
| `images` | `{ domains, cacheDir? }` | — | Pictures from other sites resized at `/_plitzi/img`, for an `image` to offer as a `srcset` (see [Images](#images)). |
|
|
187
193
|
| `ssrOnly` | `boolean` | `false` | Omit client-side JS from the rendered page. Useful for verifying SSR HTML without hydration. |
|
|
@@ -196,6 +202,20 @@ runtime, and its files into `public/` — a draft, or any published snapshot (`-
|
|
|
196
202
|
| `logger` | `ServerLogger` | the console | Where it says it: one structured `ServerLogEvent` stream. `consoleLogger` prints each as one line. |
|
|
197
203
|
| `onListenError` | `(error, { port, host, label }) => void` | exits non-zero | What to do when the server cannot take its port. By default it prints what went wrong and what to do about it, then exits — a process whose server never bound is not running. Supply this to keep it alive and decide yourself. |
|
|
198
204
|
| `workers` | `boolean \| number \| 'auto'` | `'auto'` under `NODE_ENV=production`, else `false` | How many processes serve the port: one per core, a number (lowered to the cores there are), or one. `SDK_SERVER_WORKERS` sets it when the config does not. See [Using every core](#using-every-core). |
|
|
205
|
+
| `action` | `SSRActionConfig` | — | The space's server actions: the write endpoint (`path`, default `/_action`), `lookups` (`ActionLookupsConfig`: `getAction`, `getFunctions`, `getPluginFunctions`, `getData`…), `kv`, `jobs`, `limits`, `email`, `signingSecret`, `onRun` / `onReject`. Absent, the server serves reads only. See `docs/en/server-actions.md` § 13 in the workspace. |
|
|
206
|
+
| `functions` | `{ native?, plugins?, runner?, limits? }` | — | The server's code (`FunctionsConfig` in `@plitzi/sdk-server/functions`, checked as the server starts): its own tasks and routes (`native`, from `loadFunctions`), the server halves of the plugins it ships (`plugins`, by plugin type — see [A plugin's server half](#a-plugins-server-half)), the `runner` the spaces' functions run on, and the per-invocation `limits`. See `docs/en/functions.md` § 8 in the workspace. |
|
|
207
|
+
| `connectors` | `ConnectorLookupsConfig` | — | `getConnector` and `getCredential` for the space's connectors, read by the RSC read path and the `/_action` write endpoint alike. See `docs/en/connectors.md` in the workspace. |
|
|
208
|
+
| `auth` | `Auth` | — | A `createAuth(...)` result: fills the three auth adapters in and brings the cookie naming. See [User authentication](#user-authentication). |
|
|
209
|
+
| `sessionRenewal` | `{ url } \| false` | on with `auth` | Where a page request whose access cookie died, its refresh cookie alive, renews before it is rendered. |
|
|
210
|
+
| `authCookie` | `SSRAuthCookie` | — | Name, domain, `sameSite`, `secure`, `refreshPath` (`/auth`) and `hintSuffix` (`_hint`) of the session cookies. `auth` brings its own; set here, a piece wins over it. |
|
|
211
|
+
| `exchangePath` | `string \| false` | `'/auth/exchange'` | Where a browser-obtained credential is handed over; served only with the `exchangeCredential` adapter. |
|
|
212
|
+
| `debugMode` | `boolean` | `devMode`, or the space's `devTools` | Authorizes debugging on the pages this server renders. Set, it decides for every space: `false` is a refusal no space turns around. |
|
|
213
|
+
| `frameOptions` | `'DENY' \| 'SAMEORIGIN' \| string[] \| false` | `'DENY'` | Who may embed the pages in a frame (CSP `frame-ancestors`, and `X-Frame-Options`); `false` omits both headers. A space adapter's `frameAncestors` derives it per space — see [Space adapters](#space-adapters-a-space-per-domain). |
|
|
214
|
+
| `fonts` | `{ baseUrl?, dir? }` | `baseUrl: '/fonts'` | Where the font files a space uploaded are addressed, and with `dir` served from this server under `/fonts/*`. See `docs/en/fonts.md` in the workspace. |
|
|
215
|
+
| `environment` | `'production' \| 'staging' \| 'development' \| 'local'` | `'production'` | The Plitzi tier whose endpoints the rendered pages are handed. |
|
|
216
|
+
| `services` | `{ ssr?, rsc? }` | `ssr` on, `rsc` with `getRscData` | Which request-handling services are mounted. |
|
|
217
|
+
| `preview` / `draftStore` | `SSRPreviewConfig` / `DraftStore` | off / in memory | The draft-preview endpoint (`enabled`, `path` `/__preview`, `secret`, `ttlMs`) and the store behind its tokens — share one across replicas. The rest of draft preview is `@plitzi/sdk-mcp`'s: see its [Draft preview](../mcp/README.md#draft-preview). |
|
|
218
|
+
| `port` / `host` | `number` / `string` | — | Not read: `server.listen(port, host?)` takes where it binds (`host` defaults to `0.0.0.0`). |
|
|
199
219
|
|
|
200
220
|
### HTTP version behaviour
|
|
201
221
|
|
|
@@ -297,13 +317,17 @@ const server = createServer({
|
|
|
297
317
|
server.listen(3001);
|
|
298
318
|
```
|
|
299
319
|
|
|
320
|
+
Beside the page adapters it offers `getSchema` and `getStyle`, read from the same file, and — when `offlineData` is a
|
|
321
|
+
path — `saveSchema` and `saveStyle`, each writing its document back into the file. So an MCP server
|
|
322
|
+
([`@plitzi/sdk-mcp`](../mcp/README.md#adapters)) can run over it, with a `getGrant` of yours beside it.
|
|
323
|
+
|
|
300
324
|
### `JsonAdaptersConfig`
|
|
301
325
|
|
|
302
326
|
| Option | Type | Description |
|
|
303
327
|
|---|---|---|
|
|
304
328
|
| `offlineData` | `string` | Path to a single JSON file used for every request. |
|
|
305
329
|
| `offlineData` | `(spaceId, environment, revision?) => string` | Function returning the path for the requested space. |
|
|
306
|
-
| `offlineData` | `OfflineDataRaw` | The space itself, for a consumer that already holds it. Read-only: `
|
|
330
|
+
| `offlineData` | `OfflineDataRaw` | The space itself, for a consumer that already holds it. Read-only: `saveSchema` and `saveStyle` are offered only for a path. |
|
|
307
331
|
| `deployment` | `string` | Path to a JSON file containing an `SSRSpaceDeployment` object. |
|
|
308
332
|
| `deployment` | `SSRSpaceDeployment` | Inline deployment object used for every request. |
|
|
309
333
|
| `deployment` | `Record<hostname, SSRSpaceDeployment>` | Per-hostname map. Use `'*'` as a catch-all. |
|
|
@@ -426,6 +450,24 @@ some visitors may read in it: that is served by a server action whose `access` c
|
|
|
426
450
|
|
|
427
451
|
`/.well-known/` paths follow the same lookup order: served from `publicDir` if a matching file exists, otherwise `404 Not Found`. They are never handled by the SSR renderer.
|
|
428
452
|
|
|
453
|
+
### Project data
|
|
454
|
+
|
|
455
|
+
A project with no backend keeps its data as JSON files, and `dataDir` is where they stay off the internet: nothing
|
|
456
|
+
serves the folder. A provider resolved on the server whose `query` is `/data/<file>` reads `<dataDir>/<file>` before the
|
|
457
|
+
page is answered — in the shape a browser request publishes (`{ status, data }`), so a binding reads `<source>.data.…`
|
|
458
|
+
in either runtime — and the page arrives with it. A browser asking for the same path gets a page, never the file.
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
createServer({ dataDir: path.join(PROJECT_ROOT, 'src/data'), adapters: { ... } });
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Without a `dataDir`, `/data/<file>` is answered from `action.lookups.getData(spaceId, at)` — the space's own data, as
|
|
465
|
+
its files' text by path, as of the version being rendered: what a cloud deployment keeps for a space (`plitzi push`).
|
|
466
|
+
|
|
467
|
+
`/data/…` is the data folder's first; a server provider reading another path is answered from `publicDir`, as the
|
|
468
|
+
browser would be. What a provider reads goes into the page it renders, whole: data a page must not carry — a price
|
|
469
|
+
list's costs, another visitor's rows — is a server action's to read, answering only what is shown.
|
|
470
|
+
|
|
429
471
|
## Compression
|
|
430
472
|
|
|
431
473
|
Responses are compressed based on the `Accept-Encoding` request header. By default the server offers Brotli, then
|
|
@@ -517,8 +559,12 @@ All fields are optional and combined with AND logic. Calling `invalidate()` with
|
|
|
517
559
|
|
|
518
560
|
Plugins are React component bundles that extend the Plitzi schema renderer. They are defined globally at server config level, compiled or copied on first use, and cached on disk for one week. The `getSpaceDeployment` adapter controls which plugins each space gets access to via `pluginNames`.
|
|
519
561
|
|
|
562
|
+
A compiled plugin is one module and its stylesheet, built as `plitzi pack plugin` builds it: React and the SDK kept out, and everything else inside — images and fonts as data URIs, and a file imported whole the way Vite imports it (`worker.js?raw` for its text, `engine.wasm?inline` for a data URI).
|
|
563
|
+
|
|
564
|
+
Whatever stylesheet the server writes for a plugin — compiled, copied or downloaded — goes into the `plitzi-sdk-plugin` cascade layer (`inPluginLayer` from `@plitzi/sdk-shared/style`), below the space's own: a space's classes and `customCss` win over what the plugin shipped, whatever the specificity. A `css` given as a web path (`/builder-assets/…`) is linked as it is.
|
|
565
|
+
|
|
520
566
|
```ts
|
|
521
|
-
import type { SSRSpaceDeployment } from '@plitzi/sdk-
|
|
567
|
+
import type { SSRSpaceDeployment } from '@plitzi/sdk-shared';
|
|
522
568
|
|
|
523
569
|
const server = createServer({
|
|
524
570
|
plugins: {
|
|
@@ -644,6 +690,10 @@ Compiled and copied plugin files are served under `/sdk-plugins/{name}/`:
|
|
|
644
690
|
|
|
645
691
|
Plugin responses are compressed with Brotli or gzip like all other responses.
|
|
646
692
|
|
|
693
|
+
A page imports only the plugins it draws: its own elements, its layouts', and those of the components and references it
|
|
694
|
+
holds (`pageElementTypes`). The rest reach the page as their declaration (`PluginEntry.deferred`) and are imported when
|
|
695
|
+
a page that draws them is opened, client-side navigation included.
|
|
696
|
+
|
|
647
697
|
### TTL and invalidation
|
|
648
698
|
|
|
649
699
|
Plugins are compiled once and cached for `pluginsTtlMs` (default: 1 week). The TTL is tracked via a `meta.json` file written alongside each plugin's compiled output. On the next request after expiry the plugin is automatically recompiled.
|
|
@@ -672,6 +722,28 @@ server.plugins.register('my-chart', {
|
|
|
672
722
|
|
|
673
723
|
`register` clears any in-memory cache for that plugin name, so the next request triggers a fresh compile/copy. Previously compiled disk files are reused if they are within their TTL; call `server.plugins.invalidate(name)` beforehand to force a full rebuild.
|
|
674
724
|
|
|
725
|
+
### A plugin's server half
|
|
726
|
+
|
|
727
|
+
A plugin can bring server code — its `functions/`, written as a space's functions are — whose routes answer under
|
|
728
|
+
`/fn/plugins/<type>/` and whose tasks are steps named `<type>.<action>`, run with a plugin's narrower `ctx` (its own
|
|
729
|
+
corner of `kv`, none of the space's credentials or channels). This server's own plugins hand theirs over by type:
|
|
730
|
+
|
|
731
|
+
```ts
|
|
732
|
+
import { createServer, loadFunctions } from '@plitzi/sdk-server';
|
|
733
|
+
|
|
734
|
+
const [board] = await loadFunctions(new URL('./plugins/Board/functions/', import.meta.url));
|
|
735
|
+
|
|
736
|
+
const server = createServer({ adapters, functions: { native, plugins: { board } } });
|
|
737
|
+
|
|
738
|
+
// While it runs: a server half loaded again — or, with none, taken away — without a restart.
|
|
739
|
+
server.functions.setPlugin('board', board);
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
`loadFunctionsSource(files)` loads one from its files rather than a folder: what a packed plugin carries as
|
|
743
|
+
`functions.source.json`. The plugins a space uploaded reach a run through `action.lookups.getPluginFunctions`. The
|
|
744
|
+
whole of it — `ctx`, routes, how a packed plugin carries it — is in
|
|
745
|
+
[functions § a plugin's server half](../../docs/en/functions.md#10-a-plugins-server-half).
|
|
746
|
+
|
|
675
747
|
## React Server Components (RSC)
|
|
676
748
|
|
|
677
749
|
The SSR server includes a lightweight RSC endpoint that delivers server-side data to schema elements with `runtime: 'server'`. This is not the React RSC wire protocol — it uses a simple JSON transport that any element can consume via `useRscData()`.
|
|
@@ -719,7 +791,7 @@ Implement `getRscData` in your adapters to serve data from the `/_rsc` endpoint.
|
|
|
719
791
|
When `ids` is provided the client is performing a **partial refresh** — only return data for those element IDs. When `ids` is absent, return data for all elements (full fetch):
|
|
720
792
|
|
|
721
793
|
```ts
|
|
722
|
-
import type { SSRAdapters, SSRRscContext, SSRRscData } from '@plitzi/sdk-
|
|
794
|
+
import type { SSRAdapters, SSRRscContext, SSRRscData } from '@plitzi/sdk-shared';
|
|
723
795
|
|
|
724
796
|
const getRscData = async ({ user, ids, loadOfflineData }: SSRRscContext): Promise<SSRRscData> => {
|
|
725
797
|
// Only serve data when the schema has RSC enabled. `loadOfflineData` joins the read the page render already
|
|
@@ -782,6 +854,10 @@ type SSRRscData = {
|
|
|
782
854
|
|
|
783
855
|
Return `{}` (empty object) when there is no server data for the current request. Each key must match the `id` of a schema element with `runtime: 'server'`.
|
|
784
856
|
|
|
857
|
+
**One request per question, the newest winning.** The client (`refreshRsc`) joins a refresh already in flight for the same URL, and aborts one a newer refresh would overwrite — a whole payload asked again, an element asked again — so answers never land out of order. `cancelRsc(store, ids)` (an `apiContainer`'s `cancelQuery`) aborts on purpose. The server hears it: `SSRRscContext.signal` aborts when the browser hangs up, and the default `getRscData` stops every element still resolving — an action's render run included, unless another visitor's identical render is still waiting on the same run. A custom adapter does the same with the signal it is handed.
|
|
858
|
+
|
|
859
|
+
What a refresh asks for besides the location (`/_rsc?location=…&country=fr`) is `req.ctx.rscParams`; the action resolver hands it to the action over the `input` the element was saved with.
|
|
860
|
+
|
|
785
861
|
### `/_rsc` endpoint
|
|
786
862
|
|
|
787
863
|
The server automatically registers `GET /_rsc` when `adapters.getRscData` is provided. The endpoint:
|
|
@@ -789,7 +865,7 @@ The server automatically registers `GET /_rsc` when `adapters.getRscData` is pro
|
|
|
789
865
|
1. Reads `spaceId`, `environment`, and `revision` from the resolved `spaceDeployment` context.
|
|
790
866
|
2. Reads the authenticated user from `ctx.user`.
|
|
791
867
|
3. Reads optional `?ids=elem1,elem2` for partial refresh.
|
|
792
|
-
4. Calls `adapters.getRscData({ req, spaceId, environment, revision, user, ids, loadOfflineData })`.
|
|
868
|
+
4. Calls `adapters.getRscData({ req, spaceId, environment, revision, user, ids, loadOfflineData, signal })`.
|
|
793
869
|
5. Returns a JSON payload:
|
|
794
870
|
|
|
795
871
|
```json
|
|
@@ -824,6 +900,7 @@ createServer({
|
|
|
824
900
|
| `enabled` | `boolean` | `true` (when adapter provided) | Activate or deactivate the RSC endpoint. |
|
|
825
901
|
| `path` | `string` | `'/_rsc'` | URL path for the RSC endpoint. |
|
|
826
902
|
| `cacheTtlMs` | `number` | `30000` | TTL in milliseconds for the RSC response cache. Set to `0` to disable RSC caching. Ignored for the `main` environment. |
|
|
903
|
+
| `elementTimeoutMs` | `number` | `5000` | How long one server element may take before the page is answered without it — and what it was resolving is cancelled. The page's ceiling, winning over the producer's own (`action.limits.timeoutMs`). A section cut by it says to raise this. |
|
|
827
904
|
|
|
828
905
|
### Consuming RSC data in plugins
|
|
829
906
|
|
|
@@ -1118,7 +1195,7 @@ Credential comparison uses `crypto.timingSafeEqual` to prevent timing attacks. I
|
|
|
1118
1195
|
Register request-scoped middleware to run before the SSR renderer. Middlewares execute in the order they are declared and can short-circuit by not calling `next()`.
|
|
1119
1196
|
|
|
1120
1197
|
```ts
|
|
1121
|
-
import type { SSRMiddleware } from '@plitzi/sdk-
|
|
1198
|
+
import type { SSRMiddleware } from '@plitzi/sdk-shared';
|
|
1122
1199
|
|
|
1123
1200
|
const corsMiddleware: SSRMiddleware = (req, res, next) => {
|
|
1124
1201
|
res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com');
|
|
@@ -1252,7 +1329,9 @@ ends any other event stream, lets what is being answered finish, and cuts whatev
|
|
|
1252
1329
|
(`SHUTDOWN_GRACE_MS`). A page left open on a board, or an agent's app listening, no longer holds a Ctrl+C or a
|
|
1253
1330
|
SIGTERM until something kills the process.
|
|
1254
1331
|
|
|
1255
|
-
`@plitzi/sdk-server/mysql` has the same pair over a `mysql2` pool (`createMysqlJobQueue`, `createMysqlKv`).
|
|
1332
|
+
`@plitzi/sdk-server/mysql` has the same pair over a `mysql2` pool (`createMysqlJobQueue`, `createMysqlKv`). With no
|
|
1333
|
+
database at all, `kv` survives a restart in a file: `createFileKv` (`@plitzi/sdk-server/actions`, one process, JSON) or
|
|
1334
|
+
`createSqliteKv` (`@plitzi/sdk-server/sqlite`, any number of processes on the file). Anything
|
|
1256
1335
|
else is the `ActionJobQueue` contract written against your store — see
|
|
1257
1336
|
[server actions § self-hosted](https://github.com/plitzi/plitzi-workspace/blob/main/docs/en/server-actions.md#13-for-a-self-hosted-deployment).
|
|
1258
1337
|
|
|
@@ -1308,7 +1387,7 @@ URLs become `/sdk-assets/plitzi-sdk.js?v=<assetVersion>`. This is separate from
|
|
|
1308
1387
|
By default the server uses its built-in EJS template. You can replace it with any function that receives the template params and returns an HTML string:
|
|
1309
1388
|
|
|
1310
1389
|
```ts
|
|
1311
|
-
import type { SSRTemplateFn } from '@plitzi/sdk-
|
|
1390
|
+
import type { SSRTemplateFn } from '@plitzi/sdk-shared';
|
|
1312
1391
|
|
|
1313
1392
|
const templateFn: SSRTemplateFn = ({ html, offlineData, jsPath, cssPath, plugins, react, reactDom, reactDomClient, reactJsx }) => `
|
|
1314
1393
|
<!doctype html>
|
|
@@ -1331,6 +1410,16 @@ createServer({ templateFn, adapters: { ... } });
|
|
|
1331
1410
|
|
|
1332
1411
|
The function is called once per render (cache misses only). The built-in `template.ejs` is used as fallback when `templateFn` is not set.
|
|
1333
1412
|
|
|
1413
|
+
What the built-in template does that a custom one has to decide for itself:
|
|
1414
|
+
|
|
1415
|
+
- **The space travels beside the page.** The built-in template leaves it out of the payload, names it with
|
|
1416
|
+
`spaceDocumentPath` (`<link id="plitzi-space" rel="preload" as="fetch">`) and its bootstrap fetches it before it
|
|
1417
|
+
hydrates. A custom template is handed the space inline in `offlineData`, as before.
|
|
1418
|
+
- **Only the plugins the page draws are imported up front.** An entry with `deferred` is handed to `render()` as
|
|
1419
|
+
`{ load: () => import(js), css, declaration }`: the SDK registers it at once and loads its code and stylesheet the
|
|
1420
|
+
first time one of its elements is drawn. A template that imports every entry statically still works; it only loads
|
|
1421
|
+
more than the page draws.
|
|
1422
|
+
|
|
1334
1423
|
**Streaming compatibility**: when `streaming: true` the server calls `templateFn` with a sentinel placeholder (`<!--SSR_CONTENT-->`) in place of the React HTML, splits the output at that marker, and streams head and tail separately. Existing templates that interpolate `html` as-is are compatible without any changes.
|
|
1335
1424
|
|
|
1336
1425
|
## Template props
|
|
@@ -1355,9 +1444,11 @@ return {
|
|
|
1355
1444
|
| `title` | `string` | Page `<title>`. Defaults to `'Plitzi App'`. |
|
|
1356
1445
|
| `jsPath` | `string` | URL for the SDK JS module. Defaults to `/sdk-assets/plitzi-sdk.js`. |
|
|
1357
1446
|
| `cssPath` | `string` | URL for the SDK stylesheet. Defaults to `/sdk-assets/plitzi-sdk.css`. |
|
|
1447
|
+
| `iconsCssPath` | `string` | URL for Font Awesome's stylesheet, linked after `cssPath`; its fonts are files beside it. Defaults to `/sdk-assets/plitzi-sdk-icons.css`. |
|
|
1448
|
+
| `spaceDocumentPath` | `string` | Where the page fetches the space from (`/_plitzi/space/<hash>.json`), preloaded in the head and read by the bootstrap before it hydrates. Set by the built-in template's render; absent for a draft preview and for a custom `templateFn`, which receive the space inline in `offlineData`. |
|
|
1358
1449
|
| `builderJsPath` | `string` | URL for the builder JS module. Omitted by default. |
|
|
1359
1450
|
| `builderCssPath` | `string` | URL for the builder stylesheet. Omitted by default. |
|
|
1360
|
-
| `plugins` | `PluginEntry[]` | Plugin entries to inject. Normally set automatically via `pluginNames`. |
|
|
1451
|
+
| `plugins` | `PluginEntry[]` | Plugin entries to inject. Normally set automatically via `pluginNames`. An entry with `deferred` is one the page draws none of: its declaration, for the browser to load its code when a page that draws it is opened. |
|
|
1361
1452
|
| `react` | `string` | React ESM URL. Defaults to `esm.sh/react@19`. |
|
|
1362
1453
|
| `reactDom` | `string` | ReactDOM ESM URL. |
|
|
1363
1454
|
| `reactDomClient` | `string` | ReactDOM client ESM URL. |
|
|
@@ -1447,6 +1538,27 @@ package, not a snippet.
|
|
|
1447
1538
|
|
|
1448
1539
|
## Exported types
|
|
1449
1540
|
|
|
1541
|
+
What this package's own code defines is exported from it:
|
|
1542
|
+
|
|
1543
|
+
```ts
|
|
1544
|
+
import type {
|
|
1545
|
+
ServerConfig,
|
|
1546
|
+
SSRContext,
|
|
1547
|
+
PipelineExtensions,
|
|
1548
|
+
JsonAdaptersConfig,
|
|
1549
|
+
AuthAdapters,
|
|
1550
|
+
AuthAdaptersConfig,
|
|
1551
|
+
CloudAdaptersConfig,
|
|
1552
|
+
SpaceAdaptersConfig
|
|
1553
|
+
} from '@plitzi/sdk-server';
|
|
1554
|
+
```
|
|
1555
|
+
|
|
1556
|
+
`ServerConfig` is what `createServer` takes — `SSRServerConfig` with the page adapters required, plus `auth` and
|
|
1557
|
+
`sessionRenewal` — and what a project types its own options with. `SSRContext` here is the request context a pipeline
|
|
1558
|
+
stage sees.
|
|
1559
|
+
|
|
1560
|
+
The shapes the server shares with the SDK and `@plitzi/sdk-mcp` are `@plitzi/sdk-shared`'s:
|
|
1561
|
+
|
|
1450
1562
|
```ts
|
|
1451
1563
|
import type {
|
|
1452
1564
|
SSRAdapters,
|
|
@@ -1455,13 +1567,13 @@ import type {
|
|
|
1455
1567
|
SSRResponseHelpers,
|
|
1456
1568
|
SSRMiddleware,
|
|
1457
1569
|
SSRMiddlewareNext,
|
|
1458
|
-
SSRContext,
|
|
1459
1570
|
SSRSpaceDeployment,
|
|
1460
1571
|
SSRTemplateProps,
|
|
1461
1572
|
SSRTemplateFn,
|
|
1462
1573
|
SSRCredential,
|
|
1463
1574
|
SSRUser,
|
|
1464
1575
|
SSRHeaders,
|
|
1576
|
+
SSRRscContext,
|
|
1465
1577
|
SSRRscData,
|
|
1466
1578
|
SSRRscConfig,
|
|
1467
1579
|
SSRServer,
|
|
@@ -1472,9 +1584,6 @@ import type {
|
|
|
1472
1584
|
PluginEntry,
|
|
1473
1585
|
PluginRegistry,
|
|
1474
1586
|
CacheFilter,
|
|
1475
|
-
CacheManager
|
|
1476
|
-
|
|
1477
|
-
AuthAdapters,
|
|
1478
|
-
AuthAdaptersConfig
|
|
1479
|
-
} from '@plitzi/sdk-server';
|
|
1587
|
+
CacheManager
|
|
1588
|
+
} from '@plitzi/sdk-shared';
|
|
1480
1589
|
```
|
package/dist/actions.d.ts
CHANGED
|
@@ -41,18 +41,21 @@ export { checkAction } from './modules/actions/runtime/check';
|
|
|
41
41
|
export type { ActionCheckDeps } from './modules/actions/runtime/check';
|
|
42
42
|
export { ActionRefusal, ActionRunError } from './modules/actions/runtime/errors';
|
|
43
43
|
export { DEFAULT_LIMITS } from './modules/actions/runtime/limits';
|
|
44
|
+
export { MIN_SIGNING_SECRET_LENGTH } from './modules/actions/runtime/signing';
|
|
44
45
|
export { createTaskRegistry, taskName } from './modules/actions/tasks/registry';
|
|
45
46
|
export { describeCatalog, describeTask } from './modules/actions/taskCatalog';
|
|
46
47
|
/**
|
|
47
48
|
* The `kv` seam: an ADAPTER a deployment fills, and the logic that sits on top of it.
|
|
48
49
|
*
|
|
49
|
-
* There is deliberately
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
50
|
+
* There is deliberately no client of any store here: a deployment passes six string operations over whatever it
|
|
51
|
+
* already runs (`createRedisKv` wraps a client it opened), and `createKvStore` supplies everything that decides how a
|
|
52
|
+
* counter behaves, so the rule a rate limit depends on is written once rather than once per deployment. The
|
|
53
|
+
* in-process Map is the same shape, which is why the default and a cluster's store behave identically instead of
|
|
54
|
+
* nearly so — and `createFileKv` is that Map kept in a JSON file, for one process that must survive a restart.
|
|
54
55
|
*/
|
|
55
56
|
export { createMemoryKv } from './modules/actions/runtime/memoryKv';
|
|
57
|
+
export { createFileKv } from './modules/actions/runtime/fileKv';
|
|
58
|
+
export type { FileKv, FileKvOptions } from './modules/actions/runtime/fileKv';
|
|
56
59
|
export { createKvStore } from './modules/actions/runtime/kvStore';
|
|
57
60
|
export { createRedisKv } from './modules/actions/runtime/redisKv';
|
|
58
61
|
export type { RedisKvClient, RedisKvOptions } from './modules/actions/runtime/redisKv';
|
package/dist/actions.js
CHANGED
|
@@ -9,11 +9,13 @@ import { createKvStore } from "./modules/actions/runtime/kvStore.js";
|
|
|
9
9
|
import { DEFAULT_LIMITS } from "./modules/actions/runtime/limits.js";
|
|
10
10
|
import { createMemoryKv } from "./modules/actions/runtime/memoryKv.js";
|
|
11
11
|
import { spaceKvPatterns } from "./modules/actions/runtime/namespaceKv.js";
|
|
12
|
+
import { MIN_SIGNING_SECRET_LENGTH } from "./modules/actions/runtime/signing.js";
|
|
12
13
|
import { createTaskRegistry, taskName } from "./modules/actions/tasks/registry.js";
|
|
13
14
|
import { checkAction } from "./modules/actions/runtime/check.js";
|
|
14
15
|
import { createRejectLogger, createRunLogger } from "./modules/actions/runtime/runLogger.js";
|
|
15
16
|
import { describeCatalog, describeTask } from "./modules/actions/taskCatalog.js";
|
|
16
17
|
import { createActionsModule } from "./modules/actions/index.js";
|
|
18
|
+
import { createFileKv } from "./modules/actions/runtime/fileKv.js";
|
|
17
19
|
import { createRedisKv } from "./modules/actions/runtime/redisKv.js";
|
|
18
20
|
import { cronFiresBetween, cronMatches, cronNextFire, parseCron } from "@plitzi/sdk-shared/actions";
|
|
19
|
-
export { ActionRefusal, ActionRunError, DEFAULT_LIMITS, DEFAULT_MAX_ATTEMPTS, MAX_LIST_BYTES, MAX_LIST_ENTRIES, checkAction, createActionJobs, createActionsModule, createJobWorker, createKvStore, createMemoryJobQueue, createMemoryKv, createRedisKv, createRejectLogger, createRunLogger, createScheduler, createTaskRegistry, cronFiresBetween, cronMatches, cronNextFire, describeCatalog, describeTask, parseCron, scheduleFor, scheduleJobId, schedulesFor, spaceKvPatterns, taskName };
|
|
21
|
+
export { ActionRefusal, ActionRunError, DEFAULT_LIMITS, DEFAULT_MAX_ATTEMPTS, MAX_LIST_BYTES, MAX_LIST_ENTRIES, MIN_SIGNING_SECRET_LENGTH, checkAction, createActionJobs, createActionsModule, createFileKv, createJobWorker, createKvStore, createMemoryJobQueue, createMemoryKv, createRedisKv, createRejectLogger, createRunLogger, createScheduler, createTaskRegistry, cronFiresBetween, cronMatches, cronNextFire, describeCatalog, describeTask, parseCron, scheduleFor, scheduleJobId, schedulesFor, spaceKvPatterns, taskName };
|
|
@@ -7,7 +7,7 @@ export type JsonAdaptersConfig = {
|
|
|
7
7
|
/**
|
|
8
8
|
* The space: a path to a `{ schema, style }` JSON, a function returning one per request, or the data itself for a
|
|
9
9
|
* consumer that already holds it (composed at startup, fetched once, built in a test). Only a path can be written
|
|
10
|
-
* back to, so `
|
|
10
|
+
* back to, so `saveSchema` and `saveStyle` are offered only when one was given.
|
|
11
11
|
*/
|
|
12
12
|
offlineData: OfflineDataRaw | string | ((spaceId: number, environment: string, revision?: number) => string);
|
|
13
13
|
deployment?: string | SSRSpaceDeployment | Record<string, SSRSpaceDeployment>;
|
|
@@ -38,13 +38,21 @@ var createJsonAdapters = (config) => {
|
|
|
38
38
|
return Promise.resolve(void 0);
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
|
-
const
|
|
41
|
+
const getSchema = async (spaceId, environment) => (await getOfflineData(spaceId, environment))?.schema;
|
|
42
|
+
const getStyle = async (spaceId, environment) => (await getOfflineData(spaceId, environment))?.style;
|
|
43
|
+
const writeBack = (spaceId, environment, change) => {
|
|
42
44
|
const filePath = pathFor(spaceId, environment);
|
|
43
45
|
if (!filePath) return Promise.resolve();
|
|
44
|
-
|
|
46
|
+
const current = json.read(filePath);
|
|
47
|
+
writeFileSync(filePath, JSON.stringify({
|
|
48
|
+
...current,
|
|
49
|
+
...change
|
|
50
|
+
}, null, 2));
|
|
45
51
|
json.forget(filePath);
|
|
46
52
|
return Promise.resolve();
|
|
47
53
|
};
|
|
54
|
+
const saveSchema = (spaceId, environment, schema) => writeBack(spaceId, environment, { schema });
|
|
55
|
+
const saveStyle = (spaceId, environment, style) => writeBack(spaceId, environment, { style });
|
|
48
56
|
const getSpaceDeployment = (req) => {
|
|
49
57
|
const { deployment } = config;
|
|
50
58
|
if (!deployment) return Promise.resolve({
|
|
@@ -72,7 +80,12 @@ var createJsonAdapters = (config) => {
|
|
|
72
80
|
return {
|
|
73
81
|
getOfflineData,
|
|
74
82
|
getSpaceDeployment,
|
|
75
|
-
|
|
83
|
+
getSchema,
|
|
84
|
+
getStyle,
|
|
85
|
+
...typeof config.offlineData !== "object" ? {
|
|
86
|
+
saveSchema,
|
|
87
|
+
saveStyle
|
|
88
|
+
} : {}
|
|
76
89
|
};
|
|
77
90
|
};
|
|
78
91
|
//#endregion
|
package/dist/core/configSeam.js
CHANGED
|
@@ -22,6 +22,10 @@ var isManifest = checkedOnce(isConnectorManifestDraft);
|
|
|
22
22
|
var isDocument = checkedOnce(isActionDocument);
|
|
23
23
|
/** What `getFunctions` answers: built by `@plitzi/sdk-server/functions` from a space's saved bundle, a live object. */
|
|
24
24
|
var isSpaceFunctions = (value) => isRecord(value) && isRecord(value.bundle) && typeof value.bundle.id === "string" && typeof value.bundle.load === "function" && isRecord(value.manifest);
|
|
25
|
+
/** What `getPluginFunctions` answers: a `SpaceFunctions` per plugin type. */
|
|
26
|
+
var isPluginFunctions = (value) => isRecord(value) && Object.values(value).every(isSpaceFunctions);
|
|
27
|
+
/** What `getData` answers: the space's data files' text, by path. */
|
|
28
|
+
var isProjectData = (value) => isRecord(value) && Object.values(value).every((text) => typeof text === "string");
|
|
25
29
|
/** Why a document is not one this server runs: the first problem its validator found, by where it is. */
|
|
26
30
|
var firstProblem = (errors) => {
|
|
27
31
|
const first = errors.at(0);
|
|
@@ -63,7 +67,7 @@ var connectorLookupsOf = (config) => {
|
|
|
63
67
|
* not one is refused; listed, it is left out and said, so one broken action does not take its space's schedule down.
|
|
64
68
|
*/
|
|
65
69
|
var actionLookupsOf = (config) => {
|
|
66
|
-
const { getConnector, getFunctions, listActions } = config;
|
|
70
|
+
const { getConnector, getData, getFunctions, getPluginFunctions, listActions } = config;
|
|
67
71
|
return {
|
|
68
72
|
getAction: async (spaceId, actionId, at) => {
|
|
69
73
|
const found = await config.getAction(spaceId, actionId, at);
|
|
@@ -89,14 +93,26 @@ var actionLookupsOf = (config) => {
|
|
|
89
93
|
if (!isSpaceFunctions(found)) throw new Error(`The functions of space ${String(spaceId)} are not what @plitzi/sdk-server/functions builds for one`);
|
|
90
94
|
return found;
|
|
91
95
|
} } : {},
|
|
96
|
+
...getData ? { getData: async (spaceId, at) => {
|
|
97
|
+
const found = await getData(spaceId, at);
|
|
98
|
+
if (found === void 0 || found === null) return;
|
|
99
|
+
if (!isProjectData(found)) throw new Error(`The data of space ${String(spaceId)} is not its files' text by path`);
|
|
100
|
+
return found;
|
|
101
|
+
} } : {},
|
|
102
|
+
...getPluginFunctions ? { getPluginFunctions: async (spaceId, at) => {
|
|
103
|
+
const found = await getPluginFunctions(spaceId, at);
|
|
104
|
+
if (found === void 0 || found === null) return;
|
|
105
|
+
if (!isPluginFunctions(found)) throw new Error(`The plugin functions of space ${String(spaceId)} are not, by plugin type, what @plitzi/sdk-server/functions builds for one`);
|
|
106
|
+
return found;
|
|
107
|
+
} } : {},
|
|
92
108
|
...config.getFlags ? { getFlags: config.getFlags } : {}
|
|
93
109
|
};
|
|
94
110
|
};
|
|
95
111
|
var isFunction = (value) => typeof value === "function";
|
|
96
112
|
/** A driver as `createDbDrivers` builds one: the engine a credential names, and how to query it. */
|
|
97
113
|
var isDbDriver = (value) => isRecord(value) && typeof value.engine === "string" && isFunction(value.query);
|
|
98
|
-
/** The functions config as `@plitzi/sdk-server/functions` builds it: native definitions, a runner, ceilings. */
|
|
99
|
-
var isFunctionsConfig = (value) => isRecord(value) && (value.native === void 0 || Array.isArray(value.native) && value.native.every(isRecord)) && (value.runner === void 0 || isRecord(value.runner) && isFunction(value.runner.describe) && isFunction(value.runner.invoke)) && (value.limits === void 0 || isRecord(value.limits));
|
|
114
|
+
/** The functions config as `@plitzi/sdk-server/functions` builds it: native and plugin definitions, a runner, ceilings. */
|
|
115
|
+
var isFunctionsConfig = (value) => isRecord(value) && (value.native === void 0 || Array.isArray(value.native) && value.native.every(isRecord)) && (value.plugins === void 0 || isRecord(value.plugins) && Object.values(value.plugins).every(isRecord)) && (value.runner === void 0 || isRecord(value.runner) && isFunction(value.runner.describe) && isFunction(value.runner.invoke)) && (value.limits === void 0 || isRecord(value.limits));
|
|
100
116
|
/** The deployment's database drivers, each held to what a `db` task calls — or why one is not, as the server starts. */
|
|
101
117
|
var dbDriversOf = (drivers) => {
|
|
102
118
|
if (drivers === void 0) return;
|
|
@@ -107,7 +123,7 @@ var dbDriversOf = (drivers) => {
|
|
|
107
123
|
/** The deployment's functions config, held to what runs a space's code — or why it is not, as the server starts. */
|
|
108
124
|
var functionsConfigOf = (config) => {
|
|
109
125
|
if (config === void 0) return;
|
|
110
|
-
if (!isFunctionsConfig(config)) throw new Error("`functions` is not what @plitzi/sdk-server/functions configures: `native` definitions, a `runner` with `describe` and `invoke`, `limits`.");
|
|
126
|
+
if (!isFunctionsConfig(config)) throw new Error("`functions` is not what @plitzi/sdk-server/functions configures: `native` definitions, `plugins` by type, a `runner` with `describe` and `invoke`, `limits`.");
|
|
111
127
|
return config;
|
|
112
128
|
};
|
|
113
129
|
//#endregion
|
|
@@ -14,7 +14,7 @@ import { createPageServer } from "./server/pageServer.js";
|
|
|
14
14
|
* caches or plugin manager this one does. */
|
|
15
15
|
/**
|
|
16
16
|
* Fills in `getRscData` from whatever can produce a server element's data — `connectors`, `action.lookups`, the
|
|
17
|
-
* files of `publicDir` — when the deployment did not write one.
|
|
17
|
+
* files of `dataDir` and `publicDir` — when the deployment did not write one.
|
|
18
18
|
*
|
|
19
19
|
* The lookups are already here and the assembly is entirely this package's — a resolver over `resolveRscData`.
|
|
20
20
|
* Leaving it out meant every deployment passed the same lookups twice: once as config, for the write endpoint,
|
|
@@ -25,7 +25,7 @@ import { createPageServer } from "./server/pageServer.js";
|
|
|
25
25
|
* with no configuration missing anywhere.
|
|
26
26
|
*/
|
|
27
27
|
var withConnectorRsc = (config) => {
|
|
28
|
-
if (config.adapters.getRscData || !config.connectors && !config.action?.lookups && !config.publicDir) return config;
|
|
28
|
+
if (config.adapters.getRscData || !config.connectors && !config.action?.lookups && !config.publicDir && !config.dataDir) return config;
|
|
29
29
|
const resolved = {
|
|
30
30
|
...config,
|
|
31
31
|
adapters: { ...config.adapters }
|
|
@@ -39,6 +39,7 @@ var withConnectorRsc = (config) => {
|
|
|
39
39
|
connectors: config.connectors ? connectorLookupsOf(config.connectors) : void 0,
|
|
40
40
|
actions,
|
|
41
41
|
publicDir: config.publicDir,
|
|
42
|
+
dataDir: config.dataDir,
|
|
42
43
|
elementTimeoutMs: config.rsc?.elementTimeoutMs
|
|
43
44
|
});
|
|
44
45
|
return resolved;
|
|
@@ -60,11 +60,16 @@ var createSocialAuthRouteHandlers = ({ social, cookies, issueSession, onError })
|
|
|
60
60
|
* uses: a relative path or an allowed origin is honoured, anything else silently becomes the default. A
|
|
61
61
|
* missing or unresolved parameter lands on the default too, which is what makes it safe for a template to
|
|
62
62
|
* pass a token it could not fill in.
|
|
63
|
+
*
|
|
64
|
+
* `?fallback=` is where to go instead when `redirect` is refused, vetted by the same judgement — a way BACK
|
|
65
|
+
* from the screen, whose default ("you are signed in") is the wrong place for somebody who never signed in.
|
|
63
66
|
*/
|
|
64
67
|
method: "GET",
|
|
65
68
|
path: "/continue",
|
|
66
69
|
handle: (req, res) => {
|
|
67
|
-
|
|
70
|
+
const redirect = req.query?.redirect;
|
|
71
|
+
const vetted = social.sanitizeRedirect(redirect);
|
|
72
|
+
res.redirect(vetted === redirect ? vetted : social.sanitizeRedirect(req.query?.fallback));
|
|
68
73
|
return Promise.resolve();
|
|
69
74
|
}
|
|
70
75
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Stage } from '../types';
|
|
2
|
+
import { PluginEntry } from '@plitzi/sdk-shared';
|
|
2
3
|
/** Where an open page listens for "the space changed: load again", in development. */
|
|
3
4
|
export declare const DEV_RELOAD_PATH = "/__plitzi/reload";
|
|
4
5
|
/**
|
|
@@ -8,11 +9,21 @@ export declare const DEV_RELOAD_PATH = "/__plitzi/reload";
|
|
|
8
9
|
*
|
|
9
10
|
* Only with `devReload` on: every open page keeps a connection for it, which a server that never calls `reloadPages`
|
|
10
11
|
* — a published one, or a platform's — would pay for and get nothing from. The template listens only then too.
|
|
12
|
+
*
|
|
13
|
+
* Each connection is greeted with this process's `boot`, and a page that reconnects to a different one loads again: the
|
|
14
|
+
* server restarted — `start:dev` saw its code change (`main.ts`, its options, its actions, `src/functions/`) — and
|
|
15
|
+
* what is on screen was built by the one before. Without it the page kept the old plugin until somebody reloaded it,
|
|
16
|
+
* which reads as "my change did nothing".
|
|
11
17
|
*/
|
|
12
18
|
export declare const createDevReload: () => {
|
|
13
19
|
stage: Stage;
|
|
14
20
|
/** Every page open on this server loads again. */
|
|
15
21
|
reload: () => void;
|
|
22
|
+
/**
|
|
23
|
+
* A plugin built again: every open page imports its new bundle and swaps it where it is drawn (`keyName` is what
|
|
24
|
+
* the page registered it as), and points its stylesheet at the new one — without loading again.
|
|
25
|
+
*/
|
|
26
|
+
plugin: ({ keyName, js, css }: PluginEntry) => void;
|
|
16
27
|
/** The streams ended — the server is going away, and each page reconnects to whichever comes up next. */
|
|
17
28
|
close: () => void;
|
|
18
29
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { openEventStream } from "../sse.js";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
//#region src/core/http/stages/devReload.ts
|
|
3
4
|
/** Where an open page listens for "the space changed: load again", in development. */
|
|
4
5
|
var DEV_RELOAD_PATH = "/__plitzi/reload";
|
|
@@ -9,9 +10,15 @@ var DEV_RELOAD_PATH = "/__plitzi/reload";
|
|
|
9
10
|
*
|
|
10
11
|
* Only with `devReload` on: every open page keeps a connection for it, which a server that never calls `reloadPages`
|
|
11
12
|
* — a published one, or a platform's — would pay for and get nothing from. The template listens only then too.
|
|
13
|
+
*
|
|
14
|
+
* Each connection is greeted with this process's `boot`, and a page that reconnects to a different one loads again: the
|
|
15
|
+
* server restarted — `start:dev` saw its code change (`main.ts`, its options, its actions, `src/functions/`) — and
|
|
16
|
+
* what is on screen was built by the one before. Without it the page kept the old plugin until somebody reloaded it,
|
|
17
|
+
* which reads as "my change did nothing".
|
|
12
18
|
*/
|
|
13
19
|
var createDevReload = () => {
|
|
14
20
|
const listening = /* @__PURE__ */ new Set();
|
|
21
|
+
const boot = randomUUID();
|
|
15
22
|
const stage = (ctx) => {
|
|
16
23
|
if (!ctx.config.devReload || ctx.req.path !== "/__plitzi/reload") return Promise.resolve(false);
|
|
17
24
|
const stream = openEventStream(ctx.rawRes, {
|
|
@@ -19,6 +26,7 @@ var createDevReload = () => {
|
|
|
19
26
|
retryMs: 1e3
|
|
20
27
|
});
|
|
21
28
|
listening.add(stream);
|
|
29
|
+
stream.send("hello", { boot });
|
|
22
30
|
ctx.raw.once("close", () => {
|
|
23
31
|
listening.delete(stream);
|
|
24
32
|
stream.close();
|
|
@@ -29,6 +37,15 @@ var createDevReload = () => {
|
|
|
29
37
|
stage,
|
|
30
38
|
/** Every page open on this server loads again. */
|
|
31
39
|
reload: () => listening.forEach((stream) => stream.send("reload", {})),
|
|
40
|
+
/**
|
|
41
|
+
* A plugin built again: every open page imports its new bundle and swaps it where it is drawn (`keyName` is what
|
|
42
|
+
* the page registered it as), and points its stylesheet at the new one — without loading again.
|
|
43
|
+
*/
|
|
44
|
+
plugin: ({ keyName, js, css }) => listening.forEach((stream) => stream.send("plugin", {
|
|
45
|
+
key: keyName,
|
|
46
|
+
js,
|
|
47
|
+
css
|
|
48
|
+
})),
|
|
32
49
|
/** The streams ended — the server is going away, and each page reconnects to whichever comes up next. */
|
|
33
50
|
close: () => {
|
|
34
51
|
listening.forEach((stream) => stream.close());
|
|
@@ -20,5 +20,7 @@ export interface HttpServerParts {
|
|
|
20
20
|
beforeFork?: () => Promise<void>;
|
|
21
21
|
/** The open pages told to load again (`SSRServer.reloadPages`) — a page server in `devMode`; nothing elsewhere. */
|
|
22
22
|
reloadPages?: () => void;
|
|
23
|
+
/** A plugin's server half replaced while the server runs (`SSRServer.functions`) — a page server that runs actions. */
|
|
24
|
+
setPluginFunctions?: (plugin: string, definition: unknown) => void;
|
|
23
25
|
}
|
|
24
26
|
export declare const createHttpServer: (config: SSRServerConfig, makeHandlerForPort: (port: number) => Handler, parts: HttpServerParts) => SSRServer;
|
|
@@ -35,6 +35,9 @@ var createHttpServer = (config, makeHandlerForPort, parts) => {
|
|
|
35
35
|
cache: channel && parts.cache ? relayCache(parts.cache, `${channel}.cache`) : parts.cache,
|
|
36
36
|
plugins: channel && parts.forgetPlugins ? relayPlugins(parts.plugins, parts.forgetPlugins, `${channel}.plugins`, label) : parts.plugins,
|
|
37
37
|
reloadPages: parts.reloadPages ?? (() => void 0),
|
|
38
|
+
functions: { setPlugin: parts.setPluginFunctions ?? (() => {
|
|
39
|
+
throw new Error(`[${label}] This server runs no actions, so no plugin's functions`);
|
|
40
|
+
}) },
|
|
38
41
|
listen(port, host = "0.0.0.0") {
|
|
39
42
|
if (plan.requested !== void 0 && role !== "worker") serverLog.warn(label, `${plan.requested} workers asked for, ${plan.count} cores to run them on: starting ${plan.count}`);
|
|
40
43
|
if (role === "primary") {
|