@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.
- package/CHANGELOG.md +44 -0
- package/README.md +283 -8
- package/dist/actions.d.ts +62 -0
- package/dist/actions.js +12 -0
- package/dist/adapters/cloudAdapters.d.ts +72 -0
- package/dist/adapters/cloudAdapters.js +303 -0
- package/dist/adapters/space/index.d.ts +93 -0
- package/dist/adapters/space/index.js +122 -0
- package/dist/adapters/space/resolvers.d.ts +69 -0
- package/dist/adapters/space/resolvers.js +79 -0
- package/dist/adapters/space/space.test.d.ts +1 -0
- package/dist/adapters/space/types.d.ts +64 -0
- package/dist/adapters/space/types.js +41 -0
- package/dist/auth.d.ts +50 -0
- package/dist/auth.js +19 -0
- package/dist/core/auth/api.d.ts +400 -11
- package/dist/core/auth/api.js +1044 -50
- package/dist/core/auth/createAuth.d.ts +135 -30
- package/dist/core/auth/createAuth.js +96 -24
- package/dist/core/auth/credentials.d.ts +9 -3
- package/dist/core/auth/credentials.js +10 -4
- package/dist/core/auth/csrf.d.ts +89 -0
- package/dist/core/auth/csrf.js +234 -0
- package/dist/core/auth/csrf.test.d.ts +1 -0
- package/dist/core/auth/identity.d.ts +6 -0
- package/dist/core/auth/identity.js +37 -8
- package/dist/core/auth/lifecycle.test.d.ts +1 -0
- package/dist/core/auth/oauth/index.d.ts +8 -0
- package/dist/core/auth/oauth/index.js +13 -15
- package/dist/core/auth/passwords.d.ts +15 -0
- package/dist/core/auth/passwords.js +32 -0
- package/dist/core/auth/redirects.d.ts +18 -0
- package/dist/core/auth/redirects.js +31 -0
- package/dist/core/auth/redirects.test.d.ts +1 -0
- package/dist/core/auth/routes.d.ts +4 -2
- package/dist/core/auth/routes.js +234 -9
- package/dist/core/auth/session.d.ts +11 -0
- package/dist/core/auth/session.js +1 -1
- package/dist/core/auth/spaceTokens.d.ts +38 -2
- package/dist/core/auth/spaceTokens.js +54 -2
- package/dist/core/auth/throttle.d.ts +5 -0
- package/dist/core/auth/throttle.js +81 -0
- package/dist/core/auth/throttle.test.d.ts +1 -0
- package/dist/core/auth/tokens.d.ts +83 -12
- package/dist/core/auth/tokens.js +51 -9
- package/dist/core/auth/totp.d.ts +48 -0
- package/dist/core/auth/totp.js +118 -0
- package/dist/core/auth/totp.test.d.ts +1 -0
- package/dist/core/createServer.d.ts +0 -6
- package/dist/core/createServer.js +35 -3
- package/dist/core/fontStore.d.ts +33 -0
- package/dist/core/fontStore.js +53 -0
- package/dist/core/fontStore.test.d.ts +1 -0
- package/dist/core/handlers/authMiddleware.js +1 -1
- package/dist/core/handlers/authRouteHandlers.d.ts +9 -1
- package/dist/core/handlers/authRouteHandlers.js +4 -3
- package/dist/core/handlers/csrfMiddleware.d.ts +20 -0
- package/dist/core/handlers/csrfMiddleware.js +32 -0
- package/dist/core/handlers/oauthRouteHandlers.d.ts +46 -0
- package/dist/core/handlers/oauthRouteHandlers.js +159 -0
- package/dist/core/handlers/socialRouteHandlers.d.ts +40 -0
- package/dist/core/handlers/socialRouteHandlers.js +129 -0
- package/dist/core/handlers/socialRouteHandlers.test.d.ts +1 -0
- package/dist/core/handlers/types.d.ts +22 -5
- package/dist/core/health.d.ts +11 -1
- package/dist/core/health.js +27 -3
- package/dist/core/http/dispatcher.d.ts +1 -1
- package/dist/core/http/dispatcher.js +20 -1
- package/dist/core/http/stages/authApi.js +1 -1
- package/dist/core/http/stages/fontAssets.d.ts +9 -0
- package/dist/core/http/stages/fontAssets.js +27 -0
- package/dist/core/http/stages/oauth.d.ts +19 -0
- package/dist/core/http/stages/oauth.js +118 -0
- package/dist/core/http/types.d.ts +12 -0
- package/dist/core/oauth/authorize.d.ts +22 -0
- package/dist/core/oauth/authorize.js +254 -0
- package/dist/core/oauth/authorize.test.d.ts +1 -0
- package/dist/core/oauth/challenge.d.ts +11 -0
- package/dist/core/oauth/challenge.js +33 -0
- package/dist/core/oauth/consentPage.d.ts +12 -0
- package/dist/core/oauth/consentPage.js +131 -0
- package/dist/core/oauth/metadata.d.ts +41 -0
- package/dist/core/oauth/metadata.js +71 -0
- package/dist/core/oauth/params.d.ts +5 -0
- package/dist/core/oauth/params.js +5 -0
- package/dist/core/oauth/pkce.d.ts +6 -0
- package/dist/core/oauth/pkce.js +15 -0
- package/dist/core/oauth/records.d.ts +72 -0
- package/dist/core/oauth/records.js +49 -0
- package/dist/core/oauth/register.d.ts +5 -0
- package/dist/core/oauth/register.js +55 -0
- package/dist/core/oauth/respond.d.ts +21 -0
- package/dist/core/oauth/respond.js +59 -0
- package/dist/core/oauth/token.d.ts +14 -0
- package/dist/core/oauth/token.js +130 -0
- package/dist/core/previewToken.d.ts +20 -0
- package/dist/core/previewToken.js +29 -1
- package/dist/core/server/pageServer.js +4 -1
- package/dist/core/services/action.d.ts +6 -4
- package/dist/core/services/action.js +103 -10
- package/dist/core/services/registry.js +2 -0
- package/dist/core/services/resolve.d.ts +8 -0
- package/dist/core/services/resolve.js +12 -1
- package/dist/core/staticFiles.js +1 -2
- package/dist/handlers.d.ts +7 -1
- package/dist/handlers.js +4 -1
- package/dist/helpers/buildResponseHelpers.d.ts +8 -0
- package/dist/helpers/buildResponseHelpers.js +9 -1
- package/dist/helpers/buildServerInfo.d.ts +8 -3
- package/dist/helpers/buildServerInfo.js +18 -14
- package/dist/helpers/cache/keys.d.ts +13 -0
- package/dist/helpers/cache/keys.js +12 -1
- package/dist/helpers/onAbort.d.ts +12 -0
- package/dist/helpers/onAbort.js +22 -0
- package/dist/helpers/onAbort.test.d.ts +1 -0
- package/dist/helpers/outboundGuard.d.ts +27 -0
- package/dist/helpers/outboundGuard.js +49 -0
- package/dist/helpers/outboundGuard.test.d.ts +1 -0
- package/dist/helpers/serverLog.d.ts +6 -3
- package/dist/helpers/serverLog.js +20 -3
- package/dist/index.d.ts +6 -0
- package/dist/index.js +9 -4
- package/dist/kernel.d.ts +5 -22
- package/dist/kernel.js +1 -14
- package/dist/modules/actions/{handler.js → connectorWrite.js} +5 -2
- package/dist/modules/actions/connectorWrite.test.d.ts +1 -0
- package/dist/modules/actions/index.d.ts +40 -0
- package/dist/modules/actions/index.js +46 -0
- package/dist/modules/actions/moduleFor.d.ts +3 -0
- package/dist/modules/actions/moduleFor.js +35 -0
- package/dist/modules/actions/moduleFor.test.d.ts +1 -0
- package/dist/modules/actions/runtime/check.d.ts +24 -0
- package/dist/modules/actions/runtime/check.js +111 -0
- package/dist/modules/actions/runtime/check.test.d.ts +1 -0
- package/dist/modules/actions/runtime/errors.d.ts +11 -0
- package/dist/modules/actions/runtime/errors.js +17 -0
- package/dist/modules/actions/runtime/guards.d.ts +126 -0
- package/dist/modules/actions/runtime/guards.js +246 -0
- package/dist/modules/actions/runtime/guards.test.d.ts +1 -0
- package/dist/modules/actions/runtime/kvStore.d.ts +27 -0
- package/dist/modules/actions/runtime/kvStore.js +41 -0
- package/dist/modules/actions/runtime/kvStore.test.d.ts +1 -0
- package/dist/modules/actions/runtime/limits.d.ts +11 -0
- package/dist/modules/actions/runtime/limits.js +35 -0
- package/dist/modules/actions/runtime/memoryKv.d.ts +2 -0
- package/dist/modules/actions/runtime/memoryKv.js +77 -0
- package/dist/modules/actions/runtime/namespaceKv.d.ts +9 -0
- package/dist/modules/actions/runtime/namespaceKv.js +19 -0
- package/dist/modules/actions/runtime/precheck.d.ts +27 -0
- package/dist/modules/actions/runtime/precheck.js +45 -0
- package/dist/modules/actions/runtime/renderResolver.d.ts +15 -0
- package/dist/modules/actions/runtime/renderResolver.js +130 -0
- package/dist/modules/actions/runtime/renderResolver.test.d.ts +1 -0
- package/dist/modules/actions/runtime/renderShare.d.ts +29 -0
- package/dist/modules/actions/runtime/renderShare.js +36 -0
- package/dist/modules/actions/runtime/renderShare.test.d.ts +1 -0
- package/dist/modules/actions/runtime/report.d.ts +14 -0
- package/dist/modules/actions/runtime/report.js +22 -0
- package/dist/modules/actions/runtime/runAction.d.ts +12 -0
- package/dist/modules/actions/runtime/runAction.js +405 -0
- package/dist/modules/actions/runtime/runAction.test.d.ts +1 -0
- package/dist/modules/actions/runtime/runLogger.d.ts +31 -0
- package/dist/modules/actions/runtime/runLogger.js +66 -0
- package/dist/modules/actions/runtime/runLogger.test.d.ts +1 -0
- package/dist/modules/actions/runtime/schedule.d.ts +35 -0
- package/dist/modules/actions/runtime/schedule.js +76 -0
- package/dist/modules/actions/runtime/schedule.test.d.ts +1 -0
- package/dist/modules/actions/runtime/scope.d.ts +40 -0
- package/dist/modules/actions/runtime/scope.js +103 -0
- package/dist/modules/actions/runtime/triggers.d.ts +12 -0
- package/dist/modules/actions/runtime/triggers.js +14 -0
- package/dist/modules/actions/taskCatalog.d.ts +26 -0
- package/dist/modules/actions/taskCatalog.js +24 -0
- package/dist/modules/actions/taskCatalog.test.d.ts +1 -0
- package/dist/modules/actions/tasks/auth.d.ts +2 -0
- package/dist/modules/actions/tasks/auth.js +40 -0
- package/dist/modules/actions/tasks/builtins.d.ts +8 -0
- package/dist/modules/actions/tasks/builtins.js +24 -0
- package/dist/modules/actions/tasks/connector.d.ts +2 -0
- package/dist/modules/actions/tasks/connector.js +141 -0
- package/dist/modules/actions/tasks/db.d.ts +2 -0
- package/dist/modules/actions/tasks/db.js +59 -0
- package/dist/modules/actions/tasks/db.test.d.ts +1 -0
- package/dist/modules/actions/tasks/flow.d.ts +3 -0
- package/dist/modules/actions/tasks/flow.js +109 -0
- package/dist/modules/actions/tasks/helpers.d.ts +16 -0
- package/dist/modules/actions/tasks/helpers.js +30 -0
- package/dist/modules/actions/tasks/http.d.ts +2 -0
- package/dist/modules/actions/tasks/http.js +111 -0
- package/dist/modules/actions/tasks/http.test.d.ts +1 -0
- package/dist/modules/actions/tasks/kv.d.ts +2 -0
- package/dist/modules/actions/tasks/kv.js +102 -0
- package/dist/modules/actions/tasks/kv.test.d.ts +1 -0
- package/dist/modules/actions/tasks/registry.d.ts +10 -0
- package/dist/modules/actions/tasks/registry.js +36 -0
- package/dist/modules/actions/tasks/transform.d.ts +2 -0
- package/dist/modules/actions/tasks/transform.js +34 -0
- package/dist/modules/actions/transport/callHandler.d.ts +24 -0
- package/dist/modules/actions/transport/callHandler.js +233 -0
- package/dist/modules/actions/transport/callHandler.test.d.ts +1 -0
- package/dist/modules/actions/transport/cancelHandler.d.ts +23 -0
- package/dist/modules/actions/transport/cancelHandler.js +23 -0
- package/dist/modules/actions/transport/catalogHandler.d.ts +18 -0
- package/dist/modules/actions/transport/catalogHandler.js +21 -0
- package/dist/modules/actions/transport/stream.d.ts +23 -0
- package/dist/modules/actions/transport/stream.js +100 -0
- package/dist/modules/actions/transport/verifySignature.d.ts +16 -0
- package/dist/modules/actions/transport/verifySignature.js +72 -0
- package/dist/modules/actions/transport/webhookHandler.d.ts +25 -0
- package/dist/modules/actions/transport/webhookHandler.js +206 -0
- package/dist/modules/actions/transport/webhookHandler.test.d.ts +1 -0
- package/dist/modules/actions/types.d.ts +234 -0
- package/dist/modules/connectors/engine.js +4 -1
- package/dist/modules/connectors/resolver.d.ts +10 -2
- package/dist/modules/connectors/resolver.js +8 -3
- package/dist/modules/mysql/accounts.d.ts +5 -0
- package/dist/modules/mysql/accounts.js +365 -0
- package/dist/modules/mysql/accounts.test.d.ts +1 -0
- package/dist/modules/mysql/admin.d.ts +58 -0
- package/dist/modules/mysql/admin.js +133 -0
- package/dist/modules/mysql/config.d.ts +82 -0
- package/dist/modules/mysql/config.js +30 -0
- package/dist/modules/mysql/identities.d.ts +35 -0
- package/dist/modules/mysql/identities.js +43 -0
- package/dist/modules/mysql/index.d.ts +67 -0
- package/dist/modules/mysql/index.js +62 -0
- package/dist/modules/mysql/pool.d.ts +3 -0
- package/dist/modules/mysql/pool.js +88 -0
- package/dist/modules/mysql/query.d.ts +12 -0
- package/dist/modules/mysql/query.js +21 -0
- package/dist/modules/mysql/schema.d.ts +43 -0
- package/dist/modules/mysql/schema.js +340 -0
- package/dist/modules/mysql/schema.test.d.ts +1 -0
- package/dist/modules/mysql/sessions.d.ts +33 -0
- package/dist/modules/mysql/sessions.js +46 -0
- package/dist/modules/mysql/spaceTokens.d.ts +11 -0
- package/dist/modules/mysql/spaceTokens.js +59 -0
- package/dist/modules/mysql/store.integration.test.d.ts +1 -0
- package/dist/modules/rsc/connectorRscData.d.ts +25 -0
- package/dist/modules/rsc/connectorRscData.js +51 -0
- package/dist/modules/rsc/connectorRscData.test.d.ts +1 -0
- package/dist/modules/rsc/handler.js +16 -3
- package/dist/modules/rsc/matchRscPage.d.ts +14 -0
- package/dist/modules/rsc/matchRscPage.js +26 -0
- package/dist/modules/rsc/resolveRscData.d.ts +14 -1
- package/dist/modules/rsc/resolveRscData.js +23 -34
- package/dist/modules/ssr/Component.d.ts +6 -2
- package/dist/modules/ssr/Component.js +3 -1
- package/dist/modules/ssr/draftSession.test.d.ts +1 -0
- package/dist/modules/ssr/handler.js +15 -2
- package/dist/modules/ssr/loadPluginComponents.js +22 -5
- package/dist/modules/ssr/prepareRender.js +87 -8
- package/dist/modules/ssr/prepareRender.test.d.ts +1 -0
- package/dist/modules/ssr/preview.d.ts +21 -8
- package/dist/modules/ssr/preview.js +38 -14
- package/dist/modules/ssr/registerExternalPlugins.js +2 -2
- package/dist/modules/ssr/resolvePageSeo.d.ts +18 -0
- package/dist/modules/ssr/resolvePageSeo.js +26 -0
- package/dist/modules/ssr/resolvePageSeo.test.d.ts +1 -0
- package/dist/modules/ssr/template.test.d.ts +1 -0
- package/dist/modules/ssr/views/template.ejs +71 -5
- package/dist/mysql.d.ts +26 -0
- package/dist/mysql.js +4 -0
- package/dist/oauth.d.ts +24 -0
- package/dist/oauth.js +10 -0
- package/dist/plugins/compile.d.ts +1 -0
- package/dist/plugins/compile.js +37 -3
- package/dist/plugins/manager.d.ts +40 -0
- package/dist/plugins/manager.js +113 -13
- package/dist/plugins/manager.test.d.ts +1 -0
- package/package.json +43 -14
- package/skills/plitzi-authoring/SKILL.md +252 -0
- /package/dist/{modules/actions/handler.test.d.ts → adapters/cloudAdapters.test.d.ts} +0 -0
- /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
|
|
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'` |
|
|
505
|
-
| `'lazy'` |
|
|
506
|
-
| `'visible'` |
|
|
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/
|
|
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
|
-
|
|
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';
|
package/dist/actions.js
ADDED
|
@@ -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;
|