@plitzi/sdk-server 0.38.5 → 0.38.7
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 +218 -0
- package/README.md +100 -14
- package/dist/adapters/jsonAdapters.d.ts +8 -4
- package/dist/adapters/jsonAdapters.js +5 -9
- package/dist/core/auth/api.d.ts +4 -1
- package/dist/core/auth/api.js +14 -5
- package/dist/core/auth/createAuth.d.ts +7 -1
- package/dist/core/auth/createAuth.js +14 -0
- package/dist/core/auth/credentials.d.ts +2 -0
- package/dist/core/auth/credentials.js +4 -2
- package/dist/core/auth/session.d.ts +2 -0
- package/dist/core/auth/session.js +6 -5
- package/dist/core/createServer.js +25 -4
- package/dist/core/http/stages/devReload.d.ts +2 -2
- package/dist/core/http/stages/devReload.js +2 -2
- package/dist/env.d.ts +1 -0
- package/dist/env.js +19 -0
- package/dist/env.test.d.ts +1 -0
- package/dist/functions-api.d.ts +5 -0
- package/dist/helpers/buildServerInfo.js +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/modules/actions/runtime/projectData.d.ts +18 -0
- package/dist/modules/actions/runtime/projectData.js +69 -0
- package/dist/modules/actions/runtime/taskContext.d.ts +1 -1
- package/dist/modules/actions/runtime/taskContext.js +6 -3
- package/dist/modules/actions/types.d.ts +7 -1
- package/dist/modules/functions/build.js +10 -0
- package/dist/modules/functions/capabilities.js +5 -0
- package/dist/modules/functions/context.js +10 -2
- package/dist/modules/functions/contract.d.ts +5 -0
- package/dist/modules/functions/driver.js +4 -0
- package/dist/modules/functions/local.d.ts +3 -1
- package/dist/modules/functions/local.js +4 -2
- package/dist/modules/functions/protocol.d.ts +3 -0
- package/dist/modules/functions/scope.d.ts +1 -1
- package/dist/modules/images/imageProxy.js +1 -1
- package/dist/modules/mysql/accounts.js +1 -5
- package/dist/modules/project/plugins.d.ts +31 -0
- package/dist/modules/project/plugins.js +162 -0
- package/dist/modules/project/serveProject.d.ts +78 -0
- package/dist/modules/project/serveProject.js +200 -0
- package/dist/modules/project/serveProject.test.d.ts +1 -0
- package/dist/modules/project/watchSpace.d.ts +14 -0
- package/dist/modules/project/watchSpace.js +77 -0
- package/dist/modules/realtime/failure.d.ts +6 -0
- package/dist/modules/realtime/failure.js +11 -0
- package/dist/modules/realtime/handlers.js +13 -3
- package/dist/modules/realtime/hub.js +4 -1
- package/dist/modules/realtime/socket.js +3 -2
- package/dist/modules/rsc/connectorRscData.d.ts +7 -3
- package/dist/modules/rsc/connectorRscData.js +3 -4
- package/dist/modules/rsc/handler.d.ts +1 -0
- package/dist/modules/rsc/handler.js +11 -1
- package/dist/modules/rsc/publicFileResolver.d.ts +4 -8
- package/dist/modules/rsc/publicFileResolver.js +6 -10
- package/dist/modules/runtime/bundle.js +1 -1
- package/dist/modules/ssr/Component.d.ts +2 -1
- package/dist/modules/ssr/Component.js +2 -1
- package/dist/modules/ssr/prepareRender.js +14 -3
- package/dist/modules/ssr/registerExternalPlugins.js +1 -1
- package/dist/modules/ssr/vendor.test.d.ts +1 -0
- package/dist/modules/ssr/views/template.ejs +7 -3
- package/dist/plugins/compile.js +1 -1
- package/dist/plugins/copy.js +1 -1
- package/dist/plugins/manager.js +1 -1
- package/dist/project.d.ts +9 -0
- package/dist/project.js +2 -0
- package/package.json +16 -4
- package/skills/plitzi-authoring/CHEATSHEET.md +2 -2
- package/skills/plitzi-authoring/SKILL.md +3 -2
- package/skills/plitzi-authoring/reference/accessibility.md +2 -2
- package/skills/plitzi-authoring/reference/auth.md +75 -0
- package/skills/plitzi-authoring/reference/authoring-errors.md +18 -4
- package/skills/plitzi-authoring/reference/colours-and-motion.md +3 -2
- package/skills/plitzi-authoring/reference/efficiency.md +5 -2
- package/skills/plitzi-authoring/reference/elements-and-styles.md +23 -23
- package/skills/plitzi-authoring/reference/flows.md +11 -6
- package/skills/plitzi-authoring/reference/layouts.md +15 -18
- package/skills/plitzi-authoring/reference/plugins.md +1 -1
- package/skills/plitzi-authoring/reference/review-checklist.md +2 -1
- package/skills/plitzi-authoring/reference/structure.md +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,223 @@
|
|
|
1
1
|
# @plitzi/sdk-server
|
|
2
2
|
|
|
3
|
+
## 0.38.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c7bbc2b: ## A server project's `src/main.ts` is a few lines
|
|
8
|
+
|
|
9
|
+
- **`serveProject` from `@plitzi/sdk-server/project`**: the generated `src/main.ts` hands the space, its actions and
|
|
10
|
+
its options to the server, and the rest comes with the package — the port, the plugins of `src/plugins` and
|
|
11
|
+
`vendor/plugins`, the functions and runtime, `public/`, `src/data/`, the `kv` in `state/kv.json`, `/health`,
|
|
12
|
+
`tmp/dev-server.json` and the reloads while developing. A fix there arrives with `npm update`, not `plitzi upgrade`.
|
|
13
|
+
- **No more `tmp/space.json` while developing**: a saved space is authored again in its own process and handed to the
|
|
14
|
+
server over IPC (`plitzi/author.ts --ipc`, in place of `--out`); the server swaps it in memory and the open pages
|
|
15
|
+
reload, and a refused space keeps the last one that authored. `plitzi doctor` reports a leftover `tmp/space.json`.
|
|
16
|
+
- **`projectAuthoring()` in `@plitzi/sdk-authoring/node`**: what a project's space is checked against — its plugins'
|
|
17
|
+
declarations, its built plugins, its data files — once, for the server and `npm run author` alike. Neither it nor
|
|
18
|
+
`serveProject` takes a root: it is the working directory, where every script runs (`projectAuthoringAt(root)` for a
|
|
19
|
+
tool working on another folder).
|
|
20
|
+
- **`authorProjectSpace` and `projectSpace()` in `@plitzi/sdk-authoring/node`**: the project's space read from
|
|
21
|
+
`src/space/index.ts` — `dist/space/index.js` when the server runs built — once its root and layout are checked, and
|
|
22
|
+
authored. `src/main.ts` is `serveProject({ space: authorProjectSpace, actions, connectors, serverOptions })`, with no
|
|
23
|
+
import of the space; `plitzi/author.ts`, the visual tests and the CLI's checks read it the same way. A module that
|
|
24
|
+
exports no `space` is refused saying what to export (`ProjectSpaceError`).
|
|
25
|
+
- **`.env` lives at the root and Node reads it** as each script starts (`--env-file-if-exists=.env`; `start:dev`
|
|
26
|
+
preloads `@plitzi/sdk-server/env`, since a watched process would restart on every change beside the file). Settings
|
|
27
|
+
read at the top level of `src/config/serverOptions.ts` or the actions are set. Every project gets `.env` and
|
|
28
|
+
`.env.example`; `plitzi upgrade` removes the old `src/env.ts`.
|
|
29
|
+
- **One check of a project's layout** (`@plitzi/sdk-shared/project/layout`): a plugin folder with no entry, a
|
|
30
|
+
JavaScript entry, `src/plugin/` for `src/plugins/`, a `.env` inside `src/`, a space exported by default, a broken
|
|
31
|
+
`vendor/` plugin… The server refuses to boot listing every error with its fix, `npm run author` and the CLI's checks
|
|
32
|
+
refuse with the same words, `plitzi doctor` lists them under `layout` and `--fix` makes the ones with a single fix,
|
|
33
|
+
and `plitzi lint` points at doctor. A plugin folder added broken while developing is reported in the terminal
|
|
34
|
+
instead of failing in esbuild. Plugins may start at `index.tsx`.
|
|
35
|
+
- **`createJsonAdapters` serves documents held in memory**: `offlineData` may be a function returning them, not only a
|
|
36
|
+
path.
|
|
37
|
+
- **One source for a project's layout**, `@plitzi/sdk-shared/project/paths`; `src/config/serverOptions.ts` is typed
|
|
38
|
+
`ProjectServerOptions`. `plitzi upgrade files --write` brings an existing project's `main.ts` and `author.ts` up.
|
|
39
|
+
- **`npm run build` no longer fails on a plugin's stylesheet**: `tsconfig.build.json` includes `plitzi/assets.d.ts`,
|
|
40
|
+
which a space importing a plugin's declaration reaches through its component.
|
|
41
|
+
|
|
42
|
+
## A write refreshes what shows it
|
|
43
|
+
- **Writes now refresh server-driven providers.** `runServerAction`, `webHook`, `writeRecord` and the
|
|
44
|
+
`invalidateQueries` step reach `runtime: 'server'` api containers by id, by `query` URL, or all of them, just as
|
|
45
|
+
they reach cached browser requests. Before, they reached only the browser's query cache, so a saved write left the
|
|
46
|
+
page showing the old value until a reload. Hidden providers refresh when they are shown.
|
|
47
|
+
- **A refresh because something changed asks around every cache.** `performQuery`, writes and invalidations send
|
|
48
|
+
`Cache-Control: no-cache`; `/_rsc` resolves such a request again instead of serving its cached slice, and keeps the
|
|
49
|
+
new answer. A `refreshSeconds` timer and a "load more" page still go through the caches.
|
|
50
|
+
- **`refreshRsc`'s fourth argument is an options object**, `{ location?, fresh? }`.
|
|
51
|
+
|
|
52
|
+
## A space's functions read its data
|
|
53
|
+
- **`ctx.data('products.json')`** reads one file of the space's data (a project's `src/data/`), parsed and read-only,
|
|
54
|
+
as of the run's version. Plugins' functions are refused it.
|
|
55
|
+
- **Self-hosted servers read `dataDir` through the same lookup as the platform**, so `/data/<file>` providers and
|
|
56
|
+
`ctx.data` read the same files; `plitzi functions dev` reads `src/data`. `/data/../x.json` is now the provider's
|
|
57
|
+
error state instead of falling through to `publicDir`.
|
|
58
|
+
- **Importing a file outside `functions/` names the boundary and the fix**: read the space's data with
|
|
59
|
+
`ctx.data('<file>')`.
|
|
60
|
+
|
|
61
|
+
## Signing in on a self-hosted server
|
|
62
|
+
- **`createServer({ auth })` tells the pages it renders where to sign in** (`server.auth`: provider, endpoints and
|
|
63
|
+
the session hint cookie), so a space signs in with no auth settings declared. Settings a space does declare still
|
|
64
|
+
win, and a space that names another provider ignores the server's description. New `pageAuth` server option, for a
|
|
65
|
+
deployment whose `/auth` flows are served by another host.
|
|
66
|
+
- **A page with no auth provider no longer offers `auth.login` / `auth.logout`**, and a sign-in that cannot work says
|
|
67
|
+
why in the console (`[plitzi] auth.login: …`) instead of failing silently. What a visitor keeps there
|
|
68
|
+
(`keepState`, `paintedState`) is the browser's, as on any space without accounts: what was kept as a guest's on such
|
|
69
|
+
a page before is let go once, on the first visit after the upgrade.
|
|
70
|
+
- **Renewal verifies the refresh token itself.** `findByRefreshToken` no longer has to report `refreshExpiresAt`; when
|
|
71
|
+
it does, it overrides the token's own expiry.
|
|
72
|
+
- **New authoring reference `auth.md`**: providers, `authLogin` and its result, `authLogout`, `{{ auth.* }}`,
|
|
73
|
+
`accessLevel`, visitor roles, action `access` and `ctx.user`.
|
|
74
|
+
|
|
75
|
+
## Flows
|
|
76
|
+
- **Authoring refuses a `when` that asks a step for a key it never publishes** (`condition-field-unpublished`).
|
|
77
|
+
`whenSucceeded` / `whenFailed` after `authLogin` always ran the failure branch: they read a server action's
|
|
78
|
+
`status`. The message names what the step publishes and suggests testing `signedIn.ok` with `when`.
|
|
79
|
+
- **`explain` lists what a step publishes** (`Reads:`) and finds the auth steps by their builders (`authLogin`,
|
|
80
|
+
`authLogout`, `authRefreshDetails`). `authLogin({ username, password })` no longer needs `mode`.
|
|
81
|
+
|
|
82
|
+
## Elements
|
|
83
|
+
- **Images no longer default to a 140×140 square**: they are 140px wide and as tall as their ratio, so `width` or
|
|
84
|
+
`aspect-ratio` on a class just works. `object-fit` shows in the builder too.
|
|
85
|
+
- **`formControl` with `subType: 'switch'` renders an on/off switch** (`role="switch"`) — it rendered only its label.
|
|
86
|
+
Checkboxes and switches show a `true` default or binding as ticked.
|
|
87
|
+
- **The theme toggle drops the native button look**, and the **`current` style state covers the chosen item of any
|
|
88
|
+
set**: the link to the page shown, a pressed toggle, a selected tab. The exported `CURRENT_PAGE_SELECTOR` is now
|
|
89
|
+
`CURRENT_SELECTOR`.
|
|
90
|
+
- **Element defaults in the style inspector and the MCP catalogue match what the SDK renders**, held by a test. The
|
|
91
|
+
dialog's `bodyContainer` and `headerCloseButton` slots now reach the page; pagination lays out as declared.
|
|
92
|
+
- **Markdown takes a class for each part of the document** through its own slot (`heading`, `paragraph`, `link`,
|
|
93
|
+
`list`, `listItem`, `quote`, `code`, `codeBlock`, `image`, `table`, `anchor`), and `headingLinks: false` drops the
|
|
94
|
+
link each heading offers to itself while keeping its id, so `/page#anchor` links still work. Its description spells
|
|
95
|
+
out the HTML it outputs.
|
|
96
|
+
- **Tabs show every trigger.** The SDK hid every inactive tab item, the header's included, so a set of tabs showed
|
|
97
|
+
one tab and no way to the others; only the body's panels take turns now.
|
|
98
|
+
- **A link that names a query is current only on that query**: of `/?window=6h` and `/?window=24h`, the one shown
|
|
99
|
+
carries `aria-current` — with the path alone, every one of them did. A link with no query is still current on its
|
|
100
|
+
page whatever the query.
|
|
101
|
+
|
|
102
|
+
## The style language says what `customCss` used to
|
|
103
|
+
- **Pseudo-elements on a class**: `pseudos: { after: { css: { content: '"→"' }, states: { hover: { … } } } }` —
|
|
104
|
+
`before`, `after`, `marker`, `placeholder`, `first-letter`, `first-line`, `selection`, each in the class's states and
|
|
105
|
+
variants. Authoring refuses what draws nothing: a `before`/`after` with no `content`, a `content` without its quotes,
|
|
106
|
+
a property the browser drops on that pseudo-element.
|
|
107
|
+
- **Conditions on a class**: `conditions: { 'motion-reduce': { … }, 'container card (max-width: 30rem)': { … } }` —
|
|
108
|
+
reduced or allowed motion, and container widths, each with its states and pseudo-elements.
|
|
109
|
+
- **The space's `keyframes`**, validated and written at the top of `customCss`; an `animation-name` no keyframes declare
|
|
110
|
+
is warned (`animation-name-unknown`).
|
|
111
|
+
- **New states**: `expanded` (`aria-expanded`), `first`, `last`, `odd`, `even`; the tab panel on show is `current`, and
|
|
112
|
+
the theme toggle's icon of the scheme in use is `current` on its `icon` slot.
|
|
113
|
+
- **`ancestors['>']`** is the parent, whatever it wears — a closed component's part reacting to the element around it.
|
|
114
|
+
- The builder's style inspector, the MCP's definition ops (`pseudos`, `conditions`, and variants with their states) and
|
|
115
|
+
the export to code read and write all of it; an MCP `patchDefinition` no longer drops what it did not name. Rules in
|
|
116
|
+
`customCss` a class can hold now — `.link::after`, one under `@media (prefers-reduced-motion: reduce)`, `.row:first-child`,
|
|
117
|
+
`.row:nth-child(even)`, `.toggle[aria-expanded='true']` — are folded into the class on export and suggested by
|
|
118
|
+
`custom-css-class`.
|
|
119
|
+
|
|
120
|
+
## Elements take a class for each part
|
|
121
|
+
- **`formControl`**: `field` (the `<input>` itself), `icon` (the show-password button) and `requiredMark` slots; a switch's
|
|
122
|
+
knob reads in dark mode and takes `--plitzi-switch-thumb`, `--plitzi-switch-thumb-checked`, `--plitzi-switch-thumb-shadow`.
|
|
123
|
+
- **`pagination`**: `previous`, `page` (the one shown is its `current` state), `next`, `loadMore`.
|
|
124
|
+
- **`richText`** and **`markdown`**: a slot per part — `heading` and `heading1`…`heading6`, `strong`, `emphasis`,
|
|
125
|
+
`divider`, `tableHead`, `tableRow`, `tableHeaderCell`, `tableCell`, and the code block's frame, header, language and
|
|
126
|
+
copy button. Both heading slots dress one `<h3>`, so `heading-level-overridden` warns when they set the same property
|
|
127
|
+
and the stylesheet's order makes the general one win. A document is drawn by the SDK's own renderer
|
|
128
|
+
(`MarkdownDocument` in `@plitzi/sdk-elements`) — plitzi-ui's `Markdown` keeps GitHub's stylesheet for a library's
|
|
129
|
+
consumers — and reads as a document unstyled: headings, lists, code, tables and quotes get their type from the SDK's
|
|
130
|
+
base layer, at no weight (`:where()`), so a class on a part's slot replaces what it sets property by property. A
|
|
131
|
+
`richText` body reads the same.
|
|
132
|
+
- `custom-css-slot` suggests the slot for a `customCss` rule on a part's SDK class — only where a class on the slot can
|
|
133
|
+
say the rest of the selector (a state, a pseudo-element); `element-slot-unknown` warns of a slot an element does not
|
|
134
|
+
have.
|
|
135
|
+
|
|
136
|
+
## Links, refreshes and toasts
|
|
137
|
+
- **`current: 'section'` on a link** keeps it current on its page and every page under its path (`aria-current="true"`
|
|
138
|
+
there), styled by the `current` state — no `activeOn` binding for a section.
|
|
139
|
+
- **A refresh asks only about elements the page shown holds**: a provider on the page being left no longer sends a
|
|
140
|
+
`/_rsc` about the new address.
|
|
141
|
+
- **The toasts' parts are `notifications` fields**: `minHeight`, `fontWeight`, `lineHeight`, `iconSize`, `iconGap`,
|
|
142
|
+
`closeColor`, `closeOpacity`, `progressHeight`.
|
|
143
|
+
- **A page asked for with its space's token hydrates its own space.** On a host serving many spaces
|
|
144
|
+
(`?access-token=`), the space document fetched beside the page went without the token, so the host's own space
|
|
145
|
+
answered it and the browser hydrated another space's document over the page — a hydration error, and its tree
|
|
146
|
+
rebuilt. The document's address now carries the token the page was asked for with.
|
|
147
|
+
- **The SDK's stylesheet carries the space's variables from the server's HTML.** It read them from what a provider
|
|
148
|
+
below it publishes while the page renders, which the server had not done yet when it wrote the sheet; it now
|
|
149
|
+
resolves them from the document itself (`useResolvedVariables` of `@plitzi/sdk-shared/dataSource/hooks`), the same
|
|
150
|
+
on both sides.
|
|
151
|
+
- **A realtime message the pub/sub cannot deliver is answered, not fatal.** A publish that Redis refuses — a timeout under
|
|
152
|
+
load — is answered `503` with `reason: 'unavailable'`, by the `POST` and in the WebSocket's `ack` alike; a connect,
|
|
153
|
+
a disconnect or a revoke that fails is logged, on either transport. Each used to reject with nobody waiting, and the server's `unhandledRejection` handler
|
|
154
|
+
shut the whole process down.
|
|
155
|
+
|
|
156
|
+
## Reporting to Plitzi
|
|
157
|
+
- **`plitzi feedback`** starts a report of what broke, misled or cost time: it writes the page the report is laid out
|
|
158
|
+
in (`tmp/feedback/`) with the installed versions, the project and what `doctor` finds already in it, and tells the
|
|
159
|
+
agent how to fill it — each finding reproduced first, with evidence, impact, workaround and fix, no secrets — and to
|
|
160
|
+
publish it as an artifact whose link the developer sends. `--previous <url>` continues an earlier report.
|
|
161
|
+
|
|
162
|
+
## Pages that load on every browser
|
|
163
|
+
- **The import map comes before anything that loads a module**, in the page server's HTML, a published site's
|
|
164
|
+
(`index.hbs` on the platform), the SDK's and the builder's. React's `modulepreload` links came first, and a browser
|
|
165
|
+
that had fetched a module already — Chrome before 133 — ignored the map: every bare `import "react"` failed
|
|
166
|
+
("Failed to resolve module specifier") and the page never hydrated.
|
|
167
|
+
|
|
168
|
+
## The dev tools
|
|
169
|
+
- **A page loads the React its dev tools need.** The development build only while the visitor has the dev tools on
|
|
170
|
+
(shift+F12, within what the deployment allows); turned off, the next load is a production page, React included. It
|
|
171
|
+
followed the authorization alone, so a space that allowed debugging always shipped development React.
|
|
172
|
+
|
|
173
|
+
## check and lint
|
|
174
|
+
- **`plitzi check`, `push`, `lint` and `fix` hold the space to what `npm run author` and the server do**: its data
|
|
175
|
+
files too, so `push` no longer sends a space whose browser provider reads `src/data/` (`server-data-in-browser`).
|
|
176
|
+
- **`plitzi check` lists each list's rows as drawn and as held in its source** (`feed 4 of 8 rows`,
|
|
177
|
+
`hits not rendered (16 in its source)`). In `--json`, `lists` is now `{ id: { rendered, source } }`.
|
|
178
|
+
- **`plitzi check` no longer reports bindings inside a container the page isn't showing**, nor text "in the colour
|
|
179
|
+
behind it" because of a bar painted over it or a pane that clips it.
|
|
180
|
+
- **`window.__plitzi.sources()` no longer turns a shared array into `'[…]'`**, and a list row no longer replaces its
|
|
181
|
+
list's `items`.
|
|
182
|
+
- **`plitzi lint` says authoring suggestions are quieted with `quiet` on the element**, and reports a
|
|
183
|
+
`plitzi-lint-disable` comment that names one (`disable-names-suggestion`).
|
|
184
|
+
- **`plitzi check` says when a page sent the browser elsewhere** (a page for signed-in visitors, to the sign-in) instead
|
|
185
|
+
of reporting every element missing, and `--as <username>` signs in first through the server's `/auth` routes, the
|
|
186
|
+
password from `PLITZI_CHECK_PASSWORD`.
|
|
187
|
+
- **`plitzi upgrade` never replaces @plitzi packages installed locally** — a tarball, a link, a portal, an override: it
|
|
188
|
+
leaves them and the install, and says which and the command that would install the registry's.
|
|
189
|
+
- **`plitzi explain` covers every code**: authoring's, `plitzi lint`'s and the project layout's.
|
|
190
|
+
- **Authoring reads the global sources' fields**: `auth.authenticated` in a condition is refused with "did you mean
|
|
191
|
+
`isAuthenticated`?" (`global-field-unknown`), and `invalidateElements` must name providers that exist
|
|
192
|
+
(`element-ids-target`). A template's reads of `computed`, `flags` and the global sources come off the parsed
|
|
193
|
+
template: a string inside it (`'https://auth.acme.com'`) is no longer read as `auth.acme`.
|
|
194
|
+
- **`custom-css-notifications` no longer reads the rules `notifications` itself writes** as toasts dressed by hand.
|
|
195
|
+
- **A refused space or layout is a report, not a stack**: the server's entry point imports the space once the layout is
|
|
196
|
+
checked, and `npm start` and `npm run author` print every problem and exit with 1.
|
|
197
|
+
- **New authoring suggestion `class-overrides-class`**: one class's shorthand (`padding`) silently erases a longhand
|
|
198
|
+
(`padding-top`) another class on the same element writes out.
|
|
199
|
+
|
|
200
|
+
- Updated dependencies [c7bbc2b]
|
|
201
|
+
- @plitzi/sdk-shared@0.38.7
|
|
202
|
+
- @plitzi/plitzi-sdk@0.38.7
|
|
203
|
+
|
|
204
|
+
## 0.38.6
|
|
205
|
+
|
|
206
|
+
### Patch Changes
|
|
207
|
+
|
|
208
|
+
- 3bce653: ## Fixes
|
|
209
|
+
|
|
210
|
+
- **The dev tools draw their icons again.** The panel lives in a shadow root of its own, which the page's stylesheets
|
|
211
|
+
never reach, and Font Awesome moved to a sheet of its own in 0.38.5 (`plitzi-sdk-icons.css`): the badge and every tab
|
|
212
|
+
lost their icons. The panel now links that sheet inside its root, beside its own. Where it is served is the SDK's
|
|
213
|
+
new `sdkIconsStylePath` — the page server sends `/sdk-assets/plitzi-sdk-icons.css`, the same sheet the document
|
|
214
|
+
links — which the `iframe` and `shadow` render modes read too: they had `/plitzi-sdk-icons.css` hard-coded, a path a
|
|
215
|
+
page server does not serve.
|
|
216
|
+
|
|
217
|
+
- Updated dependencies [3bce653]
|
|
218
|
+
- @plitzi/sdk-shared@0.38.6
|
|
219
|
+
- @plitzi/plitzi-sdk@0.38.6
|
|
220
|
+
|
|
3
221
|
## 0.38.5
|
|
4
222
|
|
|
5
223
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -162,18 +162,96 @@ runtime, and its files into `public/` — a draft, or any published snapshot (`-
|
|
|
162
162
|
`plitzi pull` keeps it in step, and `plitzi push` puts what changed in it back on the space. See
|
|
163
163
|
`docs/en/projects-from-spaces.md` in the workspace.
|
|
164
164
|
|
|
165
|
+
## Projects
|
|
166
|
+
|
|
167
|
+
A project `npx @plitzi/cli create --mode server` writes runs this server through `serveProject`, from
|
|
168
|
+
`@plitzi/sdk-server/project` — its own entry, so a server that is not such a project never loads it. The project's
|
|
169
|
+
`src/main.ts` hands it what is the project's, and `serveProject` wires everything else from where the project keeps it
|
|
170
|
+
(the layout is `@plitzi/sdk-shared/project/paths`'s):
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { serveProject } from '@plitzi/sdk-server/project';
|
|
174
|
+
|
|
175
|
+
import { authorProjectSpace } from '@plitzi/sdk-authoring/node';
|
|
176
|
+
|
|
177
|
+
import { actions, connectors } from './actions/index.ts';
|
|
178
|
+
import { serverOptions } from './config/serverOptions.ts';
|
|
179
|
+
|
|
180
|
+
await serveProject({ space: authorProjectSpace, actions, connectors, serverOptions });
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`space` is called once the project's root and layout are checked: `authorProjectSpace` reads `src/space/index.ts`'s
|
|
184
|
+
`space` — `dist/space/index.js` when the server runs what `build` emitted (`projectModule` of
|
|
185
|
+
`@plitzi/sdk-shared/project/root`, the rule the runtime is found by too) — and authors it with `projectAuthoring()`. A
|
|
186
|
+
module that exports no `space` is refused saying what to export (`ProjectSpaceError`), and a space that does not author
|
|
187
|
+
with every problem (`SpaceRefusedError`): each printed as the report it is, and the process exits with 1.
|
|
188
|
+
|
|
189
|
+
The project is the working directory — where every script of its `package.json` runs it, and where a deployment runs
|
|
190
|
+
`node dist/main.js` — never worked out from where a file is: `serveProject` and `authorProjectSpace` refuse to start
|
|
191
|
+
anywhere else, naming what is missing (`package.json`, `src/`; `projectRoot` of `@plitzi/sdk-shared/project/root`, one
|
|
192
|
+
check for both). Its settings are in `process.env` before any module loads: the scripts start Node with
|
|
193
|
+
`--env-file-if-exists=.env`, and no file of the project reads `.env`. `start:dev`, which Node watches, preloads it
|
|
194
|
+
instead — `node --import @plitzi/sdk-server/env --watch-path=… src/main.ts` — since Node, handed an env file under
|
|
195
|
+
`--watch-path`, watches the folder it is in and restarts on every write there: the server's own `tmp/`, from boot.
|
|
196
|
+
|
|
197
|
+
Both hold the project to its layout before anything else (`assertProjectLayout` of `@plitzi/sdk-shared/project/layout`,
|
|
198
|
+
what `plitzi doctor` says too): a part where the server would read nothing — a plugin folder with no `index.ts` (or
|
|
199
|
+
`index.tsx`), code in `src/plugin/`, `.env` in `src/`, a built plugin with no manifest — stops the boot with every error
|
|
200
|
+
at once (`ProjectLayoutError`, each with its fix; `findings` holds them as data), and what works and should not stay —
|
|
201
|
+
a plugin with no `declaration.ts`, no `.env.example` — is printed while developing, `[layout] …`.
|
|
202
|
+
|
|
203
|
+
| Option | What it is |
|
|
204
|
+
|---|---|
|
|
205
|
+
| `space` | The space held in the project: a function that authors it — `authorProjectSpace` — called once the project's layout is checked (a save while developing is authored again by `plitzi/author.ts`, in a process of its own). Served from memory; its `warnings` are printed at boot. |
|
|
206
|
+
| `cloud` | Instead of `space`: `{ name }` — the space stays in Plitzi and is read with `createCloudAdapters`, from `PLITZI_HOST_KEY` (required), `PLITZI_ENVIRONMENT` (`main` by default), `PLITZI_REVISION` and `PLITZI_SERVER_URL`. `name` is what `/health` answers with. |
|
|
207
|
+
| `actions`, `connectors` | The space's server actions, found by their id, and the connectors they call. Left out, the server runs no actions. |
|
|
208
|
+
| `serverOptions` | The project's `src/config/serverOptions.ts`, typed `ProjectServerOptions`: what `createServer` takes but for what `serveProject` wires, spread before it — `logLevel` and `logger` (`warn`, to the console; `--verbose` on the command line: `info`), `auth`, `images`, `rsc`, `action.limits`, `action.kv` are the project's to set. |
|
|
209
|
+
|
|
210
|
+
What it wires from the project:
|
|
211
|
+
|
|
212
|
+
- **The port.** `PORT`, or 8080 — while developing, the next free one from there (`freePort`). `HOST`, loopback by
|
|
213
|
+
default (`0.0.0.0` in a container). `PUBLIC_URL` is where people reach it, behind a proxy.
|
|
214
|
+
- **Its plugins.** Every folder of `src/plugins` under its name in camelCase, built from its `index.ts` (or
|
|
215
|
+
`index.tsx`, `pluginEntry`) and rendered
|
|
216
|
+
on the server (`action: 'compile'`), with its server half (`functions/`); every plugin of `vendor/plugins`, run as it
|
|
217
|
+
was built, beside its `plugin-manifest.json` — and the deployment's `pluginNames` naming them all.
|
|
218
|
+
- **Its code.** `src/functions/` (`loadFunctions`), and the space's runtime — `src/runtime/index.ts`, or
|
|
219
|
+
`dist/runtime/index.js` when the process was started on what `build` emitted (`process.argv[1]` under `dist/`), or
|
|
220
|
+
`vendor/runtime.bundle` — in this process, its endpoints answered before anything else.
|
|
221
|
+
- **Its files.** `public/` served as it is, `src/data/` read and never served (`dataDir`), the `kv` in `state/kv.json`
|
|
222
|
+
(`createFileKv`), `PLITZI_SIGNING_SECRET` what `ctx.sign` signs with.
|
|
223
|
+
- **Where it is.** `/health` answers with the space's permanent URL (or the cloud `name`), and `tmp/dev-server.json`
|
|
224
|
+
records the name, the port and the URL for `plitzi check`, `shot` and `visual`. A signal closes it (`closeOnSignals`).
|
|
225
|
+
|
|
226
|
+
While developing — `NODE_ENV` other than `production` — `devMode` and `devReload` are on, and:
|
|
227
|
+
|
|
228
|
+
- **A save to the space is authored again** by the project's `plitzi/author.ts`, in a process of its own (an import is
|
|
229
|
+
never read twice in one process), run with `--ipc`: it hands the documents back over the IPC channel, the server
|
|
230
|
+
swaps them in memory and every open page loads again (`reloadPages`). What the script refuses it prints, and the page
|
|
231
|
+
keeps the last space that authored. A save to the server's own code — `main.ts`, `src/config/`,
|
|
232
|
+
`src/actions/`, `src/connectors/`, `src/functions/`, `src/runtime/` — is `start:dev`'s to restart on; of a plugin,
|
|
233
|
+
only its `declaration.ts` is the space's.
|
|
234
|
+
- **A plugin folder added** is registered and the open pages load again; one removed is turned off; a plugin's
|
|
235
|
+
`functions/` changed is loaded again in place. A plugin's component changed is the server's own `devReload`. A folder
|
|
236
|
+
it cannot build — no entry yet, a name that is no type — or a `functions/` with no `index.ts` is said in the
|
|
237
|
+
terminal as the layout check says it (`[plugins] …`, once until it changes), and the server goes on; the folder is
|
|
238
|
+
registered once it is whole.
|
|
239
|
+
|
|
240
|
+
It answers `{ server, url, close }`: the `SSRServer`, where it listens, and what stops it — watching, the server and the
|
|
241
|
+
runtime — for a caller that stops it itself.
|
|
242
|
+
|
|
165
243
|
## Configuration
|
|
166
244
|
|
|
167
245
|
`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
|
|
169
|
-
wires itself.
|
|
246
|
+
options with: a `plitzi create` project keeps them in `src/config/serverOptions.ts`, typed `ProjectServerOptions` — it less
|
|
247
|
+
what `serveProject` wires itself (see [Projects](#projects)).
|
|
170
248
|
|
|
171
249
|
| Option | Type | Default | Description |
|
|
172
250
|
|---|---|---|---|
|
|
173
251
|
| `httpVersion` | `1 \| 2 \| 3` | `2` with `tls`, else `1` | HTTP protocol version. Falls back to the nearest available lower version. |
|
|
174
252
|
| `tls` | `{ key, cert, minVersion? }` | — | TLS key and certificate. Required for versions 2 and 3; optional for version 1. |
|
|
175
253
|
| `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`. |
|
|
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 `
|
|
254
|
+
| `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 `serveProject` does while developing) — 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. |
|
|
177
255
|
| `assetVersion` | `string` | — | Cache-buster appended as `?v=<assetVersion>` to all default SDK asset URLs. Compute from file mtime or package version at startup. |
|
|
178
256
|
| `cacheTtlMs` | `number` | `300000` | TTL in milliseconds for the SSR render cache. Set to `0` to disable. |
|
|
179
257
|
| `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. |
|
|
@@ -187,7 +265,7 @@ wires itself.
|
|
|
187
265
|
| `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. |
|
|
188
266
|
| `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. |
|
|
189
267
|
| `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
|
|
268
|
+
| `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>`, and a function reads it with `ctx.data(file)` — the space's `getData` when the deployment gives none. See [Project data](#project-data). |
|
|
191
269
|
| `static` | `Record<string, string>` | — | URL prefix → filesystem path mappings for static file serving. |
|
|
192
270
|
| `images` | `{ domains, cacheDir? }` | — | Pictures from other sites resized at `/_plitzi/img`, for an `image` to offer as a `srcset` (see [Images](#images)). |
|
|
193
271
|
| `ssrOnly` | `boolean` | `false` | Omit client-side JS from the rendered page. Useful for verifying SSR HTML without hydration. |
|
|
@@ -206,10 +284,11 @@ wires itself.
|
|
|
206
284
|
| `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
285
|
| `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
286
|
| `auth` | `Auth` | — | A `createAuth(...)` result: fills the three auth adapters in and brings the cookie naming. See [User authentication](#user-authentication). |
|
|
287
|
+
| `pageAuth` | `Omit<ServerAuth, 'sessionHintCookie'>` | from `auth` | What the rendered pages are told about signing in (`server.auth`): the provider and its endpoints, the hint cookie added per host. Name it when `/auth` is served by another host. |
|
|
209
288
|
| `sessionRenewal` | `{ url } \| false` | on with `auth` | Where a page request whose access cookie died, its refresh cookie alive, renews before it is rendered. |
|
|
210
289
|
| `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
290
|
| `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. |
|
|
291
|
+
| `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. A page loads React's development build only while its visitor has the dev tools on. |
|
|
213
292
|
| `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
293
|
| `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
294
|
| `environment` | `'production' \| 'staging' \| 'development' \| 'local'` | `'production'` | The Plitzi tier whose endpoints the rendered pages are handed. |
|
|
@@ -318,7 +397,7 @@ server.listen(3001);
|
|
|
318
397
|
```
|
|
319
398
|
|
|
320
399
|
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
|
|
400
|
+
path, or a function that may answer one — `saveSchema` and `saveStyle`, each writing its document back into the file. So an MCP server
|
|
322
401
|
([`@plitzi/sdk-mcp`](../mcp/README.md#adapters)) can run over it, with a `getGrant` of yours beside it.
|
|
323
402
|
|
|
324
403
|
### `JsonAdaptersConfig`
|
|
@@ -326,7 +405,7 @@ path — `saveSchema` and `saveStyle`, each writing its document back into the f
|
|
|
326
405
|
| Option | Type | Description |
|
|
327
406
|
|---|---|---|
|
|
328
407
|
| `offlineData` | `string` | Path to a single JSON file used for every request. |
|
|
329
|
-
| `offlineData` | `(spaceId, environment, revision?) => string` | Function
|
|
408
|
+
| `offlineData` | `(spaceId, environment, revision?) => string \| OfflineDataRaw` | Function answering per request: the path for the requested space, or the documents themselves — a process that replaces them while it runs (`serveProject` re-authoring a space). A save to documents it answered is refused: there is no file. |
|
|
330
409
|
| `offlineData` | `OfflineDataRaw` | The space itself, for a consumer that already holds it. Read-only: `saveSchema` and `saveStyle` are offered only for a path. |
|
|
331
410
|
| `deployment` | `string` | Path to a JSON file containing an `SSRSpaceDeployment` object. |
|
|
332
411
|
| `deployment` | `SSRSpaceDeployment` | Inline deployment object used for every request. |
|
|
@@ -461,12 +540,16 @@ in either runtime — and the page arrives with it. A browser asking for the sam
|
|
|
461
540
|
createServer({ dataDir: path.join(PROJECT_ROOT, 'src/data'), adapters: { ... } });
|
|
462
541
|
```
|
|
463
542
|
|
|
464
|
-
|
|
465
|
-
|
|
543
|
+
The data is read through one lookup, `action.lookups.getData(spaceId, at)` — the space's own data as its files' text by
|
|
544
|
+
path, as of the version being rendered or run: what a cloud deployment keeps for a space (`plitzi push`). Given none,
|
|
545
|
+
`createServer` derives it from `dataDir` (its `.json` files, read again once they change), so the folder and the
|
|
546
|
+
platform are read the same way.
|
|
466
547
|
|
|
467
548
|
`/data/…` is the data folder's first; a server provider reading another path is answered from `publicDir`, as the
|
|
468
549
|
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.
|
|
550
|
+
list's costs, another visitor's rows — is a server action's to read, answering only what is shown. A function reads
|
|
551
|
+
it with `ctx.data('products.json')` (parsed, read-only, as of the run's version); importing it from `functions/` is
|
|
552
|
+
refused when they are built.
|
|
470
553
|
|
|
471
554
|
## Compression
|
|
472
555
|
|
|
@@ -726,7 +809,7 @@ server.plugins.register('my-chart', {
|
|
|
726
809
|
|
|
727
810
|
A plugin can bring server code — its `functions/`, written as a space's functions are — whose routes answer under
|
|
728
811
|
`/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
|
|
812
|
+
corner of `kv`, none of the space's credentials, channels or data). This server's own plugins hand theirs over by type:
|
|
730
813
|
|
|
731
814
|
```ts
|
|
732
815
|
import { createServer, loadFunctions } from '@plitzi/sdk-server';
|
|
@@ -879,7 +962,7 @@ The server automatically registers `GET /_rsc` when `adapters.getRscData` is pro
|
|
|
879
962
|
}
|
|
880
963
|
```
|
|
881
964
|
|
|
882
|
-
**Cache-Control**: `no-store` for the `main` environment; `private, max-age=30` for other environments when `rsc.cacheTtlMs > 0`. Responses also include `X-Cache: HIT` or `X-Cache: MISS` for observability.
|
|
965
|
+
**Cache-Control**: `no-store` for the `main` environment; `private, max-age=30` for other environments when `rsc.cacheTtlMs > 0`. Responses also include `X-Cache: HIT` or `X-Cache: MISS` for observability. A request sent with `Cache-Control: no-cache` is resolved again rather than read from the cache, and its answer replaces the cached one — the SDK sends it for a refresh that exists because something changed (a write, `invalidateQueries`, `performQuery`); a `refreshSeconds` timer goes through the cache.
|
|
883
966
|
|
|
884
967
|
The endpoint returns `400` if `spaceId` is missing or invalid, `500` if `getRscData` throws, and `501` if the adapter is not configured.
|
|
885
968
|
|
|
@@ -952,7 +1035,10 @@ which meant the ordinary deployment went without, because nobody configures what
|
|
|
952
1035
|
`api.rateLimit` to put one counter behind a fleet.
|
|
953
1036
|
|
|
954
1037
|
`auth` on the server is the whole of the wiring: it mounts the `/auth` flows, answers the identity adapters a page
|
|
955
|
-
server asks for, and carries the cookie naming with it — so there is no second place to keep in step.
|
|
1038
|
+
server asks for, and carries the cookie naming with it — so there is no second place to keep in step. It also tells
|
|
1039
|
+
every page it renders where it signs in (`server.auth`: `userProvider: 'basic'`, `loginUrl`, `userUrl`, `logoutUrl`,
|
|
1040
|
+
`refreshUrl` and `mfaUrl` where offered, and `sessionHintCookie` named for the host), so the space declares nothing. A
|
|
1041
|
+
setting the space does declare wins, and a space that names another provider ignores the server's altogether. Everything is
|
|
956
1042
|
still exported separately (`createTokens`, `createIdentity`, `createAuthApi`, `authRoutes`, …) for a deployment that
|
|
957
1043
|
wants to assemble or replace one piece.
|
|
958
1044
|
|
|
@@ -1011,7 +1097,7 @@ Postgres, MySQL, Mongo or an identity service:
|
|
|
1011
1097
|
| `clearSession(target)` | signing out |
|
|
1012
1098
|
| `loadAccess(userId)` | the roles and permissions a grant answers with |
|
|
1013
1099
|
| `findByUsername(username)` | password sign-in |
|
|
1014
|
-
| `findByRefreshToken(token)` | renewal
|
|
1100
|
+
| `findByRefreshToken(token)` | renewal — the row is its revocation switch. `refreshExpiresAt` overrides the token's own expiry |
|
|
1015
1101
|
| `findMembership(userId, spaceId)` | space-level permission checks (`auth.can`) |
|
|
1016
1102
|
| `createAccount`, `findByEmail`, `setResetToken`, `sendMail`, … | signup, password reset, verification |
|
|
1017
1103
|
|
|
@@ -5,11 +5,15 @@ import { OfflineDataRaw, SSRPageAdapters, SSRSpaceDeployment } from '@plitzi/sdk
|
|
|
5
5
|
*/
|
|
6
6
|
export type JsonAdaptersConfig = {
|
|
7
7
|
/**
|
|
8
|
-
* The space: a path to a `{ schema, style }` JSON,
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* The space: a path to a `{ schema, style }` JSON, the data itself for a consumer that already holds it (composed at
|
|
9
|
+
* startup, fetched once, built in a test), or a function answering either per request — a path per space, or the
|
|
10
|
+
* documents a process holds and replaces while it runs (a project re-authoring its space on a save). Only a path can
|
|
11
|
+
* be written back to: `saveSchema` and `saveStyle` are not offered for the data itself, and refuse a function's
|
|
12
|
+
* answer that is not a path.
|
|
11
13
|
*/
|
|
12
|
-
offlineData: OfflineDataRaw | string |
|
|
14
|
+
offlineData: OfflineDataRaw | string | OfflineDataSource;
|
|
13
15
|
deployment?: string | SSRSpaceDeployment | Record<string, SSRSpaceDeployment>;
|
|
14
16
|
};
|
|
17
|
+
/** Where the space a request asks for is: a JSON file's path, or the documents themselves. */
|
|
18
|
+
export type OfflineDataSource = (spaceId: number, environment: string, revision?: number) => string | OfflineDataRaw;
|
|
15
19
|
export declare const createJsonAdapters: (config: JsonAdaptersConfig) => SSRPageAdapters;
|
|
@@ -24,15 +24,11 @@ var createJsonReader = () => {
|
|
|
24
24
|
};
|
|
25
25
|
var createJsonAdapters = (config) => {
|
|
26
26
|
const json = createJsonReader();
|
|
27
|
-
const
|
|
28
|
-
if (typeof config.offlineData === "function") return config.offlineData(spaceId, environment, revision);
|
|
29
|
-
return typeof config.offlineData === "string" ? config.offlineData : void 0;
|
|
30
|
-
};
|
|
27
|
+
const sourceFor = (spaceId, environment, revision) => typeof config.offlineData === "function" ? config.offlineData(spaceId, environment, revision) : config.offlineData;
|
|
31
28
|
const getOfflineData = (spaceId, environment, revision) => {
|
|
32
29
|
try {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
return Promise.resolve(json.read(filePath));
|
|
30
|
+
const source = sourceFor(spaceId, environment, revision);
|
|
31
|
+
return Promise.resolve(typeof source === "string" ? json.read(source) : source);
|
|
36
32
|
} catch (err) {
|
|
37
33
|
serverLog.error("JsonAdapters", "Failed to read offlineData", err);
|
|
38
34
|
return Promise.resolve(void 0);
|
|
@@ -41,8 +37,8 @@ var createJsonAdapters = (config) => {
|
|
|
41
37
|
const getSchema = async (spaceId, environment) => (await getOfflineData(spaceId, environment))?.schema;
|
|
42
38
|
const getStyle = async (spaceId, environment) => (await getOfflineData(spaceId, environment))?.style;
|
|
43
39
|
const writeBack = (spaceId, environment, change) => {
|
|
44
|
-
const filePath =
|
|
45
|
-
if (
|
|
40
|
+
const filePath = sourceFor(spaceId, environment);
|
|
41
|
+
if (typeof filePath !== "string") return Promise.reject(/* @__PURE__ */ new Error(`Space ${String(spaceId)} (${environment}) is held in memory, not in a file: there is nowhere to save it.`));
|
|
46
42
|
const current = json.read(filePath);
|
|
47
43
|
writeFileSync(filePath, JSON.stringify({
|
|
48
44
|
...current,
|
package/dist/core/auth/api.d.ts
CHANGED
|
@@ -17,7 +17,10 @@ export interface AccountRecord {
|
|
|
17
17
|
verified: boolean;
|
|
18
18
|
/** Absent for accounts that have no password — one created through an identity provider, for instance. */
|
|
19
19
|
passwordHash?: string;
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Unix seconds the refresh credential being renewed dies at, reported by `findByRefreshToken` — an override: left
|
|
22
|
+
* out, the credential's own expiry decides.
|
|
23
|
+
*/
|
|
21
24
|
refreshExpiresAt?: number;
|
|
22
25
|
/**
|
|
23
26
|
* Unix seconds the session being renewed BEGAN, for `lifetimes.session` — the cap on how long one may live
|
package/dist/core/auth/api.js
CHANGED
|
@@ -3,7 +3,7 @@ import { forwardedIp } from "../requestParser.js";
|
|
|
3
3
|
import { checkPermission } from "./authorize.js";
|
|
4
4
|
import { generateToken, hashPassword, verifyPassword } from "./passwords.js";
|
|
5
5
|
import { createMemoryRateLimit, fleetRateLimit } from "./throttle.js";
|
|
6
|
-
import { authFailureMessage } from "./tokens.js";
|
|
6
|
+
import { authFailureMessage, userIdOf } from "./tokens.js";
|
|
7
7
|
import { generateRecoveryCodes, generateTotpSecret, normalizeRecoveryCode, randomCode, totpStep, totpUri } from "./totp.js";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
9
|
//#region src/core/auth/api.ts
|
|
@@ -705,14 +705,23 @@ var createAuthApi = ({ tokens, identity, adapters, config = {} }) => {
|
|
|
705
705
|
if (!refreshToken) return refuse(400, "Refresh token is required");
|
|
706
706
|
/**
|
|
707
707
|
* Every refusal below ends the session in this browser, because each of them means the credential it just
|
|
708
|
-
* presented can never work again: no such row, past its deadline, or an
|
|
709
|
-
* is being cleared is not authority — the row is already gone — it is the
|
|
708
|
+
* presented can never work again: not one this deployment signed, no such row, past its deadline, or an
|
|
709
|
+
* account that may not hold one. What is being cleared is not authority — the row is already gone — it is the
|
|
710
|
+
* browser's reason to keep asking.
|
|
711
|
+
*
|
|
712
|
+
* The token carries its own deadline, so the store is asked only whether it still holds the row — the switch
|
|
713
|
+
* that revokes it. A `refreshExpiresAt` it reports overrides that deadline, and a store that keeps none needs
|
|
714
|
+
* to report nothing.
|
|
710
715
|
*/
|
|
711
|
-
const
|
|
712
|
-
if (!account || !account.refreshExpiresAt || account.refreshExpiresAt < Math.floor(Date.now() / 1e3)) return {
|
|
716
|
+
const lapsed = {
|
|
713
717
|
...refuse(401, "Invalid or expired refresh token", "expired"),
|
|
714
718
|
endSession: true
|
|
715
719
|
};
|
|
720
|
+
const verified = tokens.verifyRefreshToken(refreshToken);
|
|
721
|
+
if (!verified.ok) return lapsed;
|
|
722
|
+
const account = await adapters.findByRefreshToken?.(refreshToken);
|
|
723
|
+
const deadline = account?.refreshExpiresAt ?? verified.payload.exp;
|
|
724
|
+
if (!account || account.id !== userIdOf(verified.payload) || deadline === void 0 || deadline < now()) return lapsed;
|
|
716
725
|
if (!account.active) {
|
|
717
726
|
const reason = deniedReason(account);
|
|
718
727
|
return {
|
|
@@ -4,7 +4,7 @@ import { CredentialCarrier } from './credentials';
|
|
|
4
4
|
import { IdentityAdapters, IdentityConfig } from './identity';
|
|
5
5
|
import { CookieSink } from './session';
|
|
6
6
|
import { TokenConfig } from './tokens';
|
|
7
|
-
import { SSRAuthCookie, SSRSession, SSRUser } from '@plitzi/sdk-shared';
|
|
7
|
+
import { ServerAuth, SSRAuthCookie, SSRSession, SSRUser } from '@plitzi/sdk-shared';
|
|
8
8
|
/**
|
|
9
9
|
* Everything a deployment says about its own auth, in one object.
|
|
10
10
|
*
|
|
@@ -187,6 +187,12 @@ export declare const createAuth: (config: AuthConfig) => {
|
|
|
187
187
|
/** Where these flows were mounted, so whatever serves them reads it here rather than defaulting to `/auth`
|
|
188
188
|
* on its own — a host that guessed would serve the routes somewhere the policy above does not guard. */
|
|
189
189
|
basePath: string;
|
|
190
|
+
/**
|
|
191
|
+
* What a page server tells the pages it renders about these flows (`server.auth`), so a space signs in here with
|
|
192
|
+
* nothing to declare: the HTTP+JSON shape the SDK's `basic` provider speaks, at this `basePath`, with renewal and
|
|
193
|
+
* the second factor only where this deployment offers them.
|
|
194
|
+
*/
|
|
195
|
+
pageAuth: Omit<ServerAuth, "sessionHintCookie">;
|
|
190
196
|
/** The naming this was built with, so a page server takes it from here instead of being told a second time. */
|
|
191
197
|
cookieConfig: SSRAuthCookie | undefined;
|
|
192
198
|
/**
|
|
@@ -54,6 +54,14 @@ var createAuth = (config) => {
|
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
56
|
const basePath = config.basePath ?? "/auth";
|
|
57
|
+
const pageAuth = {
|
|
58
|
+
userProvider: "basic",
|
|
59
|
+
loginUrl: `${basePath}/login`,
|
|
60
|
+
userUrl: `${basePath}/session`,
|
|
61
|
+
logoutUrl: `${basePath}/logout`,
|
|
62
|
+
...api.capabilities.refresh ? { refreshUrl: `${basePath}/refresh` } : {},
|
|
63
|
+
...api.capabilities.mfa ? { mfaUrl: `${basePath}/mfa/complete` } : {}
|
|
64
|
+
};
|
|
57
65
|
const policy = {
|
|
58
66
|
rules: [...config.rules ?? [], ...authPolicyRules(basePath)],
|
|
59
67
|
fallback: config.fallback ?? "actor"
|
|
@@ -117,6 +125,12 @@ var createAuth = (config) => {
|
|
|
117
125
|
/** Where these flows were mounted, so whatever serves them reads it here rather than defaulting to `/auth`
|
|
118
126
|
* on its own — a host that guessed would serve the routes somewhere the policy above does not guard. */
|
|
119
127
|
basePath,
|
|
128
|
+
/**
|
|
129
|
+
* What a page server tells the pages it renders about these flows (`server.auth`), so a space signs in here with
|
|
130
|
+
* nothing to declare: the HTTP+JSON shape the SDK's `basic` provider speaks, at this `basePath`, with renewal and
|
|
131
|
+
* the second factor only where this deployment offers them.
|
|
132
|
+
*/
|
|
133
|
+
pageAuth,
|
|
120
134
|
/** The naming this was built with, so a page server takes it from here instead of being told a second time. */
|
|
121
135
|
cookieConfig: config.cookie,
|
|
122
136
|
/**
|
|
@@ -20,6 +20,8 @@ export declare const readCookies: (carrier: CredentialCarrier) => Record<string,
|
|
|
20
20
|
* both and the typed verifier decides which it actually is — that is what `scope` is for. Trying the wrong kind
|
|
21
21
|
* costs nothing: it fails on its scope claim before any database is touched.
|
|
22
22
|
*/
|
|
23
|
+
/** The query parameter a space token rides in: how a page on a host serving many spaces names the one it is. */
|
|
24
|
+
export declare const SPACE_TOKEN_PARAM = "access-token";
|
|
23
25
|
export declare const createCarriers: (sessionCookieName: (hostname: string) => string) => {
|
|
24
26
|
userTokenCandidates: (carrier: CredentialCarrier) => string[];
|
|
25
27
|
spaceTokenCandidates: (carrier: CredentialCarrier) => string[];
|
|
@@ -23,6 +23,8 @@ var unique = (candidates) => [...new Set(candidates.filter(Boolean))];
|
|
|
23
23
|
* both and the typed verifier decides which it actually is — that is what `scope` is for. Trying the wrong kind
|
|
24
24
|
* costs nothing: it fails on its scope claim before any database is touched.
|
|
25
25
|
*/
|
|
26
|
+
/** The query parameter a space token rides in: how a page on a host serving many spaces names the one it is. */
|
|
27
|
+
var SPACE_TOKEN_PARAM = "access-token";
|
|
26
28
|
var createCarriers = (sessionCookieName) => ({
|
|
27
29
|
userTokenCandidates: (carrier) => unique([
|
|
28
30
|
headerValue(carrier.headers["plitzi-access-token"]),
|
|
@@ -30,7 +32,7 @@ var createCarriers = (sessionCookieName) => ({
|
|
|
30
32
|
readCookies(carrier)[sessionCookieName(carrier.hostname)] ?? ""
|
|
31
33
|
]),
|
|
32
34
|
spaceTokenCandidates: (carrier) => {
|
|
33
|
-
const queryToken = carrier.query?.[
|
|
35
|
+
const queryToken = carrier.query?.[SPACE_TOKEN_PARAM];
|
|
34
36
|
return unique([
|
|
35
37
|
headerValue(carrier.headers["x-access-token"]),
|
|
36
38
|
bearer(carrier),
|
|
@@ -51,4 +53,4 @@ var createCarriers = (sessionCookieName) => ({
|
|
|
51
53
|
*/
|
|
52
54
|
var presentedOrigin = (carrier) => headerValue(carrier.headers.origin);
|
|
53
55
|
//#endregion
|
|
54
|
-
export { createCarriers, presentedOrigin, readCookies };
|
|
56
|
+
export { SPACE_TOKEN_PARAM, createCarriers, presentedOrigin, readCookies };
|