@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.
Files changed (81) hide show
  1. package/CHANGELOG.md +218 -0
  2. package/README.md +100 -14
  3. package/dist/adapters/jsonAdapters.d.ts +8 -4
  4. package/dist/adapters/jsonAdapters.js +5 -9
  5. package/dist/core/auth/api.d.ts +4 -1
  6. package/dist/core/auth/api.js +14 -5
  7. package/dist/core/auth/createAuth.d.ts +7 -1
  8. package/dist/core/auth/createAuth.js +14 -0
  9. package/dist/core/auth/credentials.d.ts +2 -0
  10. package/dist/core/auth/credentials.js +4 -2
  11. package/dist/core/auth/session.d.ts +2 -0
  12. package/dist/core/auth/session.js +6 -5
  13. package/dist/core/createServer.js +25 -4
  14. package/dist/core/http/stages/devReload.d.ts +2 -2
  15. package/dist/core/http/stages/devReload.js +2 -2
  16. package/dist/env.d.ts +1 -0
  17. package/dist/env.js +19 -0
  18. package/dist/env.test.d.ts +1 -0
  19. package/dist/functions-api.d.ts +5 -0
  20. package/dist/helpers/buildServerInfo.js +6 -0
  21. package/dist/index.d.ts +1 -1
  22. package/dist/modules/actions/runtime/projectData.d.ts +18 -0
  23. package/dist/modules/actions/runtime/projectData.js +69 -0
  24. package/dist/modules/actions/runtime/taskContext.d.ts +1 -1
  25. package/dist/modules/actions/runtime/taskContext.js +6 -3
  26. package/dist/modules/actions/types.d.ts +7 -1
  27. package/dist/modules/functions/build.js +10 -0
  28. package/dist/modules/functions/capabilities.js +5 -0
  29. package/dist/modules/functions/context.js +10 -2
  30. package/dist/modules/functions/contract.d.ts +5 -0
  31. package/dist/modules/functions/driver.js +4 -0
  32. package/dist/modules/functions/local.d.ts +3 -1
  33. package/dist/modules/functions/local.js +4 -2
  34. package/dist/modules/functions/protocol.d.ts +3 -0
  35. package/dist/modules/functions/scope.d.ts +1 -1
  36. package/dist/modules/images/imageProxy.js +1 -1
  37. package/dist/modules/mysql/accounts.js +1 -5
  38. package/dist/modules/project/plugins.d.ts +31 -0
  39. package/dist/modules/project/plugins.js +162 -0
  40. package/dist/modules/project/serveProject.d.ts +78 -0
  41. package/dist/modules/project/serveProject.js +200 -0
  42. package/dist/modules/project/serveProject.test.d.ts +1 -0
  43. package/dist/modules/project/watchSpace.d.ts +14 -0
  44. package/dist/modules/project/watchSpace.js +77 -0
  45. package/dist/modules/realtime/failure.d.ts +6 -0
  46. package/dist/modules/realtime/failure.js +11 -0
  47. package/dist/modules/realtime/handlers.js +13 -3
  48. package/dist/modules/realtime/hub.js +4 -1
  49. package/dist/modules/realtime/socket.js +3 -2
  50. package/dist/modules/rsc/connectorRscData.d.ts +7 -3
  51. package/dist/modules/rsc/connectorRscData.js +3 -4
  52. package/dist/modules/rsc/handler.d.ts +1 -0
  53. package/dist/modules/rsc/handler.js +11 -1
  54. package/dist/modules/rsc/publicFileResolver.d.ts +4 -8
  55. package/dist/modules/rsc/publicFileResolver.js +6 -10
  56. package/dist/modules/runtime/bundle.js +1 -1
  57. package/dist/modules/ssr/Component.d.ts +2 -1
  58. package/dist/modules/ssr/Component.js +2 -1
  59. package/dist/modules/ssr/prepareRender.js +14 -3
  60. package/dist/modules/ssr/registerExternalPlugins.js +1 -1
  61. package/dist/modules/ssr/vendor.test.d.ts +1 -0
  62. package/dist/modules/ssr/views/template.ejs +7 -3
  63. package/dist/plugins/compile.js +1 -1
  64. package/dist/plugins/copy.js +1 -1
  65. package/dist/plugins/manager.js +1 -1
  66. package/dist/project.d.ts +9 -0
  67. package/dist/project.js +2 -0
  68. package/package.json +16 -4
  69. package/skills/plitzi-authoring/CHEATSHEET.md +2 -2
  70. package/skills/plitzi-authoring/SKILL.md +3 -2
  71. package/skills/plitzi-authoring/reference/accessibility.md +2 -2
  72. package/skills/plitzi-authoring/reference/auth.md +75 -0
  73. package/skills/plitzi-authoring/reference/authoring-errors.md +18 -4
  74. package/skills/plitzi-authoring/reference/colours-and-motion.md +3 -2
  75. package/skills/plitzi-authoring/reference/efficiency.md +5 -2
  76. package/skills/plitzi-authoring/reference/elements-and-styles.md +23 -23
  77. package/skills/plitzi-authoring/reference/flows.md +11 -6
  78. package/skills/plitzi-authoring/reference/layouts.md +15 -18
  79. package/skills/plitzi-authoring/reference/plugins.md +1 -1
  80. package/skills/plitzi-authoring/reference/review-checklist.md +2 -1
  81. 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 from it less what its `main.ts`
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 `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. |
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>`. See [Project data](#project-data). |
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 returning the path for the requested space. |
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
- 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`).
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 channels). This server's own plugins hand theirs over by type:
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. Everything is
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. Answer with `refreshExpiresAt`, or every renewal is refused as expired |
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, a function returning one per request, or the data itself for a
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 `saveSchema` and `saveStyle` are offered only when one was given.
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 | ((spaceId: number, environment: string, revision?: number) => 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 pathFor = (spaceId, environment, revision) => {
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 filePath = pathFor(spaceId, environment, revision);
34
- if (!filePath) return Promise.resolve(config.offlineData);
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 = pathFor(spaceId, environment);
45
- if (!filePath) return Promise.resolve();
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,
@@ -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
- /** Unix seconds, for the refresh credential this account currently holds. */
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
@@ -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 account that may not hold one. What
709
- * is being cleared is not authority — the row is already gone — it is the browser's reason to keep asking.
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 account = await adapters.findByRefreshToken?.(refreshToken);
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?.["access-token"];
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 };