@plitzi/sdk-server 0.33.2 → 0.34.0

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 (274) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +283 -8
  3. package/dist/actions.d.ts +62 -0
  4. package/dist/actions.js +12 -0
  5. package/dist/adapters/cloudAdapters.d.ts +72 -0
  6. package/dist/adapters/cloudAdapters.js +303 -0
  7. package/dist/adapters/space/index.d.ts +93 -0
  8. package/dist/adapters/space/index.js +122 -0
  9. package/dist/adapters/space/resolvers.d.ts +69 -0
  10. package/dist/adapters/space/resolvers.js +79 -0
  11. package/dist/adapters/space/space.test.d.ts +1 -0
  12. package/dist/adapters/space/types.d.ts +64 -0
  13. package/dist/adapters/space/types.js +41 -0
  14. package/dist/auth.d.ts +50 -0
  15. package/dist/auth.js +19 -0
  16. package/dist/core/auth/api.d.ts +400 -11
  17. package/dist/core/auth/api.js +1044 -50
  18. package/dist/core/auth/createAuth.d.ts +135 -30
  19. package/dist/core/auth/createAuth.js +96 -24
  20. package/dist/core/auth/credentials.d.ts +9 -3
  21. package/dist/core/auth/credentials.js +10 -4
  22. package/dist/core/auth/csrf.d.ts +89 -0
  23. package/dist/core/auth/csrf.js +234 -0
  24. package/dist/core/auth/csrf.test.d.ts +1 -0
  25. package/dist/core/auth/identity.d.ts +6 -0
  26. package/dist/core/auth/identity.js +37 -8
  27. package/dist/core/auth/lifecycle.test.d.ts +1 -0
  28. package/dist/core/auth/oauth/index.d.ts +8 -0
  29. package/dist/core/auth/oauth/index.js +13 -15
  30. package/dist/core/auth/passwords.d.ts +15 -0
  31. package/dist/core/auth/passwords.js +32 -0
  32. package/dist/core/auth/redirects.d.ts +18 -0
  33. package/dist/core/auth/redirects.js +31 -0
  34. package/dist/core/auth/redirects.test.d.ts +1 -0
  35. package/dist/core/auth/routes.d.ts +4 -2
  36. package/dist/core/auth/routes.js +234 -9
  37. package/dist/core/auth/session.d.ts +11 -0
  38. package/dist/core/auth/session.js +1 -1
  39. package/dist/core/auth/spaceTokens.d.ts +38 -2
  40. package/dist/core/auth/spaceTokens.js +54 -2
  41. package/dist/core/auth/throttle.d.ts +5 -0
  42. package/dist/core/auth/throttle.js +81 -0
  43. package/dist/core/auth/throttle.test.d.ts +1 -0
  44. package/dist/core/auth/tokens.d.ts +83 -12
  45. package/dist/core/auth/tokens.js +51 -9
  46. package/dist/core/auth/totp.d.ts +48 -0
  47. package/dist/core/auth/totp.js +118 -0
  48. package/dist/core/auth/totp.test.d.ts +1 -0
  49. package/dist/core/createServer.d.ts +0 -6
  50. package/dist/core/createServer.js +35 -3
  51. package/dist/core/fontStore.d.ts +33 -0
  52. package/dist/core/fontStore.js +53 -0
  53. package/dist/core/fontStore.test.d.ts +1 -0
  54. package/dist/core/handlers/authMiddleware.js +1 -1
  55. package/dist/core/handlers/authRouteHandlers.d.ts +9 -1
  56. package/dist/core/handlers/authRouteHandlers.js +4 -3
  57. package/dist/core/handlers/csrfMiddleware.d.ts +20 -0
  58. package/dist/core/handlers/csrfMiddleware.js +32 -0
  59. package/dist/core/handlers/oauthRouteHandlers.d.ts +46 -0
  60. package/dist/core/handlers/oauthRouteHandlers.js +159 -0
  61. package/dist/core/handlers/socialRouteHandlers.d.ts +40 -0
  62. package/dist/core/handlers/socialRouteHandlers.js +129 -0
  63. package/dist/core/handlers/socialRouteHandlers.test.d.ts +1 -0
  64. package/dist/core/handlers/types.d.ts +22 -5
  65. package/dist/core/health.d.ts +11 -1
  66. package/dist/core/health.js +27 -3
  67. package/dist/core/http/dispatcher.d.ts +1 -1
  68. package/dist/core/http/dispatcher.js +20 -1
  69. package/dist/core/http/stages/authApi.js +1 -1
  70. package/dist/core/http/stages/fontAssets.d.ts +9 -0
  71. package/dist/core/http/stages/fontAssets.js +27 -0
  72. package/dist/core/http/stages/oauth.d.ts +19 -0
  73. package/dist/core/http/stages/oauth.js +118 -0
  74. package/dist/core/http/types.d.ts +12 -0
  75. package/dist/core/oauth/authorize.d.ts +22 -0
  76. package/dist/core/oauth/authorize.js +254 -0
  77. package/dist/core/oauth/authorize.test.d.ts +1 -0
  78. package/dist/core/oauth/challenge.d.ts +11 -0
  79. package/dist/core/oauth/challenge.js +33 -0
  80. package/dist/core/oauth/consentPage.d.ts +12 -0
  81. package/dist/core/oauth/consentPage.js +131 -0
  82. package/dist/core/oauth/metadata.d.ts +41 -0
  83. package/dist/core/oauth/metadata.js +71 -0
  84. package/dist/core/oauth/params.d.ts +5 -0
  85. package/dist/core/oauth/params.js +5 -0
  86. package/dist/core/oauth/pkce.d.ts +6 -0
  87. package/dist/core/oauth/pkce.js +15 -0
  88. package/dist/core/oauth/records.d.ts +72 -0
  89. package/dist/core/oauth/records.js +49 -0
  90. package/dist/core/oauth/register.d.ts +5 -0
  91. package/dist/core/oauth/register.js +55 -0
  92. package/dist/core/oauth/respond.d.ts +21 -0
  93. package/dist/core/oauth/respond.js +59 -0
  94. package/dist/core/oauth/token.d.ts +14 -0
  95. package/dist/core/oauth/token.js +130 -0
  96. package/dist/core/previewToken.d.ts +20 -0
  97. package/dist/core/previewToken.js +29 -1
  98. package/dist/core/server/pageServer.js +4 -1
  99. package/dist/core/services/action.d.ts +6 -4
  100. package/dist/core/services/action.js +103 -10
  101. package/dist/core/services/registry.js +2 -0
  102. package/dist/core/services/resolve.d.ts +8 -0
  103. package/dist/core/services/resolve.js +12 -1
  104. package/dist/core/staticFiles.js +1 -2
  105. package/dist/handlers.d.ts +7 -1
  106. package/dist/handlers.js +4 -1
  107. package/dist/helpers/buildResponseHelpers.d.ts +8 -0
  108. package/dist/helpers/buildResponseHelpers.js +9 -1
  109. package/dist/helpers/buildServerInfo.d.ts +8 -3
  110. package/dist/helpers/buildServerInfo.js +18 -14
  111. package/dist/helpers/cache/keys.d.ts +13 -0
  112. package/dist/helpers/cache/keys.js +12 -1
  113. package/dist/helpers/onAbort.d.ts +12 -0
  114. package/dist/helpers/onAbort.js +22 -0
  115. package/dist/helpers/onAbort.test.d.ts +1 -0
  116. package/dist/helpers/outboundGuard.d.ts +27 -0
  117. package/dist/helpers/outboundGuard.js +49 -0
  118. package/dist/helpers/outboundGuard.test.d.ts +1 -0
  119. package/dist/helpers/serverLog.d.ts +6 -3
  120. package/dist/helpers/serverLog.js +20 -3
  121. package/dist/index.d.ts +6 -0
  122. package/dist/index.js +9 -4
  123. package/dist/kernel.d.ts +5 -22
  124. package/dist/kernel.js +1 -14
  125. package/dist/modules/actions/{handler.js → connectorWrite.js} +5 -2
  126. package/dist/modules/actions/connectorWrite.test.d.ts +1 -0
  127. package/dist/modules/actions/index.d.ts +40 -0
  128. package/dist/modules/actions/index.js +46 -0
  129. package/dist/modules/actions/moduleFor.d.ts +3 -0
  130. package/dist/modules/actions/moduleFor.js +35 -0
  131. package/dist/modules/actions/moduleFor.test.d.ts +1 -0
  132. package/dist/modules/actions/runtime/check.d.ts +24 -0
  133. package/dist/modules/actions/runtime/check.js +111 -0
  134. package/dist/modules/actions/runtime/check.test.d.ts +1 -0
  135. package/dist/modules/actions/runtime/errors.d.ts +11 -0
  136. package/dist/modules/actions/runtime/errors.js +17 -0
  137. package/dist/modules/actions/runtime/guards.d.ts +126 -0
  138. package/dist/modules/actions/runtime/guards.js +246 -0
  139. package/dist/modules/actions/runtime/guards.test.d.ts +1 -0
  140. package/dist/modules/actions/runtime/kvStore.d.ts +27 -0
  141. package/dist/modules/actions/runtime/kvStore.js +41 -0
  142. package/dist/modules/actions/runtime/kvStore.test.d.ts +1 -0
  143. package/dist/modules/actions/runtime/limits.d.ts +11 -0
  144. package/dist/modules/actions/runtime/limits.js +35 -0
  145. package/dist/modules/actions/runtime/memoryKv.d.ts +2 -0
  146. package/dist/modules/actions/runtime/memoryKv.js +77 -0
  147. package/dist/modules/actions/runtime/namespaceKv.d.ts +9 -0
  148. package/dist/modules/actions/runtime/namespaceKv.js +19 -0
  149. package/dist/modules/actions/runtime/precheck.d.ts +27 -0
  150. package/dist/modules/actions/runtime/precheck.js +45 -0
  151. package/dist/modules/actions/runtime/renderResolver.d.ts +15 -0
  152. package/dist/modules/actions/runtime/renderResolver.js +130 -0
  153. package/dist/modules/actions/runtime/renderResolver.test.d.ts +1 -0
  154. package/dist/modules/actions/runtime/renderShare.d.ts +29 -0
  155. package/dist/modules/actions/runtime/renderShare.js +36 -0
  156. package/dist/modules/actions/runtime/renderShare.test.d.ts +1 -0
  157. package/dist/modules/actions/runtime/report.d.ts +14 -0
  158. package/dist/modules/actions/runtime/report.js +22 -0
  159. package/dist/modules/actions/runtime/runAction.d.ts +12 -0
  160. package/dist/modules/actions/runtime/runAction.js +405 -0
  161. package/dist/modules/actions/runtime/runAction.test.d.ts +1 -0
  162. package/dist/modules/actions/runtime/runLogger.d.ts +31 -0
  163. package/dist/modules/actions/runtime/runLogger.js +66 -0
  164. package/dist/modules/actions/runtime/runLogger.test.d.ts +1 -0
  165. package/dist/modules/actions/runtime/schedule.d.ts +35 -0
  166. package/dist/modules/actions/runtime/schedule.js +76 -0
  167. package/dist/modules/actions/runtime/schedule.test.d.ts +1 -0
  168. package/dist/modules/actions/runtime/scope.d.ts +40 -0
  169. package/dist/modules/actions/runtime/scope.js +103 -0
  170. package/dist/modules/actions/runtime/triggers.d.ts +12 -0
  171. package/dist/modules/actions/runtime/triggers.js +14 -0
  172. package/dist/modules/actions/taskCatalog.d.ts +26 -0
  173. package/dist/modules/actions/taskCatalog.js +24 -0
  174. package/dist/modules/actions/taskCatalog.test.d.ts +1 -0
  175. package/dist/modules/actions/tasks/auth.d.ts +2 -0
  176. package/dist/modules/actions/tasks/auth.js +40 -0
  177. package/dist/modules/actions/tasks/builtins.d.ts +8 -0
  178. package/dist/modules/actions/tasks/builtins.js +24 -0
  179. package/dist/modules/actions/tasks/connector.d.ts +2 -0
  180. package/dist/modules/actions/tasks/connector.js +141 -0
  181. package/dist/modules/actions/tasks/db.d.ts +2 -0
  182. package/dist/modules/actions/tasks/db.js +59 -0
  183. package/dist/modules/actions/tasks/db.test.d.ts +1 -0
  184. package/dist/modules/actions/tasks/flow.d.ts +3 -0
  185. package/dist/modules/actions/tasks/flow.js +109 -0
  186. package/dist/modules/actions/tasks/helpers.d.ts +16 -0
  187. package/dist/modules/actions/tasks/helpers.js +30 -0
  188. package/dist/modules/actions/tasks/http.d.ts +2 -0
  189. package/dist/modules/actions/tasks/http.js +111 -0
  190. package/dist/modules/actions/tasks/http.test.d.ts +1 -0
  191. package/dist/modules/actions/tasks/kv.d.ts +2 -0
  192. package/dist/modules/actions/tasks/kv.js +102 -0
  193. package/dist/modules/actions/tasks/kv.test.d.ts +1 -0
  194. package/dist/modules/actions/tasks/registry.d.ts +10 -0
  195. package/dist/modules/actions/tasks/registry.js +36 -0
  196. package/dist/modules/actions/tasks/transform.d.ts +2 -0
  197. package/dist/modules/actions/tasks/transform.js +34 -0
  198. package/dist/modules/actions/transport/callHandler.d.ts +24 -0
  199. package/dist/modules/actions/transport/callHandler.js +233 -0
  200. package/dist/modules/actions/transport/callHandler.test.d.ts +1 -0
  201. package/dist/modules/actions/transport/cancelHandler.d.ts +23 -0
  202. package/dist/modules/actions/transport/cancelHandler.js +23 -0
  203. package/dist/modules/actions/transport/catalogHandler.d.ts +18 -0
  204. package/dist/modules/actions/transport/catalogHandler.js +21 -0
  205. package/dist/modules/actions/transport/stream.d.ts +23 -0
  206. package/dist/modules/actions/transport/stream.js +100 -0
  207. package/dist/modules/actions/transport/verifySignature.d.ts +16 -0
  208. package/dist/modules/actions/transport/verifySignature.js +72 -0
  209. package/dist/modules/actions/transport/webhookHandler.d.ts +25 -0
  210. package/dist/modules/actions/transport/webhookHandler.js +206 -0
  211. package/dist/modules/actions/transport/webhookHandler.test.d.ts +1 -0
  212. package/dist/modules/actions/types.d.ts +234 -0
  213. package/dist/modules/connectors/engine.js +4 -1
  214. package/dist/modules/connectors/resolver.d.ts +10 -2
  215. package/dist/modules/connectors/resolver.js +8 -3
  216. package/dist/modules/mysql/accounts.d.ts +5 -0
  217. package/dist/modules/mysql/accounts.js +365 -0
  218. package/dist/modules/mysql/accounts.test.d.ts +1 -0
  219. package/dist/modules/mysql/admin.d.ts +58 -0
  220. package/dist/modules/mysql/admin.js +133 -0
  221. package/dist/modules/mysql/config.d.ts +82 -0
  222. package/dist/modules/mysql/config.js +30 -0
  223. package/dist/modules/mysql/identities.d.ts +35 -0
  224. package/dist/modules/mysql/identities.js +43 -0
  225. package/dist/modules/mysql/index.d.ts +67 -0
  226. package/dist/modules/mysql/index.js +62 -0
  227. package/dist/modules/mysql/pool.d.ts +3 -0
  228. package/dist/modules/mysql/pool.js +88 -0
  229. package/dist/modules/mysql/query.d.ts +12 -0
  230. package/dist/modules/mysql/query.js +21 -0
  231. package/dist/modules/mysql/schema.d.ts +43 -0
  232. package/dist/modules/mysql/schema.js +340 -0
  233. package/dist/modules/mysql/schema.test.d.ts +1 -0
  234. package/dist/modules/mysql/sessions.d.ts +33 -0
  235. package/dist/modules/mysql/sessions.js +46 -0
  236. package/dist/modules/mysql/spaceTokens.d.ts +11 -0
  237. package/dist/modules/mysql/spaceTokens.js +59 -0
  238. package/dist/modules/mysql/store.integration.test.d.ts +1 -0
  239. package/dist/modules/rsc/connectorRscData.d.ts +25 -0
  240. package/dist/modules/rsc/connectorRscData.js +51 -0
  241. package/dist/modules/rsc/connectorRscData.test.d.ts +1 -0
  242. package/dist/modules/rsc/handler.js +16 -3
  243. package/dist/modules/rsc/matchRscPage.d.ts +14 -0
  244. package/dist/modules/rsc/matchRscPage.js +26 -0
  245. package/dist/modules/rsc/resolveRscData.d.ts +14 -1
  246. package/dist/modules/rsc/resolveRscData.js +23 -34
  247. package/dist/modules/ssr/Component.d.ts +6 -2
  248. package/dist/modules/ssr/Component.js +3 -1
  249. package/dist/modules/ssr/draftSession.test.d.ts +1 -0
  250. package/dist/modules/ssr/handler.js +15 -2
  251. package/dist/modules/ssr/loadPluginComponents.js +22 -5
  252. package/dist/modules/ssr/prepareRender.js +87 -8
  253. package/dist/modules/ssr/prepareRender.test.d.ts +1 -0
  254. package/dist/modules/ssr/preview.d.ts +21 -8
  255. package/dist/modules/ssr/preview.js +38 -14
  256. package/dist/modules/ssr/registerExternalPlugins.js +2 -2
  257. package/dist/modules/ssr/resolvePageSeo.d.ts +18 -0
  258. package/dist/modules/ssr/resolvePageSeo.js +26 -0
  259. package/dist/modules/ssr/resolvePageSeo.test.d.ts +1 -0
  260. package/dist/modules/ssr/template.test.d.ts +1 -0
  261. package/dist/modules/ssr/views/template.ejs +71 -5
  262. package/dist/mysql.d.ts +26 -0
  263. package/dist/mysql.js +4 -0
  264. package/dist/oauth.d.ts +24 -0
  265. package/dist/oauth.js +10 -0
  266. package/dist/plugins/compile.d.ts +1 -0
  267. package/dist/plugins/compile.js +37 -3
  268. package/dist/plugins/manager.d.ts +40 -0
  269. package/dist/plugins/manager.js +113 -13
  270. package/dist/plugins/manager.test.d.ts +1 -0
  271. package/package.json +43 -14
  272. package/skills/plitzi-authoring/SKILL.md +252 -0
  273. /package/dist/{modules/actions/handler.test.d.ts → adapters/cloudAdapters.test.d.ts} +0 -0
  274. /package/dist/modules/actions/{handler.d.ts → connectorWrite.d.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # @plitzi/sdk-server
2
2
 
3
+ ## 0.34.0
4
+
5
+ ### Minor Changes
6
+
7
+ - v0.34.0
8
+ - 5aceda0: Draft previews you can iterate against, and a `DraftStore` contract that says so.
9
+
10
+ A preview token was one-shot: spent by the render that used it. That is right for a capture and wrong for a
11
+ person — reloading showed the saved space again, so "look at the change, adjust it, look again" meant minting a
12
+ new token for every look. `POST /__preview` now takes `mode: 'session'`, which mints a token that stays
13
+ resolvable until it expires (`preview.sessionTtlMs`, 15 minutes by default) or until `POST /__preview/end` ends
14
+ it. The token is remembered in an `HttpOnly` cookie on the first render, so the draft follows a navigation —
15
+ the page after a link carries no query parameter.
16
+
17
+ A draft render, either mode, is never cached, never metered and answers `Cache-Control: no-store` plus
18
+ `X-Robots-Tag: noindex`. Data refreshes (`/_rsc`) made from inside a session are excluded from metering and
19
+ caching too — without that, an open preview tab would be billed as live traffic.
20
+
21
+ **Breaking, for anyone who implements `DraftStore`** (a shared store for a multi-replica deployment). The
22
+ default in-memory store is unaffected; a custom one needs three changes:
23
+
24
+ ```ts
25
+ // before
26
+ put(token, data, ttlMs)
27
+ take(token): OfflineDataRaw | undefined
28
+
29
+ // after
30
+ put(token, data, { ttlMs, reusable }) // `reusable` is a session; absent is one-shot
31
+ take(token): { data, reusable } | undefined // consume unless reusable — and say which it was
32
+ drop(token) // end a session before its TTL
33
+ ```
34
+
35
+ `take` reports which kind it resolved because the render that resolves a session is the one that has to remember
36
+ it for the rest of the visit, and only the store knows whether the token survived the read.
37
+
38
+ ### Patch Changes
39
+
40
+ - Updated dependencies [5aceda0]
41
+ - Updated dependencies
42
+ - Updated dependencies [5aceda0]
43
+ - Updated dependencies [9c3292c]
44
+ - @plitzi/sdk-shared@0.34.0
45
+ - @plitzi/plitzi-sdk@0.34.0
46
+
3
47
  ## 0.33.2
4
48
 
5
49
  ### Patch Changes
package/README.md CHANGED
@@ -38,6 +38,120 @@ createServer({
38
38
  });
39
39
  ```
40
40
 
41
+ ## Your first space
42
+
43
+ A page server needs a **space** to serve, and there are two ways to get one: export it from the Plitzi builder, or
44
+ write it yourself. This is the second one, whole — one file, no account, no API key, no JSON:
45
+
46
+ ```bash
47
+ yarn add @plitzi/sdk-authoring # writing spaces is its own package, and it installs nothing else
48
+ ```
49
+
50
+ ```ts
51
+ // server.ts
52
+ import { authorSpace, container, heading, link, text } from '@plitzi/sdk-authoring';
53
+ import { consoleLogger, createJsonAdapters, createServer } from '@plitzi/sdk-server';
54
+
55
+ /** A space is a tree, some CSS and a palette. Ids, class names and the breakpoint maps are derived from it. */
56
+ const space = authorSpace({
57
+ name: 'My space',
58
+ permanentUrl: 'my-space',
59
+
60
+ variables: { color: { ink: { light: '#17171c', dark: '#fafafa', default: '#17171c' } } },
61
+
62
+ // Rules written once and named. An element reaches one with `class`.
63
+ classes: {
64
+ page: {
65
+ desktop: {
66
+ display: 'flex',
67
+ 'flex-direction': 'column',
68
+ 'align-items': 'center',
69
+ gap: '16px',
70
+ padding: '96px 24px',
71
+ 'font-family': 'system-ui, sans-serif',
72
+ color: 'var(--ink)'
73
+ }
74
+ },
75
+ card: { desktop: { 'border-radius': '12px', border: '1px solid #e4e4e7', padding: '24px' } }
76
+ },
77
+
78
+ pages: [
79
+ {
80
+ name: 'Home',
81
+ slug: '',
82
+ class: 'page',
83
+ body: [
84
+ heading('Hello from my own server', { subType: 'h1' }),
85
+ container({
86
+ class: 'card',
87
+ children: [text('This page is a document. Nothing here was compiled.'), link({ href: '/about' })]
88
+ })
89
+ ]
90
+ },
91
+ { name: 'About', slug: 'about', class: 'page', body: [heading('About', { subType: 'h1' })] }
92
+ ]
93
+ });
94
+
95
+ /** Where the server gets a space from. Hand it the documents and it fills in the reads a page server needs; a
96
+ * real deployment swaps this for adapters that hit its own database, and the server never learns the difference. */
97
+ const server = createServer({
98
+ port: 3001,
99
+ devMode: true,
100
+ adapters: createJsonAdapters({ offlineData: space }),
101
+ logger: consoleLogger
102
+ });
103
+
104
+ server.listen(3001, '127.0.0.1');
105
+ ```
106
+
107
+ ```bash
108
+ yarn add @plitzi/sdk-server react react-dom
109
+ yarn tsx server.ts # http://127.0.0.1:3001/
110
+ ```
111
+
112
+ Both pages render server-side, with the CSS the space declared. `authorSpace` refuses to hand back a space that
113
+ would not render — a CSS property the style editor could not read back, a class nothing declares, a binding
114
+ pointing at an element that is not there — so a mistake is an error at the line that made it rather than a blank
115
+ section in production.
116
+
117
+ **Shorthands are fine.** `padding: '96px 24px'` and `border: '1px solid #e4e4e7'` above are expanded into the
118
+ atomic longhands Plitzi's style editor reads back, so a space written the way anyone writes CSS still opens in the
119
+ builder.
120
+
121
+ Everything else — data on the server, sessions, an agent editing the space, work the server runs — is the same
122
+ space with more declared in it. The full surface (element factories, bindings, flows, validation) is in
123
+ **[Authoring spaces](https://github.com/plitzi/plitzi-workspace/blob/main/docs/en/authoring-spaces.md)**, and
124
+ there are runnable versions of each step in
125
+ [`examples/`](https://github.com/plitzi/plitzi-workspace/tree/main/examples).
126
+
127
+ ### Working with an agent
128
+
129
+ This package ships an [Agent Skill](https://agentskills.io/) for authoring, because an agent in your project sees
130
+ only what npm installed — not this repository. It teaches the declaration, the flat prop model, the binding and
131
+ flow builders, and the mistakes the validator refuses, so an agent writes a space instead of reconstructing schema
132
+ JSON from memory. It is `@plitzi/sdk-authoring`'s file, shipped here as well so a server has it without installing
133
+ anything else:
134
+
135
+ ```bash
136
+ cp -R node_modules/@plitzi/sdk-server/skills/plitzi-authoring ~/.claude/skills/
137
+ ```
138
+
139
+ It installs the same way into any agent that reads a `SKILL.md` (Claude Code, VS Code / Copilot, Codex, Gemini
140
+ CLI, Cline, Goose). Everything it defers to is in the package too: every factory, spec field and step builder
141
+ carries its documentation in the published `.d.ts`, so hovering a call or reading
142
+ `node_modules/@plitzi/sdk-authoring/dist/index.d.ts` answers what an attribute takes without leaving the project.
143
+
144
+ ### Already have a space?
145
+
146
+ An export from the builder is a `{ schema, style }` JSON and goes in the same door:
147
+
148
+ ```ts
149
+ adapters: createJsonAdapters({ offlineData: './space.json' })
150
+ ```
151
+
152
+ `validateSpace({ schema, style })` from `@plitzi/sdk-authoring` answers whether one is servable before you
153
+ serve it — worth running over anything that arrives as a file.
154
+
41
155
  ## Configuration
42
156
 
43
157
  | Option | Type | Default | Description |
@@ -97,6 +211,57 @@ type SSRAdapters = {
97
211
  - **`endSession`** *(optional)* — called when `POST {logoutPath}` is received. Revoke the session at the source; the server clears the cookies. Clearing them alone would leave the credential itself working for anyone who had already copied it. A navigation receives a `303` redirect; a fetch receives `204 No Content`.
98
212
  - **`getRscData`** *(optional)* — called by the RSC endpoint (`/_rsc`), and once per page render, to fetch server-side data for schema elements with `runtime: 'server'`. Takes one `SSRRscContext`: the request, the space context, the resolved user so authenticated operations are safe, `ids` on a partial refresh (absent means every element), and `loadOfflineData`. That last one is the space itself, shared with the page render happening alongside — await it instead of fetching the schema again, and it is read once per request however many callers ask. Return `{}` when there is no server data for the current request (see [RSC](#react-server-components-rsc)).
99
213
 
214
+ ## Space adapters (a space per domain)
215
+
216
+ `getSpaceDeployment` looks like glue and is not: the same handful of rules turn up in every deployment that serves
217
+ more than one space, and getting any of them wrong is a real failure. `createSpaceAdapters` states them once and
218
+ asks you only for the lookups.
219
+
220
+ ```ts
221
+ import { authoringPreview, createSpaceAdapters, verifiedDomain, wildcardSubdomain } from '@plitzi/sdk-server';
222
+
223
+ const spaces = createSpaceAdapters({
224
+ resolvers: [
225
+ authoringPreview({ hosts: platformHosts, resolveGrant: auth.identity.resolveGrant, find: findSpace }),
226
+ wildcardSubdomain({ suffix: 'example.app', find: findSpaceBySlug }),
227
+ verifiedDomain(findDeploymentByDomain)
228
+ ],
229
+ cache: redisCache,
230
+ frameAncestors: { find: findSpaceDomains, floor: platformHosts, cache: true },
231
+ decorate: resolution => Promise.resolve({ pluginNames: pluginsFor(resolution.spaceId) })
232
+ });
233
+
234
+ createServer({ adapters: { ...spaces.adapters, getOfflineData } });
235
+ ```
236
+
237
+ **Resolvers are an ordered list**, and the order is the policy. Each returns a resolution, a refusal, or nothing —
238
+ and the difference between the last two is the point: a refusal ENDS the chain, so a request that tried to act for
239
+ a space and failed is never quietly served as an anonymous visitor of whatever else that host resolves to. Three
240
+ are built in and a fourth is `(req) => …` of your own, which is how a deployment that identifies tenants by header,
241
+ by path prefix or by a table nobody else has still gets everything below.
242
+
243
+ | Built in | What it is |
244
+ |---|---|
245
+ | `authoringPreview` | An author looking at their own space through a builder, on a host you own. Marks the render `authoring` so metering skips it. Put it first. |
246
+ | `wildcardSubdomain` | `<slug>.example.app`, with no per-space row to configure. Refuses to read a deeper sub-domain as a slug. |
247
+ | `verifiedDomain` | A custom domain, through a row that says it was proven. |
248
+ | `fixedSpace` | Always this one — a single-space deployment, or a catch-all last in the chain. |
249
+
250
+ What it decides for you:
251
+
252
+ - **A credentialed request is never served from, nor written to, the shared cache.** It is keyed by host, and a
253
+ credential resolves the same host to a different space — so a hit would serve one author's preview to the next.
254
+ - **`frame-ancestors` is derived on every resolution**, from the domains the space declared plus your floor.
255
+ Deriving it per branch is how a deployment ends up with one branch that forgets, and that branch serves a space
256
+ framable by anyone.
257
+ - **Refusals are not cached**, so fixing a row fixes the site rather than fixing it in five minutes.
258
+ - **A resolver that throws is a 404 for that request**, reported through `onError`, not a dead server.
259
+
260
+ `cache` is any `get`/`set`/`delete` of strings — Redis, Memcached, whatever you have — or `createMemoryCache()` for
261
+ a single-process deployment. `invalidate.resolution(host)` and `invalidate.domains(spaceId)` drop what a change made
262
+ untrue, which matters the moment an owner edits a domain list: until then the old framing policy stands, and a TTL
263
+ is not a security boundary.
264
+
100
265
  ## JSON adapters (offline mode)
101
266
 
102
267
  `createJsonAdapters` provides a ready-made adapter set that reads a space from local JSON files, useful for offline mode, integration tests, and static deployments.
@@ -497,13 +662,13 @@ Enable RSC at the top level of your schema:
497
662
  | `'client'` | Skipped during SSR; rendered only in the browser after hydration. |
498
663
  | `'shared'` | Rendered on both server and client (default behaviour). |
499
664
 
500
- **`loadStrategy`** — controls when the browser requests the element's data (schema field; browser runtime behaviour is handled by the SDK):
665
+ **`loadStrategy`** — controls when an element's items are mounted, relative to its `visibility` state. The element itself always renders (it is what registers the callbacks that show it again); only what is inside it is deferred. The builder canvas ignores it and keeps everything mounted.
501
666
 
502
667
  | Value | Behaviour |
503
668
  |---|---|
504
- | `'eager'` | Data fetched immediately on mount. |
505
- | `'lazy'` | Data fetched after the initial render completes. |
506
- | `'visible'` | Data fetched when the element enters the viewport. |
669
+ | `'eager'` | Items always mounted, hidden or not (default behaviour). |
670
+ | `'lazy'` | Items mounted the first time the element is shown, and kept from then on. `modalContainer` and `dialogContainer` declare it by default. |
671
+ | `'visible'` | Items mounted only while the element is shown; their state is dropped on every hide. |
507
672
 
508
673
  ### `getRscData` adapter
509
674
 
@@ -650,18 +815,23 @@ cookies, and the store your accounts live in — and returns the whole cycle wir
650
815
 
651
816
  ```ts
652
817
  import { createServer } from '@plitzi/sdk-server';
653
- import { createAuth } from '@plitzi/sdk-server/kernel';
818
+ import { createAuth } from '@plitzi/sdk-server/auth';
654
819
 
655
820
  const auth = createAuth({
656
821
  tokens: { secret: process.env.AUTH_SECRET, issuer: 'https://acme.com', audience: ['https://acme.com'] },
657
822
  cookie: { name: 'acme_session' },
658
- adapters: accounts,
659
- api: { verifyPassword }
823
+ adapters: accounts
660
824
  });
661
825
 
662
826
  createServer({ port: 443, adapters: { getOfflineData, getSpaceDeployment }, auth });
663
827
  ```
664
828
 
829
+ There is no third argument for the security-relevant parts, on purpose: password hashing defaults to scrypt, sign-in
830
+ is rate-limited in memory, mailed links expire, and CSRF is on. Each of those used to be an option with no default —
831
+ which meant the ordinary deployment went without, because nobody configures what they have not read about. Supply
832
+ `api.hashPassword`/`verifyPassword` to keep an existing algorithm (a store of bcrypt hashes needs bcrypt), and
833
+ `api.rateLimit` to put one counter behind a fleet.
834
+
665
835
  `auth` on the server is the whole of the wiring: it mounts the `/auth` flows, answers the identity adapters a page
666
836
  server asks for, and carries the cookie naming with it — so there is no second place to keep in step. Everything is
667
837
  still exported separately (`createTokens`, `createIdentity`, `createAuthApi`, `authRoutes`, …) for a deployment that
@@ -730,6 +900,30 @@ Postgres, MySQL, Mongo or an identity service:
730
900
  rather than failing at runtime. Declining a flow is one act: do not implement it. `GET /auth/capabilities` publishes
731
901
  the result, so a sign-in page renders what the backend actually answers instead of a button that dead-ends.
732
902
 
903
+ ### …or don't: `@plitzi/sdk-server/mysql`
904
+
905
+ If you are standing a user store up rather than adapting one you have, the table above is ceremony. Import the
906
+ store instead — it is every adapter in it, already written, over tables it creates:
907
+
908
+ ```ts
909
+ import { createMysqlStore } from '@plitzi/sdk-server/mysql';
910
+
911
+ const store = await createMysqlStore({ url: process.env.DATABASE_URL });
912
+
913
+ const auth = createAuth({
914
+ tokens: { secret, issuer },
915
+ adapters: store.authAdapters, // spread your own on top: { ...store.authAdapters, sendMail }
916
+ });
917
+ ```
918
+
919
+ It connects to a MySQL server; it does not start one. `store.admin` seeds the roles, permissions and memberships
920
+ that no request creates. `mysql2` is an optional peer dependency, since this is the only part of the package that
921
+ touches a database.
922
+
923
+ The tables, the contract they satisfy, and the four traps that fail somewhere other than where they were caused
924
+ are in [`docs/auth/mysql-schema.md`](./docs/auth/mysql-schema.md) — worth reading even if you are mapping your own
925
+ schema, because it is written as the set of questions your adapters have to answer.
926
+
733
927
  ### What you get
734
928
 
735
929
  `POST /auth/login`, `/auth/refresh`, `/auth/logout`, `/auth/sessions/revoke`, `/auth/exchange`, `GET /auth/session`,
@@ -737,6 +931,18 @@ the result, so a sign-in page renders what the backend actually answers instead
737
931
  sign-out also answer a full-page form submission with a `303` so the view re-renders, which a `fetch` client does
738
932
  not need and a `<form>` does.
739
933
 
934
+ **Changing an email is a confirmation, not a write.** Supply `setPendingEmail` / `findByPendingEmail` /
935
+ `clearPendingEmail` (the MySQL store has them) and `POST /auth/profile` parks the new address instead of applying
936
+ it: the account keeps signing in with the old one until `POST /auth/confirm-email` proves somebody reads the new
937
+ one, which also marks it verified. A typo then costs a resend rather than the account. Leave them out and the
938
+ address changes on the spot, as before.
939
+
940
+ **Impersonation is off until you name its permission.** With `api.impersonationPermission` set,
941
+ `POST /auth/admin/impersonate` answers a session as another account — one that carries `act` (RFC 8693) so every
942
+ request made with it is distinguishable from the account holder's, lives fifteen minutes, cannot renew, and is
943
+ returned in the body rather than written over the administrator's own cookie. `Actor.impersonatedBy` is that claim
944
+ read back, and `GET /auth/session` reports it.
945
+
740
946
  Behaviour you do not have to get right yourself: the credential is renewed ahead of expiry and rotated when it is;
741
947
  signing out revokes at the source rather than only clearing the browser's copy; a readable hint cookie rides beside
742
948
  the session carrying nothing but expiry timestamps, so a page can tell that nobody is signed in without a request;
@@ -747,12 +953,81 @@ and every refusal names a machine-readable `reason`, so a client can tell "renew
747
953
  | Config | Effect |
748
954
  |---|---|
749
955
  | `cookie` | Name, domain, `SameSite`, `Secure`, the refresh path, the hint suffix. Defaults derive from the request host |
956
+ | `api.password` | What a password has to be. `minLength` defaults to 8 (NIST SP 800-63B's floor); `validate` is where a breach-list lookup or a strength estimator goes. Applied wherever one is set — signing up, resetting, changing |
957
+ | `api.rateLimit` | May this attempt proceed? **Defaults to an in-memory sliding window**, so no deployment is unthrottled by omission; supply one to put a single counter behind a whole fleet. Called before the password is checked, so a throttled attempt costs no hash |
958
+ | `api.adminPermission` | The global capability the `/auth/admin/*` routes require. Default `userManage` |
959
+ | `api.impersonationPermission` | The capability it takes to obtain a session **as** another account, and the switch that offers the flow at all — **absent, there is no impersonation**. Its own permission on purpose: suspending an account and becoming one are not the same grant |
960
+ | `api.onEvent` | Every act worth recording — sign-ins, failures, password changes, admin actions — as a `SecurityEvent`. An audit trail, a webhook and an alert are the same feed. Never awaited and never able to fail a request |
961
+ | `api.mfaIssuer` | What an authenticator app calls this deployment. Defaults to the token issuer |
962
+ | `tokens.lifetimes.session` | The longest a session may live however often it renews. `0` (default) means no cap — the renewal window is already an idle timeout, since a session nobody refreshes dies with its refresh token |
963
+ | `api.onMailError` | Where a failed delivery is reported. It is never thrown: every one of these sends after something has already been committed, so letting the provider decide whether the request succeeded reports a change that did happen as a 500 |
750
964
  | `basePath` | Where the flows are mounted. The guard's rules follow it |
751
965
  | `rules` / `fallback` | Extra authorization rules, applied before the derived ones, and what an unlisted path requires |
752
966
  | `identity` | Your own hosts and origins — the floor for domain binding and framing |
753
967
  | `tokens.lifetimes` | How long each credential lives |
754
968
 
755
- A working example is [`examples/02-with-users`](../../examples/02-with-users).
969
+ Working examples are in [`examples/02-with-users`](../../examples/02-with-users): `01-sessions` over a store you
970
+ write, `02-mysql` over one you do not.
971
+
972
+ ### Cross-site request forgery
973
+
974
+ **On by default.** The session cookie defaults to `SameSite=None` off localhost — a space is embedded in an iframe
975
+ on somebody else's domain — so the browser attaches it to requests another site caused. That is the attack, and
976
+ `Lax` is what would otherwise prevent it.
977
+
978
+ Two requests can never be forged into whatever they are reaching, and neither is ever asked for a token:
979
+
980
+ | Never asked | Why |
981
+ |---|---|
982
+ | `GET`, `HEAD`, `OPTIONS` | They change nothing |
983
+ | Anything with `Authorization: Bearer` | A cross-origin page cannot set that header without a preflight you would have to allow. **Every API client is unaffected** |
984
+
985
+ After that it depends on what the flow does, because there are two different attacks:
986
+
987
+ | Flow | Guarded by | Because |
988
+ |---|---|---|
989
+ | **An action taken as somebody** — profile, password, sessions, admin | A token, whenever a **session cookie** is present | The attack is the browser attaching the victim's credentials. No cookie, nothing to forge |
990
+ | **A sign-in** — login, signup, the password-reset pair, the passwordless and MFA halves, `confirm-email` | A token, whenever the request came from **a site this deployment does not recognise** | There is no cookie to protect yet. The attack is login CSRF: another site signing a visitor into an account *it* controls. What separates that from a legitimate sign-in is not a cookie — it is where the request came from |
991
+ | **`/auth/exchange`** | Its own grant | It acts for a space and is already refused unless the origin is one that space declared — narrower than any list here. A space is embedded on somebody else's domain by design |
992
+ | **`/auth/refresh`, `/auth/logout`** | Nothing | Both authenticate with the refresh credential and must work when the access token has lapsed. Forging either gains an attacker nothing: renewing somebody's session hands the new credential to their own browser |
993
+
994
+ **"A site this deployment recognises"** is decided by two headers a page cannot set: `Sec-Fetch-Site` — the
995
+ browser's own account of where the request came from — and, where that is absent, `Origin`, matched exactly against
996
+ this host or `identity.platformOrigins`, the hosts you already declared. **Neither header present means the caller
997
+ is not a browser**, and a client that is not a browser has no victim's session in it to forge with — which is what
998
+ lets every API client, mobile app and script sign in with nothing extra to send.
999
+
1000
+ #### What a browser client does
1001
+
1002
+ ```js
1003
+ // Once, or whenever a write is refused with 403 and reason "mismatch".
1004
+ const { token } = await (await fetch('/auth/csrf', { credentials: 'include' })).json();
1005
+
1006
+ await fetch('/auth/profile', {
1007
+ method: 'POST',
1008
+ credentials: 'include',
1009
+ headers: { 'content-type': 'application/json', 'x-csrf-token': token },
1010
+ body: JSON.stringify({ email })
1011
+ });
1012
+ ```
1013
+
1014
+ The same token is also written to a **readable cookie** (`<session cookie>_csrf`), so a page that would rather read
1015
+ it out of `document.cookie` than call an endpoint can. A `<form>` without JavaScript posts it as `_csrf` instead.
1016
+
1017
+ **Signing in re-issues it.** The token a signed-out page held is bound to nobody, and every write it attempted
1018
+ afterwards would be refused — so the grant that creates a session writes a fresh cookie bound to it. A client that
1019
+ re-reads the cookie after signing in never notices this exists.
1020
+
1021
+ #### Why it is a signed token and not just a matching cookie
1022
+
1023
+ Plain double-submit compares a cookie to a header, which fails against anyone who can *write* a cookie — a
1024
+ sub-domain they took over. Here the token is an HMAC over a nonce **and the session it belongs to**, so forging one
1025
+ needs the secret, and a token minted for one session is refused for another. A refusal always names a `reason`:
1026
+ `missing`, `malformed`, `expired` or `mismatch`.
1027
+
1028
+ There is nothing to configure: the secret, the cookie scope and the origins all come from what you already told
1029
+ `createAuth`. Turn it off entirely with `csrf: false`. Outside this package, `createCsrfMiddleware` from
1030
+ `@plitzi/sdk-server/handlers` applies the same check to your own routes.
756
1031
 
757
1032
  ### `SSRUser`
758
1033
 
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Server actions, for the deployment that supplies them.
3
+ *
4
+ * Its own entry, and not part of the barrel, for the same reason `mysql` is: what a deployment needs here is the
5
+ * task contract and the run types, and reaching for them through the package root would load the whole render
6
+ * path to get at a type. A page server that runs no actions never touches this file.
7
+ *
8
+ * The server builds the module itself from `action.lookups` — that is not a deployment's call — so what is
9
+ * exported is what a deployment writes AGAINST: its own tasks, and the shapes its lookups must answer.
10
+ */
11
+ /**
12
+ * Builds a runner outside a page server.
13
+ *
14
+ * The page server builds its own from `action.lookups`; this is for the deployment that needs to run an action
15
+ * from somewhere else — a scheduler, a queue consumer, or the API role answering a builder's test run. Same
16
+ * runner, same checks: a trigger that skipped them would be a weaker path to the same work.
17
+ */
18
+ export { createActionsModule } from './modules/actions';
19
+ export { createScheduleRunner } from './modules/actions/runtime/schedule';
20
+ /**
21
+ * Cron, straight from `sdk-shared`, where the parser lives because the validator needs it too.
22
+ *
23
+ * Re-exported HERE and nowhere in between: a deployment mounting its own scheduler reaches for one package, and
24
+ * the file that used to sit in `runtime/` existed only so a neighbouring import could look local.
25
+ */
26
+ export { cronMatches, parseCron } from '@plitzi/sdk-shared/actions';
27
+ /**
28
+ * What only this deployment can answer about an action, before anybody runs it: a task it does not register, a
29
+ * credential the space has not got, a key missing from the one it has, a connector that was deleted, an engine
30
+ * with no driver, a cron that will never fire. The document validator in `sdk-shared` catches the other half.
31
+ */
32
+ export { checkAction } from './modules/actions/runtime/check';
33
+ export type { ActionCheckDeps } from './modules/actions/runtime/check';
34
+ export { ActionRunError } from './modules/actions/runtime/errors';
35
+ export { DEFAULT_LIMITS } from './modules/actions/runtime/limits';
36
+ export { createTaskRegistry, taskName } from './modules/actions/tasks/registry';
37
+ export { describeCatalog, describeTask } from './modules/actions/taskCatalog';
38
+ /**
39
+ * The `kv` seam: an ADAPTER a deployment fills, and the logic that sits on top of it.
40
+ *
41
+ * There is deliberately nothing here that talks to a store — no Redis, no database, no client of any kind. A
42
+ * deployment passes five string operations over whatever it already runs, and `createKvStore` supplies everything
43
+ * that decides how a counter behaves, so the rule a rate limit depends on is written once rather than once per
44
+ * deployment. The in-process Map is the same shape, which is why the default and a cluster's store behave
45
+ * identically instead of nearly so.
46
+ */
47
+ export { createMemoryKv } from './modules/actions/runtime/memoryKv';
48
+ export { createKvStore } from './modules/actions/runtime/kvStore';
49
+ /**
50
+ * `onRun` and `onReject` for a deployment that wants to SEE its flows without building somewhere to keep them: a
51
+ * run — and a request that never became one — becomes one more event on the log stream the server already
52
+ * reports through.
53
+ *
54
+ * The two are separate hooks because they are separate questions. A deployment that keeps run history in a table
55
+ * and still wants refusals on the log stream wires one of each; that is the normal shape, not a workaround.
56
+ */
57
+ export { createRunLogger, createRejectLogger } from './modules/actions/runtime/runLogger';
58
+ export type { ActionCredential, ActionDbDriver, ActionKvAdapter, ActionKvStore, ActionLookups, ActionRejectRecord, ActionRunRecord, ActionRunRequest, ActionRunResult, ActionsConfig, ActionTask, ActionTaskContext, ActionTaskRegistry, RegisteredTask, ResolvedConnector } from './modules/actions/types';
59
+ export type { ActionsModule } from './modules/actions';
60
+ export type { ActiveRun, RunGuards } from './modules/actions/runtime/guards';
61
+ export type { ScheduleResult, ScheduleRunner, ScheduleTick } from './modules/actions/runtime/schedule';
62
+ export type { ActionTaskDescriptor } from './modules/actions/taskCatalog';
@@ -0,0 +1,12 @@
1
+ import { ActionRunError } from "./modules/actions/runtime/errors.js";
2
+ import { createKvStore } from "./modules/actions/runtime/kvStore.js";
3
+ import { DEFAULT_LIMITS } from "./modules/actions/runtime/limits.js";
4
+ import { createMemoryKv } from "./modules/actions/runtime/memoryKv.js";
5
+ import { createTaskRegistry, taskName } from "./modules/actions/tasks/registry.js";
6
+ import { checkAction } from "./modules/actions/runtime/check.js";
7
+ import { createScheduleRunner } from "./modules/actions/runtime/schedule.js";
8
+ import { createRejectLogger, createRunLogger } from "./modules/actions/runtime/runLogger.js";
9
+ import { describeCatalog, describeTask } from "./modules/actions/taskCatalog.js";
10
+ import { createActionsModule } from "./modules/actions/index.js";
11
+ import { cronMatches, parseCron } from "@plitzi/sdk-shared/actions";
12
+ export { ActionRunError, DEFAULT_LIMITS, checkAction, createActionsModule, createKvStore, createMemoryKv, createRejectLogger, createRunLogger, createScheduleRunner, createTaskRegistry, cronMatches, describeCatalog, describeTask, parseCron, taskName };
@@ -0,0 +1,72 @@
1
+ import { Environment, SSRPageAdapters, SSRSpaceDeployment } from '@plitzi/sdk-shared';
2
+ /**
3
+ * Where a fetched space is kept between requests. Five operations over strings, with no rule to obey — Redis, a
4
+ * table, a directory, whatever this deployment already runs.
5
+ *
6
+ * Omitted leaves an in-process Map, which is per replica: five replicas mean five copies and five refreshes per
7
+ * window. Sharing one costs the cloud a single fetch per version no matter how many of them there are, and it
8
+ * survives a restart — which is what keeps a cold deploy from asking Plitzi once per replica.
9
+ */
10
+ export type CloudSpaceCache = {
11
+ get: (key: string) => Promise<string | undefined>;
12
+ set: (key: string, value: string) => Promise<void>;
13
+ };
14
+ export type CloudAdaptersConfig = {
15
+ /** The GraphQL endpoint of the Plitzi server role. Defaults to production. */
16
+ serverUrl?: string;
17
+ /**
18
+ * The space's **host** key, which is what says WHICH space: the token is minted for one, so no space id travels
19
+ * here and none can be asked for by guessing a number.
20
+ *
21
+ * This is NOT the public render key that a published page embeds. It is issued separately, shown once, and must
22
+ * stay secret — see {@link assertHostKey} for why the public one is refused here rather than being allowed to
23
+ * work.
24
+ */
25
+ webKey: string;
26
+ /**
27
+ * Which environment to serve. `main` is the live document the builder is editing; anything else is published.
28
+ *
29
+ * This is the single switch between the two modes below, and it is worth being deliberate about: `main` is a
30
+ * development target and a published environment is a production one.
31
+ */
32
+ environment?: Environment;
33
+ /**
34
+ * Which published revision to serve. **Ignored when `environment` is `main`** — the live document has no
35
+ * revisions, it has whatever the builder last saved.
36
+ *
37
+ * Pinned to a number, this deployment serves that exact version until somebody changes the config: a revision
38
+ * cannot change, so it is fetched once and kept for the life of the process. That is what a deployment wants
39
+ * when it rolls forward on its own schedule, or when it has to be able to say precisely what it is serving.
40
+ *
41
+ * Left out, it serves the **latest** revision of that environment and notices when a new one is published: a
42
+ * cheap probe every `cacheSeconds` asks which revision is current, and the space is refetched only when the
43
+ * answer changes. Publishing from the builder is then all it takes to release.
44
+ */
45
+ revision?: number;
46
+ /**
47
+ * How often Plitzi is ASKED anything, in seconds. Default a minute. Ignored in both directions at the edges:
48
+ *
49
+ * - `main` **does not cache at all**. It is the document somebody is actively editing, and an edit that shows
50
+ * up a minute later is worse than the round trip — every request reads it live. (Concurrent requests still
51
+ * share one in-flight fetch: that is not caching, it is not asking the same question twice at once.)
52
+ * - A **pinned revision** never expires. Expiring an immutable document on a timer would be paying for a
53
+ * question whose answer is already known.
54
+ *
55
+ * So this paces exactly one thing: the "which revision is current" probe, in latest mode. Even then it never
56
+ * sits in front of a visitor — the copy already held is served while the probe runs behind it.
57
+ */
58
+ cacheSeconds?: number;
59
+ /** Where fetched spaces are kept. Omitted, they are kept in this process only — see {@link CloudSpaceCache}. */
60
+ cache?: CloudSpaceCache;
61
+ /**
62
+ * What this server calls the space locally — the number every per-space thing here is keyed by (its actions, its
63
+ * cache entries, its logs). It is NOT Plitzi's id and does not have to match one: the key already says which
64
+ * space is being read, and a deployment serving one space has one number to choose.
65
+ */
66
+ spaceId?: number;
67
+ /** Who may frame this space, and whether a render counts as authoring. Passed through to the page server. */
68
+ deployment?: Omit<SSRSpaceDeployment, 'spaceId' | 'environment' | 'revision'>;
69
+ fetchImpl?: typeof fetch;
70
+ };
71
+ export declare const createCloudAdapters: (config: CloudAdaptersConfig) => SSRPageAdapters;
72
+ export default createCloudAdapters;