@plitzi/sdk-server 0.38.3 → 0.38.5
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 +530 -0
- package/README.md +142 -24
- 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/http/stages/images.js +1 -1
- 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/images/imageProxy.js +19 -1
- package/dist/modules/rsc/connectorRscData.d.ts +3 -1
- package/dist/modules/rsc/connectorRscData.js +9 -5
- 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 -9
- package/dist/modules/rsc/publicFileResolver.js +61 -23
- 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/prepareRender.js +45 -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 +41 -12
- 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 +9 -5
- package/skills/plitzi-authoring/SKILL.md +24 -20
- package/skills/plitzi-authoring/recipes/accordion.ts +58 -0
- package/skills/plitzi-authoring/recipes/link-to-a-section.ts +1 -1
- package/skills/plitzi-authoring/recipes/plugin.ts +1 -1
- package/skills/plitzi-authoring/recipes/server-data.ts +65 -0
- package/skills/plitzi-authoring/recipes/show-data.ts +3 -2
- package/skills/plitzi-authoring/recipes/style-a-page.ts +1 -1
- package/skills/plitzi-authoring/recipes/typed-data.ts +1 -1
- package/skills/plitzi-authoring/reference/accessibility.md +6 -2
- package/skills/plitzi-authoring/reference/authoring-errors.md +16 -5
- package/skills/plitzi-authoring/reference/colours-and-motion.md +8 -2
- package/skills/plitzi-authoring/reference/data-and-visibility.md +38 -26
- package/skills/plitzi-authoring/reference/drawing.md +24 -0
- package/skills/plitzi-authoring/reference/efficiency.md +27 -1
- package/skills/plitzi-authoring/reference/elements-and-styles.md +5 -9
- package/skills/plitzi-authoring/reference/flows.md +3 -0
- package/skills/plitzi-authoring/reference/layouts.md +8 -23
- package/skills/plitzi-authoring/reference/lists.md +1 -1
- 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 +16 -2
- package/skills/plitzi-authoring/reference/snippets-and-export.md +2 -2
- package/skills/plitzi-authoring/reference/structure.md +32 -1
- package/skills/plitzi-authoring/reference/templates.md +6 -3
- package/skills/plitzi-authoring/reference/testing.md +10 -3
- 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
|
|
|
@@ -158,16 +159,21 @@ serve it — worth running over anything that arrives as a file.
|
|
|
158
159
|
A space on Plitzi comes out whole, as a project wired to this server: `npx @plitzi/cli create my-site --from <space>`
|
|
159
160
|
writes its pages as authoring code, its actions as `defineAction` code, its functions, the source of its plugins and
|
|
160
161
|
runtime, and its files into `public/` — a draft, or any published snapshot (`--environment`, `--revision`) — and
|
|
161
|
-
`plitzi pull` keeps it in step
|
|
162
|
+
`plitzi pull` keeps it in step, and `plitzi push` puts what changed in it back on the space. See
|
|
163
|
+
`docs/en/projects-from-spaces.md` in the workspace.
|
|
162
164
|
|
|
163
165
|
## Configuration
|
|
164
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
|
+
|
|
165
171
|
| Option | Type | Default | Description |
|
|
166
172
|
|---|---|---|---|
|
|
167
173
|
| `httpVersion` | `1 \| 2 \| 3` | `2` with `tls`, else `1` | HTTP protocol version. Falls back to the nearest available lower version. |
|
|
168
174
|
| `tls` | `{ key, cert, minVersion? }` | — | TLS key and certificate. Required for versions 2 and 3; optional for version 1. |
|
|
169
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`. |
|
|
170
|
-
| `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. |
|
|
171
177
|
| `assetVersion` | `string` | — | Cache-buster appended as `?v=<assetVersion>` to all default SDK asset URLs. Compute from file mtime or package version at startup. |
|
|
172
178
|
| `cacheTtlMs` | `number` | `300000` | TTL in milliseconds for the SSR render cache. Set to `0` to disable. |
|
|
173
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. |
|
|
@@ -176,11 +182,12 @@ runtime, and its files into `public/` — a draft, or any published snapshot (`-
|
|
|
176
182
|
| `signIn` | `SSRSignInConfig` | — | Sign visitors in by redirect through an OAuth 2.1 authorization server: `GET /auth/sign-in?return=/x` registers this host as a client, sends the browser out with PKCE (state in a `__Host-` cookie), and `/auth/sign-in/callback` redeems the code server to server and hands the token to `exchangeCredential`, which makes the session here. Needs the `exchangeCredential` adapter. |
|
|
177
183
|
| `templateFn` | `SSRTemplateFn` | built-in EJS template | Custom render function. Receives all template params and returns an HTML string. |
|
|
178
184
|
| `plugins` | `Record<string, PluginSource>` | — | Named plugin definitions. Compiled or copied on first use and cached for `pluginsTtlMs`. |
|
|
179
|
-
| `pluginsCacheDir` | `string` |
|
|
185
|
+
| `pluginsCacheDir` | `string` | `tmp/.sdk-plugins` | Directory where compiled plugin files are stored — under `tmp/`, with everything else the server writes for itself. |
|
|
180
186
|
| `pluginsTtlMs` | `number` | `604800000` | TTL in milliseconds for compiled plugins (default: 1 week). |
|
|
181
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. |
|
|
182
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. |
|
|
183
|
-
| `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. |
|
|
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). |
|
|
184
191
|
| `static` | `Record<string, string>` | — | URL prefix → filesystem path mappings for static file serving. |
|
|
185
192
|
| `images` | `{ domains, cacheDir? }` | — | Pictures from other sites resized at `/_plitzi/img`, for an `image` to offer as a `srcset` (see [Images](#images)). |
|
|
186
193
|
| `ssrOnly` | `boolean` | `false` | Omit client-side JS from the rendered page. Useful for verifying SSR HTML without hydration. |
|
|
@@ -195,6 +202,20 @@ runtime, and its files into `public/` — a draft, or any published snapshot (`-
|
|
|
195
202
|
| `logger` | `ServerLogger` | the console | Where it says it: one structured `ServerLogEvent` stream. `consoleLogger` prints each as one line. |
|
|
196
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. |
|
|
197
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`). |
|
|
198
219
|
|
|
199
220
|
### HTTP version behaviour
|
|
200
221
|
|
|
@@ -296,13 +317,17 @@ const server = createServer({
|
|
|
296
317
|
server.listen(3001);
|
|
297
318
|
```
|
|
298
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
|
+
|
|
299
324
|
### `JsonAdaptersConfig`
|
|
300
325
|
|
|
301
326
|
| Option | Type | Description |
|
|
302
327
|
|---|---|---|
|
|
303
328
|
| `offlineData` | `string` | Path to a single JSON file used for every request. |
|
|
304
329
|
| `offlineData` | `(spaceId, environment, revision?) => string` | Function returning the path for the requested space. |
|
|
305
|
-
| `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. |
|
|
306
331
|
| `deployment` | `string` | Path to a JSON file containing an `SSRSpaceDeployment` object. |
|
|
307
332
|
| `deployment` | `SSRSpaceDeployment` | Inline deployment object used for every request. |
|
|
308
333
|
| `deployment` | `Record<hostname, SSRSpaceDeployment>` | Per-hostname map. Use `'*'` as a catch-all. |
|
|
@@ -397,11 +422,13 @@ createServer({ images: { domains: ['images.example.com', '*.cdn.example.com'] },
|
|
|
397
422
|
|
|
398
423
|
An `image` whose `src` is another site's then offers the browser a `srcset` of widths this server makes (320 to 1920
|
|
399
424
|
px) at `/_plitzi/img?url=…&w=…`, in AVIF or WebP when the browser takes them. Everything is kept on disk (`cacheDir`,
|
|
400
|
-
default
|
|
425
|
+
default `tmp/images`): each original is downloaded once, and each size is made once from it. A week on, the kept
|
|
401
426
|
files keep answering while the original is asked for with its `ETag` / `Last-Modified` — unchanged, nothing is resized
|
|
402
427
|
again; changed, its sizes are made anew. A picture that could not be fetched is not asked for again for five minutes.
|
|
403
428
|
Only the listed hosts are fetched, every redirect is held to the same
|
|
404
|
-
list and to the rule every outbound request follows (no private network)
|
|
429
|
+
list and to the rule every outbound request follows (no private network). An SVG is never resized nor served from
|
|
430
|
+
this origin: the proxy answers it with a redirect to its own address (`307`), so the picture still shows. An `image`
|
|
431
|
+
with `resize: false` keeps its `src` as written. Resizing needs `sharp`
|
|
405
432
|
(an optional peer): without it pictures are passed through and kept, and the server says so once.
|
|
406
433
|
|
|
407
434
|
### Public directory
|
|
@@ -417,8 +444,30 @@ createServer({
|
|
|
417
444
|
|
|
418
445
|
The lookup order for a request is: `publicDir` → `static` prefix routes → SSR renderer.
|
|
419
446
|
|
|
447
|
+
**Everything in `publicDir` is on the internet.** A file there is served to anyone who asks for it, as it is — no
|
|
448
|
+
session, no permission, no check. Never put a secret, a key, an `.env`, a private document, a database dump or data only
|
|
449
|
+
some visitors may read in it: that is served by a server action whose `access` checks who is asking.
|
|
450
|
+
|
|
420
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.
|
|
421
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
|
+
|
|
422
471
|
## Compression
|
|
423
472
|
|
|
424
473
|
Responses are compressed based on the `Accept-Encoding` request header. By default the server offers Brotli, then
|
|
@@ -510,15 +559,19 @@ All fields are optional and combined with AND logic. Calling `invalidate()` with
|
|
|
510
559
|
|
|
511
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`.
|
|
512
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
|
+
|
|
513
566
|
```ts
|
|
514
|
-
import type { SSRSpaceDeployment } from '@plitzi/sdk-
|
|
567
|
+
import type { SSRSpaceDeployment } from '@plitzi/sdk-shared';
|
|
515
568
|
|
|
516
569
|
const server = createServer({
|
|
517
570
|
plugins: {
|
|
518
571
|
// From a source file — compiled to ESM with esbuild
|
|
519
572
|
'my-chart': {
|
|
520
573
|
js: '/abs/path/to/MyChart.tsx',
|
|
521
|
-
css: '/abs/path/to/MyChart.css', // filesystem path — copied to
|
|
574
|
+
css: '/abs/path/to/MyChart.css', // filesystem path — copied to tmp/.sdk-plugins
|
|
522
575
|
version: '1.2.0'
|
|
523
576
|
},
|
|
524
577
|
// Pre-compiled local file — copied as-is (version defaults to '1.0.0')
|
|
@@ -637,6 +690,10 @@ Compiled and copied plugin files are served under `/sdk-plugins/{name}/`:
|
|
|
637
690
|
|
|
638
691
|
Plugin responses are compressed with Brotli or gzip like all other responses.
|
|
639
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
|
+
|
|
640
697
|
### TTL and invalidation
|
|
641
698
|
|
|
642
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.
|
|
@@ -665,13 +722,37 @@ server.plugins.register('my-chart', {
|
|
|
665
722
|
|
|
666
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.
|
|
667
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
|
+
|
|
668
747
|
## React Server Components (RSC)
|
|
669
748
|
|
|
670
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()`.
|
|
671
750
|
|
|
672
751
|
### Schema setup
|
|
673
752
|
|
|
674
|
-
|
|
753
|
+
Server data is on unless the schema turns it off with `"rsc": { "enabled": false }` — the page render and the
|
|
754
|
+
default `getRscData` (`connectorRscData`) read a missing `rsc` alike. `authorSpace` writes it out for a space with a
|
|
755
|
+
server provider:
|
|
675
756
|
|
|
676
757
|
```json
|
|
677
758
|
{
|
|
@@ -710,7 +791,7 @@ Implement `getRscData` in your adapters to serve data from the `/_rsc` endpoint.
|
|
|
710
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):
|
|
711
792
|
|
|
712
793
|
```ts
|
|
713
|
-
import type { SSRAdapters, SSRRscContext, SSRRscData } from '@plitzi/sdk-
|
|
794
|
+
import type { SSRAdapters, SSRRscContext, SSRRscData } from '@plitzi/sdk-shared';
|
|
714
795
|
|
|
715
796
|
const getRscData = async ({ user, ids, loadOfflineData }: SSRRscContext): Promise<SSRRscData> => {
|
|
716
797
|
// Only serve data when the schema has RSC enabled. `loadOfflineData` joins the read the page render already
|
|
@@ -773,6 +854,10 @@ type SSRRscData = {
|
|
|
773
854
|
|
|
774
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'`.
|
|
775
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
|
+
|
|
776
861
|
### `/_rsc` endpoint
|
|
777
862
|
|
|
778
863
|
The server automatically registers `GET /_rsc` when `adapters.getRscData` is provided. The endpoint:
|
|
@@ -780,7 +865,7 @@ The server automatically registers `GET /_rsc` when `adapters.getRscData` is pro
|
|
|
780
865
|
1. Reads `spaceId`, `environment`, and `revision` from the resolved `spaceDeployment` context.
|
|
781
866
|
2. Reads the authenticated user from `ctx.user`.
|
|
782
867
|
3. Reads optional `?ids=elem1,elem2` for partial refresh.
|
|
783
|
-
4. Calls `adapters.getRscData({ req, spaceId, environment, revision, user, ids, loadOfflineData })`.
|
|
868
|
+
4. Calls `adapters.getRscData({ req, spaceId, environment, revision, user, ids, loadOfflineData, signal })`.
|
|
784
869
|
5. Returns a JSON payload:
|
|
785
870
|
|
|
786
871
|
```json
|
|
@@ -815,6 +900,7 @@ createServer({
|
|
|
815
900
|
| `enabled` | `boolean` | `true` (when adapter provided) | Activate or deactivate the RSC endpoint. |
|
|
816
901
|
| `path` | `string` | `'/_rsc'` | URL path for the RSC endpoint. |
|
|
817
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. |
|
|
818
904
|
|
|
819
905
|
### Consuming RSC data in plugins
|
|
820
906
|
|
|
@@ -1109,7 +1195,7 @@ Credential comparison uses `crypto.timingSafeEqual` to prevent timing attacks. I
|
|
|
1109
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()`.
|
|
1110
1196
|
|
|
1111
1197
|
```ts
|
|
1112
|
-
import type { SSRMiddleware } from '@plitzi/sdk-
|
|
1198
|
+
import type { SSRMiddleware } from '@plitzi/sdk-shared';
|
|
1113
1199
|
|
|
1114
1200
|
const corsMiddleware: SSRMiddleware = (req, res, next) => {
|
|
1115
1201
|
res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com');
|
|
@@ -1243,7 +1329,9 @@ ends any other event stream, lets what is being answered finish, and cuts whatev
|
|
|
1243
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
|
|
1244
1330
|
SIGTERM until something kills the process.
|
|
1245
1331
|
|
|
1246
|
-
`@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
|
|
1247
1335
|
else is the `ActionJobQueue` contract written against your store — see
|
|
1248
1336
|
[server actions § self-hosted](https://github.com/plitzi/plitzi-workspace/blob/main/docs/en/server-actions.md#13-for-a-self-hosted-deployment).
|
|
1249
1337
|
|
|
@@ -1299,7 +1387,7 @@ URLs become `/sdk-assets/plitzi-sdk.js?v=<assetVersion>`. This is separate from
|
|
|
1299
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:
|
|
1300
1388
|
|
|
1301
1389
|
```ts
|
|
1302
|
-
import type { SSRTemplateFn } from '@plitzi/sdk-
|
|
1390
|
+
import type { SSRTemplateFn } from '@plitzi/sdk-shared';
|
|
1303
1391
|
|
|
1304
1392
|
const templateFn: SSRTemplateFn = ({ html, offlineData, jsPath, cssPath, plugins, react, reactDom, reactDomClient, reactJsx }) => `
|
|
1305
1393
|
<!doctype html>
|
|
@@ -1322,6 +1410,16 @@ createServer({ templateFn, adapters: { ... } });
|
|
|
1322
1410
|
|
|
1323
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.
|
|
1324
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
|
+
|
|
1325
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.
|
|
1326
1424
|
|
|
1327
1425
|
## Template props
|
|
@@ -1346,9 +1444,11 @@ return {
|
|
|
1346
1444
|
| `title` | `string` | Page `<title>`. Defaults to `'Plitzi App'`. |
|
|
1347
1445
|
| `jsPath` | `string` | URL for the SDK JS module. Defaults to `/sdk-assets/plitzi-sdk.js`. |
|
|
1348
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`. |
|
|
1349
1449
|
| `builderJsPath` | `string` | URL for the builder JS module. Omitted by default. |
|
|
1350
1450
|
| `builderCssPath` | `string` | URL for the builder stylesheet. Omitted by default. |
|
|
1351
|
-
| `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. |
|
|
1352
1452
|
| `react` | `string` | React ESM URL. Defaults to `esm.sh/react@19`. |
|
|
1353
1453
|
| `reactDom` | `string` | ReactDOM ESM URL. |
|
|
1354
1454
|
| `reactDomClient` | `string` | ReactDOM client ESM URL. |
|
|
@@ -1438,6 +1538,27 @@ package, not a snippet.
|
|
|
1438
1538
|
|
|
1439
1539
|
## Exported types
|
|
1440
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
|
+
|
|
1441
1562
|
```ts
|
|
1442
1563
|
import type {
|
|
1443
1564
|
SSRAdapters,
|
|
@@ -1446,13 +1567,13 @@ import type {
|
|
|
1446
1567
|
SSRResponseHelpers,
|
|
1447
1568
|
SSRMiddleware,
|
|
1448
1569
|
SSRMiddlewareNext,
|
|
1449
|
-
SSRContext,
|
|
1450
1570
|
SSRSpaceDeployment,
|
|
1451
1571
|
SSRTemplateProps,
|
|
1452
1572
|
SSRTemplateFn,
|
|
1453
1573
|
SSRCredential,
|
|
1454
1574
|
SSRUser,
|
|
1455
1575
|
SSRHeaders,
|
|
1576
|
+
SSRRscContext,
|
|
1456
1577
|
SSRRscData,
|
|
1457
1578
|
SSRRscConfig,
|
|
1458
1579
|
SSRServer,
|
|
@@ -1463,9 +1584,6 @@ import type {
|
|
|
1463
1584
|
PluginEntry,
|
|
1464
1585
|
PluginRegistry,
|
|
1465
1586
|
CacheFilter,
|
|
1466
|
-
CacheManager
|
|
1467
|
-
|
|
1468
|
-
AuthAdapters,
|
|
1469
|
-
AuthAdaptersConfig
|
|
1470
|
-
} from '@plitzi/sdk-server';
|
|
1587
|
+
CacheManager
|
|
1588
|
+
} from '@plitzi/sdk-shared';
|
|
1471
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
|
};
|