@plitzi/sdk-shared 0.33.2 → 0.34.1

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 (204) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/actions/cron.d.ts +34 -0
  3. package/dist/actions/cron.mjs +72 -0
  4. package/dist/actions/index.d.ts +6 -0
  5. package/dist/actions/index.mjs +5 -0
  6. package/dist/actions/triggerParams.d.ts +41 -0
  7. package/dist/actions/triggerParams.mjs +40 -0
  8. package/dist/actions/triggers.d.ts +29 -0
  9. package/dist/actions/triggers.mjs +4 -0
  10. package/dist/actions/validateDocument.d.ts +23 -0
  11. package/dist/actions/validateDocument.mjs +267 -0
  12. package/dist/auth/failureChannel.d.ts +9 -4
  13. package/dist/auth/failureChannel.mjs +3 -2
  14. package/dist/authoring/builder.d.ts +51 -0
  15. package/dist/authoring/builder.mjs +29 -0
  16. package/dist/authoring/declare.d.ts +64 -0
  17. package/dist/authoring/declare.mjs +4 -0
  18. package/dist/authoring/elementCallbacks.d.ts +13 -0
  19. package/dist/authoring/elementCallbacks.mjs +71 -0
  20. package/dist/authoring/index.d.ts +21 -0
  21. package/dist/authoring/index.mjs +6 -0
  22. package/dist/authoring/paramSpec.d.ts +51 -0
  23. package/dist/authoring/paramSpec.mjs +40 -0
  24. package/dist/authoring/spaceCallbacks.d.ts +10 -0
  25. package/dist/authoring/spaceCallbacks.mjs +51 -0
  26. package/dist/builder/contexts/BuilderContext.d.ts +2 -4
  27. package/dist/dataSource/getBindingsDetails.mjs +1 -1
  28. package/dist/dataSource/helpers/getSourceName.d.ts +1 -3
  29. package/dist/dataSource/helpers/getSourceName.mjs +1 -1
  30. package/dist/dataSource/index.d.ts +2 -0
  31. package/dist/dataSource/index.mjs +6 -5
  32. package/dist/dataSource/resolveVariables.d.ts +22 -0
  33. package/dist/dataSource/resolveVariables.mjs +16 -0
  34. package/dist/dataSource/utility/index.mjs +15 -13
  35. package/dist/dataSource/utility/not.d.ts +11 -0
  36. package/dist/dataSource/utility/not.mjs +15 -0
  37. package/dist/devTools/debugCookie.d.ts +16 -0
  38. package/dist/devTools/debugCookie.mjs +7 -0
  39. package/dist/devTools/index.d.ts +6 -2
  40. package/dist/devTools/index.mjs +4 -2
  41. package/dist/devTools/interactionLog.d.ts +14 -0
  42. package/dist/devTools/interactionLog.mjs +4 -0
  43. package/dist/helpers/index.d.ts +2 -0
  44. package/dist/helpers/index.mjs +11 -9
  45. package/dist/helpers/reducerOrigin.d.ts +15 -0
  46. package/dist/helpers/reducerOrigin.mjs +4 -0
  47. package/dist/helpers/ruleEvaluator.d.ts +14 -0
  48. package/dist/helpers/ruleEvaluator.mjs +5 -0
  49. package/dist/helpers/twigWrapper/AST.d.ts +12 -1
  50. package/dist/helpers/twigWrapper/AST.mjs +0 -0
  51. package/dist/helpers/twigWrapper/Evaluator/Evaluator.mjs +7 -0
  52. package/dist/helpers/twigWrapper/Parser/Cursor.d.ts +7 -0
  53. package/dist/helpers/twigWrapper/Parser/Cursor.mjs +5 -0
  54. package/dist/helpers/twigWrapper/Parser/ExpressionParser.mjs +46 -8
  55. package/dist/helpers/twigWrapper/Parser/types.mjs +0 -0
  56. package/dist/helpers/twigWrapper/charClass.d.ts +7 -0
  57. package/dist/helpers/twigWrapper/charClass.mjs +16 -12
  58. package/dist/helpers/twigWrapper/tokens/tokenPatterns.mjs +2 -2
  59. package/dist/helpers/utils.d.ts +0 -2
  60. package/dist/helpers/utils.mjs +16 -19
  61. package/dist/hooks/index.d.ts +2 -1
  62. package/dist/hooks/index.mjs +4 -3
  63. package/dist/hooks/useFontHead.d.ts +3 -0
  64. package/dist/hooks/useFontHead.mjs +23 -0
  65. package/dist/hooks/usePlitziServiceContext.d.ts +3 -2
  66. package/dist/index.mjs +61 -45
  67. package/dist/navigation/index.d.ts +1 -1
  68. package/dist/navigation/index.mjs +2 -2
  69. package/dist/navigation/routes.d.ts +8 -0
  70. package/dist/navigation/routes.mjs +39 -39
  71. package/dist/network/graphql/builder/Mutations/Segment/SegmentAddMutation.mjs +0 -1
  72. package/dist/network/graphql/builder/Mutations/Segment/SegmentAddTemplateMutation.mjs +0 -1
  73. package/dist/network/graphql/builder/Mutations/Segment/SegmentUpdateMutation.mjs +0 -1
  74. package/dist/network/graphql/builder/Mutations/Segment/space/elements/SegmentAddElementMutation.mjs +0 -1
  75. package/dist/network/graphql/builder/Mutations/Segment/space/elements/SegmentCloneElementMutation.mjs +0 -1
  76. package/dist/network/graphql/builder/Mutations/Segment/space/elements/SegmentMoveElementMutation.mjs +0 -1
  77. package/dist/network/graphql/builder/Mutations/Segment/space/elements/SegmentRemoveElementMutation.mjs +0 -1
  78. package/dist/network/graphql/builder/Mutations/Segment/space/elements/SegmentRenameElementMutation.d.ts +3 -0
  79. package/dist/network/graphql/builder/Mutations/Segment/space/elements/SegmentRenameElementMutation.mjs +9 -0
  80. package/dist/network/graphql/builder/Mutations/Segment/space/elements/SegmentUpdateElementMutation.mjs +0 -1
  81. package/dist/network/graphql/builder/Mutations/Segment/space/elements/SegmentUpdateElementsMutation.mjs +0 -1
  82. package/dist/network/graphql/builder/Mutations/Space/SpaceAddElementMutation.mjs +0 -1
  83. package/dist/network/graphql/builder/Mutations/Space/SpaceAddTemplateMutation.mjs +0 -1
  84. package/dist/network/graphql/builder/Mutations/Space/SpaceCloneElementMutation.mjs +0 -1
  85. package/dist/network/graphql/builder/Mutations/Space/SpaceMoveElementMutation.mjs +0 -1
  86. package/dist/network/graphql/builder/Mutations/Space/SpaceRemoveElementMutation.mjs +0 -1
  87. package/dist/network/graphql/builder/Mutations/Space/SpaceRenameElementMutation.d.ts +5 -0
  88. package/dist/network/graphql/builder/Mutations/Space/SpaceRenameElementMutation.mjs +9 -0
  89. package/dist/network/graphql/builder/Mutations/Space/SpaceUpdateElementMutation.mjs +0 -1
  90. package/dist/network/graphql/builder/Mutations/Space/SpaceUpdateElementsMutation.mjs +0 -1
  91. package/dist/network/graphql/builder/Mutations/Space/SpaceUpdateMutation.mjs +0 -1
  92. package/dist/network/graphql/builder/Mutations/Space/SpaceUpdateSchemaMutation.mjs +0 -1
  93. package/dist/network/graphql/builder/Mutations/Space/actions/SpaceAddActionMutation.d.ts +8 -0
  94. package/dist/network/graphql/builder/Mutations/Space/actions/SpaceAddActionMutation.mjs +16 -0
  95. package/dist/network/graphql/builder/Mutations/Space/actions/SpaceRemoveActionMutation.d.ts +4 -0
  96. package/dist/network/graphql/builder/Mutations/Space/actions/SpaceRemoveActionMutation.mjs +16 -0
  97. package/dist/network/graphql/builder/Mutations/Space/actions/SpaceRunActionMutation.d.ts +4 -0
  98. package/dist/network/graphql/builder/Mutations/Space/actions/SpaceRunActionMutation.mjs +14 -0
  99. package/dist/network/graphql/builder/Mutations/Space/actions/SpaceUpdateActionMutation.d.ts +4 -0
  100. package/dist/network/graphql/builder/Mutations/Space/actions/SpaceUpdateActionMutation.mjs +16 -0
  101. package/dist/network/graphql/builder/Mutations/Space/pages/SpaceAddPageMutation.mjs +2 -3
  102. package/dist/network/graphql/builder/Mutations/Space/pages/SpaceUpdatePageMutation.mjs +0 -1
  103. package/dist/network/graphql/builder/Mutations/Style/fonts/StyleAddFontMutation.d.ts +2 -0
  104. package/dist/network/graphql/builder/Mutations/Style/fonts/StyleAddFontMutation.mjs +11 -0
  105. package/dist/network/graphql/builder/Mutations/Style/fonts/StyleRemoveFontMutation.d.ts +2 -0
  106. package/dist/network/graphql/builder/Mutations/Style/fonts/StyleRemoveFontMutation.mjs +11 -0
  107. package/dist/network/graphql/builder/Mutations/Style/fonts/StyleUpdateFontMutation.d.ts +2 -0
  108. package/dist/network/graphql/builder/Mutations/Style/fonts/StyleUpdateFontMutation.mjs +11 -0
  109. package/dist/network/graphql/builder/Mutations/index.d.ts +24 -0
  110. package/dist/network/graphql/builder/Mutations/index.mjs +150 -132
  111. package/dist/network/graphql/builder/Queries/InitQuery.mjs +1 -2
  112. package/dist/network/graphql/builder/Queries/Segment/SegmentQuery.mjs +0 -1
  113. package/dist/network/graphql/builder/Queries/Segment/SegmentsQuery.mjs +0 -1
  114. package/dist/network/graphql/builder/Queries/Space/SpaceActionEventsQuery.d.ts +9 -0
  115. package/dist/network/graphql/builder/Queries/Space/SpaceActionEventsQuery.mjs +30 -0
  116. package/dist/network/graphql/builder/Queries/Space/SpaceActionTasksQuery.d.ts +6 -0
  117. package/dist/network/graphql/builder/Queries/Space/SpaceActionTasksQuery.mjs +16 -0
  118. package/dist/network/graphql/builder/Queries/Space/SpaceActionsQuery.d.ts +9 -0
  119. package/dist/network/graphql/builder/Queries/Space/SpaceActionsQuery.mjs +25 -0
  120. package/dist/network/graphql/builder/Queries/Space/SpaceCheckActionQuery.d.ts +6 -0
  121. package/dist/network/graphql/builder/Queries/Space/SpaceCheckActionQuery.mjs +17 -0
  122. package/dist/network/graphql/builder/Queries/Space/SpaceQuotaQuery.d.ts +30 -0
  123. package/dist/network/graphql/builder/Queries/Space/SpaceQuotaQuery.mjs +40 -0
  124. package/dist/network/graphql/builder/Queries/index.d.ts +16 -0
  125. package/dist/network/graphql/builder/Queries/index.mjs +24 -14
  126. package/dist/network/graphql/sdk/Queries/InitQuery.d.ts +4 -0
  127. package/dist/network/graphql/sdk/Queries/InitQuery.mjs +5 -2
  128. package/dist/network/graphql/sdk/Queries/Segment/SegmentQuery.mjs +0 -1
  129. package/dist/network/graphql/sdk/Queries/Segment/SegmentsQuery.mjs +0 -1
  130. package/dist/network/spaceEvents.d.ts +20 -1
  131. package/dist/network/spaceEvents.mjs +89 -74
  132. package/dist/schema/index.d.ts +1 -0
  133. package/dist/schema/index.mjs +2 -1
  134. package/dist/schema/serverElements.d.ts +16 -0
  135. package/dist/schema/serverElements.mjs +18 -0
  136. package/dist/server/actions/useActionsSync.d.ts +10 -0
  137. package/dist/server/actions/useActionsSync.mjs +7 -0
  138. package/dist/server/index.d.ts +2 -1
  139. package/dist/server/index.mjs +5 -4
  140. package/dist/server/rsc/refreshRsc.d.ts +11 -1
  141. package/dist/server/rsc/refreshRsc.mjs +30 -12
  142. package/dist/server/rsc/useRscSync.mjs +20 -13
  143. package/dist/state/runtimeStatePersist.mjs +4 -3
  144. package/dist/store/actionRuns/actionRunsRecorder.d.ts +36 -0
  145. package/dist/store/actionRuns/actionRunsRecorder.mjs +39 -0
  146. package/dist/store/actionRuns/actionRunsStore.d.ts +16 -0
  147. package/dist/store/actionRuns/actionRunsStore.mjs +5 -0
  148. package/dist/store/actionRuns/index.d.ts +6 -0
  149. package/dist/store/actionRuns/index.mjs +4 -0
  150. package/dist/store/actionRuns/useActionRuns.d.ts +13 -0
  151. package/dist/store/actionRuns/useActionRuns.mjs +10 -0
  152. package/dist/store/index.d.ts +46 -45
  153. package/dist/store/index.mjs +6 -3
  154. package/dist/store/renderSettings.d.ts +3 -0
  155. package/dist/store/renderSettings.mjs +17 -5
  156. package/dist/store/tracing/tracingCollector.d.ts +1 -1
  157. package/dist/store/tracing/tracingCollector.mjs +37 -35
  158. package/dist/store/tracing/tracingStore.d.ts +13 -0
  159. package/dist/store/tracing/tracingStore.mjs +3 -3
  160. package/dist/style/fontAssets.d.ts +12 -0
  161. package/dist/style/fontAssets.mjs +18 -0
  162. package/dist/style/fontValidation.d.ts +8 -0
  163. package/dist/style/fontValidation.mjs +107 -0
  164. package/dist/style/fonts.d.ts +74 -0
  165. package/dist/style/fonts.mjs +198 -0
  166. package/dist/style/fontsFromCss.d.ts +27 -0
  167. package/dist/style/fontsFromCss.mjs +43 -0
  168. package/dist/style/index.d.ts +4 -0
  169. package/dist/style/index.mjs +5 -1
  170. package/dist/style/styleConstants.d.ts +2 -2
  171. package/dist/style/styleConstants.mjs +1 -0
  172. package/dist/theme/ThemeProvider.d.ts +36 -6
  173. package/dist/theme/ThemeProvider.mjs +49 -35
  174. package/dist/theme/ThemeScope.d.ts +16 -0
  175. package/dist/theme/ThemeScope.mjs +6 -0
  176. package/dist/theme/index.d.ts +7 -1
  177. package/dist/theme/index.mjs +6 -2
  178. package/dist/theme/themeCookie.d.ts +30 -0
  179. package/dist/theme/themeCookie.mjs +24 -0
  180. package/dist/theme/themeStore.d.ts +40 -0
  181. package/dist/theme/themeStore.mjs +17 -0
  182. package/dist/theme/useTheme.d.ts +29 -0
  183. package/dist/theme/useTheme.mjs +20 -0
  184. package/dist/types/ActionTypes.d.ts +396 -0
  185. package/dist/types/ActionTypes.mjs +0 -0
  186. package/dist/types/AuthTypes.d.ts +23 -2
  187. package/dist/types/BuilderTypes.d.ts +0 -12
  188. package/dist/types/CommonTypes.d.ts +13 -0
  189. package/dist/types/DevToolsTypes.d.ts +57 -14
  190. package/dist/types/EventBridgeTypes.d.ts +1 -1
  191. package/dist/types/InteractionTypes.d.ts +23 -3
  192. package/dist/types/PluginTypes.d.ts +0 -3
  193. package/dist/types/SchemaTypes.d.ts +29 -4
  194. package/dist/types/ServerTypes.d.ts +390 -46
  195. package/dist/types/SpaceTypes.d.ts +16 -0
  196. package/dist/types/StoreTypes.d.ts +181 -3
  197. package/dist/types/StyleTypes.d.ts +83 -4
  198. package/dist/types/TemplateTypes.d.ts +20 -0
  199. package/dist/types/TemplateTypes.mjs +0 -0
  200. package/dist/types/ThemeTypes.d.ts +28 -1
  201. package/dist/types/index.d.ts +2 -0
  202. package/eslint.config.mjs +2 -0
  203. package/package.json +244 -29
  204. package/vite.config.ts +27 -7
@@ -1,8 +1,9 @@
1
+ import { ActionEntry, ActionLimits, ActionRejectReason, ActionRejectRecord, ActionRunRecord, ActionRunStatus, ActionTaskDescriptor, ActionTriggerType } from './ActionTypes';
1
2
  import { Environment } from './CommonTypes';
2
3
  import { ConnectorEntry } from './ConnectorTypes';
3
4
  import { Schema } from './SchemaTypes';
4
5
  import { AnalyticsConfig, OfflineDataRaw } from './SdkTypes';
5
- import { Style } from './StyleTypes';
6
+ import { FontHead, Style } from './StyleTypes';
6
7
  import { IncomingHttpHeaders } from 'node:http';
7
8
  import { FC } from 'react';
8
9
  export type ServerEnvironment = 'development' | 'production' | 'staging' | 'local';
@@ -33,7 +34,11 @@ export type SSRResponseHelpers = {
33
34
  * several, and collapsing them into one string would produce a single malformed cookie. */
34
35
  setHeader: (name: string, value: string | string[]) => void;
35
36
  setStatus: (code: number) => void;
36
- send: (body: string) => void;
37
+ /**
38
+ * A `Buffer` is sent byte for byte and never compressed: it is how a binary reaches the wire — a font file, an
39
+ * image — and what it holds is usually compressed already. A string keeps the encoding negotiation.
40
+ */
41
+ send: (body: string | Buffer) => void;
37
42
  write: (chunk: string | Buffer) => void;
38
43
  end: () => void;
39
44
  };
@@ -46,7 +51,18 @@ export type SSRCredential = {
46
51
  provider: string;
47
52
  data: unknown;
48
53
  };
49
- export type PluginAction = 'copy' | 'compile' | 'download';
54
+ /**
55
+ * How a plugin's build reaches the page.
56
+ *
57
+ * `copy`, `compile` and `download` all end with a file on this server's disk, which is also what lets the SSR
58
+ * render import the component and put its output in the HTML. `cdn` deliberately does not: the published URL is
59
+ * handed straight to the browser and the plugin renders on the client only.
60
+ *
61
+ * That trade is worth taking for a plugin this server did not build. Importing a third-party bundle into the
62
+ * render process means the bundle's own copy of React runs inside the renderer, and two copies of React is an
63
+ * invalid hook call — a crash that takes the whole page down rather than the one component.
64
+ */
65
+ export type PluginAction = 'copy' | 'compile' | 'download' | 'cdn';
50
66
  export type PluginSourceFile<T = Record<string, unknown>> = {
51
67
  js: string;
52
68
  css?: string;
@@ -74,9 +90,19 @@ export type PluginEntry = {
74
90
  filePath?: string;
75
91
  css?: string;
76
92
  props: Record<string, unknown>;
93
+ /**
94
+ * Whether the SSR render actually included this plugin's own output.
95
+ *
96
+ * False for a plugin served straight from its CDN, and for one whose server-side import failed. Either way the
97
+ * client must not render it on the hydration pass: the markup would not be in the document React is hydrating
98
+ * against, and the mismatch throws away the whole tree it happens in.
99
+ */
100
+ ssr?: boolean;
77
101
  };
78
102
  export type SSRTemplateProps = {
79
103
  title?: string;
104
+ /** Meta description for the document, and the share-preview text derived from it. */
105
+ description?: string;
80
106
  jsPath?: string;
81
107
  cssPath?: string;
82
108
  builderJsPath?: string;
@@ -90,15 +116,52 @@ export type SSRTemplateProps = {
90
116
  /** When true the client-side <script> block is omitted — useful for inspecting raw SSR HTML. */
91
117
  ssrOnly?: boolean;
92
118
  debugMode?: boolean;
119
+ /**
120
+ * The theme class this document wears on `<html>` — `dark`, `light`, or nothing at all.
121
+ *
122
+ * The theme is kept in a cookie, so the server that renders the document already knows it: the page arrives
123
+ * painted correctly, with no blocking script in the head and no first paint in the other theme. Empty is not a
124
+ * failure — it is `system`, and the stylesheet's `prefers-color-scheme` queries answer it.
125
+ */
126
+ themeClass?: string;
127
+ /**
128
+ * The document's web fonts: `fontsToHead(style.fonts, ...)` from this package.
129
+ *
130
+ * In the template rather than left to the SDK because a page paints before its JavaScript exists. A face
131
+ * requested at hydration arrives after the text it is for has already been drawn in a fallback, which is a
132
+ * reflow the visitor watches happen.
133
+ */
134
+ fonts?: FontHead;
135
+ };
136
+ /**
137
+ * How a store-relative font path becomes a URL a browser can fetch.
138
+ *
139
+ * A space's manifest stores paths, never absolute URLs: the same space is rendered by Plitzi's cloud from a CDN,
140
+ * by a self-hosted server from its own disk, and by a static export from wherever it was published to. Baking one
141
+ * of those origins into the document would send every other deployment asking Plitzi for its customer's fonts.
142
+ */
143
+ export type SSRFontsConfig = {
144
+ /** Prefix joined to each path, e.g. `/fonts` or `https://cdn.example.com/fonts`. Default: `/fonts`. */
145
+ baseUrl?: string;
146
+ /**
147
+ * A directory this server serves the uploaded files from, under `/fonts/*`.
148
+ *
149
+ * The self-hosting answer, and the reason `baseUrl` defaults to a path rather than an origin: with a directory
150
+ * here a deployment needs no object storage, no credentials and no network to serve its customers' fonts. A
151
+ * cloud deployment leaves it out and points `baseUrl` at its CDN instead.
152
+ */
153
+ dir?: string;
93
154
  };
94
155
  export type SSRPlugin = {
95
156
  component: FC;
96
157
  props: Record<string, unknown>;
97
158
  };
98
159
  /** What a space token is worth. `render` is the public credential every published site embeds — it is readable
99
- * by anyone who views the page, so it may only read. `agent` is the delegated grant an MCP connector receives
100
- * after a member consents, and is the only bearer that may write without a session behind it. */
101
- export type SpaceScope = 'render' | 'agent';
160
+ * by anyone who views the page, so it may only read, and only from the origins it declares. `host` is that same
161
+ * read access for a SERVER that renders the space as its own (self-hosting): secret rather than published, since
162
+ * a server has no browser origin for the allowlist to check. `agent` is the delegated grant an MCP connector
163
+ * receives after a member consents, and is the only bearer that may write without a session behind it. */
164
+ export type SpaceScope = 'render' | 'agent' | 'host';
102
165
  /** A resolved space token: which space, what the bearer may do, and (for `agent`) the member who consented.
103
166
  * `canWrite` is computed by the consumer from its own authorization model — the MCP never derives it. */
104
167
  export type SSRGrant = {
@@ -220,9 +283,9 @@ export type SSRAdapters = {
220
283
  /** Which space, environment and revision a request resolves to. Optional here for the same reason as
221
284
  * `getOfflineData`: MCP resolves its space from the request token (`getGrant`), never from the host. */
222
285
  getSpaceDeployment?: (req: SSRRequest) => Promise<SSRSpaceDeployment>;
223
- /** Persist a space mutated by the mcp-ai `apply` tool. Implementations must recompute derived caches
224
- * (notably `style.cache`) before storing. When omitted, mcp-ai runs read/preview/validate only and
225
- * `apply` reports `persisted: false`. */
286
+ /** Persist a space mutated by the mcp-ai `apply` tool store it as given; derived caches (notably `style.cache`)
287
+ * arrive already compiled. When omitted, mcp-ai runs read/preview/validate only and `apply` reports
288
+ * `persisted: false`. */
226
289
  saveOfflineData?: (spaceId: number, environment: string, data: OfflineDataRaw) => Promise<void>;
227
290
  /** Resolve the grant the MCP request operates under, from the verified `Authorization` bearer. The consumer
228
291
  * owns the JWT secret and the authorization model, so it decides here; the MCP service stays stateless.
@@ -249,10 +312,25 @@ export type SSRAdapters = {
249
312
  saveConnector?: (spaceId: number, entry: ConnectorEntry) => Promise<void>;
250
313
  /** Remove one connector by its identifier. Omitted alongside `saveConnector` for a read-only deployment. */
251
314
  deleteConnector?: (spaceId: number, connectorId: string) => Promise<void>;
315
+ /** Read every server action configured for the space, so the MCP can list them and author flows against them.
316
+ * Space-level server-side state like connectors: the documents name credentials, connectors and steps, so this
317
+ * must never feed a browser payload. When omitted, the MCP's action resource is empty. */
318
+ getActions?: (spaceId: number) => Promise<ActionEntry[] | undefined>;
319
+ /** The server tasks a flow can be built from — what THIS deployment can run, which is the SDK's built-ins plus
320
+ * whatever tasks it registered. Shaped as `ActionTaskDescriptor` in `@plitzi/sdk-server/actions`. Without it an
321
+ * agent authoring an action is guessing at the step vocabulary. */
322
+ getActionTasks?: (spaceId: number) => Promise<ActionTaskDescriptor[] | undefined>;
323
+ /** Create or replace one action, keyed by `entry.id`. When omitted, action ops apply in memory only and `apply`
324
+ * reports `persisted: false`. */
325
+ saveAction?: (spaceId: number, entry: ActionEntry) => Promise<void>;
326
+ /** Remove one action by its identifier. Omitted alongside `saveAction` for a read-only deployment. */
327
+ deleteAction?: (spaceId: number, actionId: string) => Promise<void>;
252
328
  /** Persist the element schema mutated by the MCP `apply` tool. When omitted, `apply` reports `persisted: false`. */
253
329
  saveSchema?: (spaceId: number, environment: Environment, schema: Schema) => Promise<void>;
254
- /** Persist the style document mutated by the MCP `apply` tool. Implementations must recompute `style.cache`
255
- * before storing. When omitted, `apply` reports `persisted: false`. */
330
+ /** Persist the style document mutated by the MCP `apply` tool store it as given. `style.cache` arrives already
331
+ * compiled: the renderer serves that string and nothing else, so recomputing it is not a detail to delegate, and
332
+ * asking every deployment to remember it was one bug each of them could write alone. When omitted, `apply`
333
+ * reports `persisted: false`. */
256
334
  saveStyle?: (spaceId: number, environment: Environment, style: Style) => Promise<void>;
257
335
  /** Who this request carries, if anyone. The adapter reads the credential and resolves it; the cookie it arrived
258
336
  * in was written by the server, from {@link SSRAuthCookie}. */
@@ -363,17 +441,130 @@ export interface SSRRscContext {
363
441
  */
364
442
  loadOfflineData: () => Promise<OfflineDataRaw | undefined>;
365
443
  }
444
+ /**
445
+ * Which published version of a space is asking.
446
+ *
447
+ * Absent means the live one — the document the builder edits. Present means a page PUBLISHED at that revision,
448
+ * which must read the private documents it was published against: a page shipped yesterday running whatever the
449
+ * action says today, or reading through a manifest since pointed at a different API, is the discrepancy
450
+ * versioning exists to remove.
451
+ *
452
+ * One type for actions and connectors, because it is one concept: a version of a space.
453
+ */
454
+ export type SpaceRevision = {
455
+ environment: Environment;
456
+ revision: number;
457
+ };
458
+ /**
459
+ * How the server reaches a space's actions, and what it may run.
460
+ *
461
+ * Typed loosely for the same reason `ConnectorLookupsConfig` is — the document is executed by
462
+ * `@plitzi/sdk-server`'s actions module and the shared types stay free of its internals. Shaped as `ActionLookups`
463
+ * there.
464
+ */
465
+ export type ActionLookupsConfig = {
466
+ getAction: (spaceId: number, actionId: string, at?: SpaceRevision) => Promise<unknown>;
467
+ listActions?: (spaceId: number, at?: SpaceRevision) => Promise<unknown[]>;
468
+ getCredential?: (spaceId: number, identifier: string) => Promise<Record<string, string> | undefined>;
469
+ getConnector?: (spaceId: number, connectorId: string, at?: SpaceRevision) => Promise<unknown>;
470
+ };
366
471
  export type SSRActionConfig = {
367
- /** URL path for the write endpoint. Defaults to '/_action'. */
472
+ /** URL path for the write endpoint, and the base for the action-addressed routes. Defaults to '/_action'. */
368
473
  path?: string;
369
- /** Connector manifest and credential lookups. Without them the endpoint stays inert: a write can only be
370
- * authorized against a manifest, and there is nothing to authorize against. Shaped as `ConnectorLookups`
371
- * in `@plitzi/sdk-server`; typed loosely here so the shared types stay free of the server's internals. */
372
- lookups?: {
373
- getConnector: (spaceId: number, connectorId: string) => Promise<unknown>;
374
- getCredential?: (spaceId: number, identifier: string) => Promise<Record<string, string> | undefined>;
375
- fetchImpl?: typeof fetch;
474
+ /**
475
+ * Supplying these is what turns server actions on: without a way to read a document there is nothing to run, and
476
+ * the endpoint keeps answering element-addressed connector writes alone.
477
+ */
478
+ lookups?: ActionLookupsConfig;
479
+ /** Deployment-owned tasks, shaped as `ActionTask` in `@plitzi/sdk-server/actions`. Validated at boot. */
480
+ tasks?: unknown[];
481
+ /** Ceilings for every run this server accepts. A document may tighten them, never widen them. */
482
+ limits?: ActionLimits;
483
+ /**
484
+ * Concurrency ceilings.
485
+ *
486
+ * `perSpace`/`perProcess` count CALLS — a click, a webhook, a schedule — where one run is somebody asking for
487
+ * work and one space's callers must not starve another's. A `render` is counted only by `renderPerProcess`,
488
+ * because it arrives because people are reading the page: a ceiling per space there would refuse the five
489
+ * hundredth visitor of a page that is simply doing well.
490
+ */
491
+ concurrency?: {
492
+ perSpace?: number;
493
+ perProcess?: number;
494
+ renderPerProcess?: number;
495
+ };
496
+ /** Inbound webhooks are public by construction, so they are counted per caller per minute. Default 60. */
497
+ rateLimit?: {
498
+ webhookPerMinute?: number;
376
499
  };
500
+ /**
501
+ * Database engines a flow may reach, shaped as `ActionDbDriver` in `@plitzi/sdk-server/actions`.
502
+ *
503
+ * Omitted leaves the `db.query` task unregistered entirely. What a driver connects to is always a database the
504
+ * SPACE declared as a credential — never this deployment's own, which no credential a space holds can name.
505
+ */
506
+ dbDrivers?: unknown[];
507
+ /**
508
+ * Called once per run that STARTED — completed, failed or aborted — for a deployment that keeps a record.
509
+ * Shaped as `ActionRunRecord` in `@plitzi/sdk-server/actions`.
510
+ *
511
+ * A refused request is not a run and is not reported here; it goes to {@link SSRActionConfig.onReject}.
512
+ */
513
+ onRun?: (record: ActionRunRecord) => void | Promise<void>;
514
+ /**
515
+ * Called once per request that was REFUSED before it became a run — a webhook whose signature did not verify, a
516
+ * caller over the rate limit, an action nobody may start. Shaped as `ActionRejectRecord`.
517
+ *
518
+ * Its own hook because it answers a different question from `onRun`: runs are history, refusals are a fault
519
+ * report, and the one that matters most — a signature that does not match — is otherwise indistinguishable
520
+ * from the integration simply never firing. Everything is reported; which refusals are worth keeping (a
521
+ * duplicate delivery is a polite retry, not a fault) is the deployment's to decide.
522
+ */
523
+ onReject?: (record: ActionRejectRecord) => void | Promise<void>;
524
+ /**
525
+ * Replaying a finished run's answer to a caller that asks again with the same key, for this many milliseconds.
526
+ *
527
+ * Off unless set, and it only ever applies to a key the CALLER named — an `idempotencyKey` on the call or the
528
+ * delivery id a webhook sender stamps. Single-flight already refuses a retry that arrives while the first run
529
+ * is going; this is for the one that arrives after it finished, which is how every provider retries.
530
+ */
531
+ idempotency?: {
532
+ replayTtlMs?: number;
533
+ };
534
+ /**
535
+ * Where the `kv` tasks keep things — shaped as `ActionKvAdapter` in `@plitzi/sdk-server/actions`.
536
+ *
537
+ * Five operations over strings, with no rule to obey: Redis, Memcached, a table, whatever this deployment
538
+ * already runs. How a counter BEHAVES — the key prefixing, the JSON round trip, and the rule that a window's
539
+ * lifetime is set once by whoever created it — belongs to the server, not to the thing it writes into.
540
+ *
541
+ * Omitted leaves an in-process Map, which counts only its own replica. That is fine for one; for a cluster it is
542
+ * a rate limit that multiplies by the number of them, so a multi-replica deployment supplies a shared one.
543
+ */
544
+ kv?: {
545
+ get: (key: string) => Promise<string | undefined>;
546
+ set: (key: string, value: string, ttlSeconds?: number) => Promise<void>;
547
+ delete: (key: string) => Promise<void>;
548
+ increment: (key: string, amount: number) => Promise<number>;
549
+ expire: (key: string, ttlSeconds: number) => Promise<void>;
550
+ };
551
+ };
552
+ /**
553
+ * How the server reaches a space's connectors: the manifest that describes one, and the credential it authenticates
554
+ * with. Shaped as `ConnectorLookups` in `@plitzi/sdk-server`; typed loosely here so the shared types stay free of the
555
+ * server's internals.
556
+ *
557
+ * Top-level, and not under `action`, because BOTH directions need them. Supplying them is what makes the write
558
+ * endpoint able to authorize a write — a write can only be checked against a manifest — and it is also all the server
559
+ * needs to answer a server-driven element's READ, so `adapters.getRscData` is derived from them unless a deployment
560
+ * supplies its own. They lived under `action` when writes were the only caller, which left every deployment building
561
+ * that read half by hand out of `createConnectorResolver` and `resolveRscData` — from these very lookups, passed a
562
+ * second time.
563
+ */
564
+ export type ConnectorLookupsConfig = {
565
+ getConnector: (spaceId: number, connectorId: string, at?: SpaceRevision) => Promise<unknown>;
566
+ getCredential?: (spaceId: number, identifier: string) => Promise<Record<string, string> | undefined>;
567
+ fetchImpl?: typeof fetch;
377
568
  };
378
569
  export type SSRHealthConfig = {
379
570
  path?: string;
@@ -414,6 +605,18 @@ export type SSRRscConfig = {
414
605
  path?: string;
415
606
  /** Server-side cache TTL for RSC responses in milliseconds. Defaults to 30 000. Set to 0 to disable. */
416
607
  cacheTtlMs?: number;
608
+ /**
609
+ * How long ONE server element may take before the page is answered without it. Defaults to 5 000.
610
+ *
611
+ * It is the page's ceiling, and it wins over the producer's own: an action may be allowed ten seconds of its
612
+ * own (`action.limits.timeoutMs`) and still be cut off here, because a section is worth waiting for only as
613
+ * long as the visitor is. Whichever is tighter decides, and now that the budget CANCELS what it stops waiting
614
+ * for, being cut here ends the run rather than leaving it to finish for nobody.
615
+ *
616
+ * Raise it for a deployment whose sections are genuinely slow and worth the wait; lower it to keep a page fast
617
+ * at the cost of showing more empty sections when a provider is having a bad day.
618
+ */
619
+ elementTimeoutMs?: number;
417
620
  };
418
621
  /** What every log event carries, whatever layer it came from. */
419
622
  type ServerLogEventBase = {
@@ -459,15 +662,59 @@ export type McpResourceLogEvent = ServerLogEventBase & {
459
662
  /** The resource URI that was read, e.g. 'plitzi://element/hero_1'. */
460
663
  name: string;
461
664
  };
665
+ /** One server action run that STARTED — completed, failed or aborted.
666
+ *
667
+ * Its own event rather than a line on the request that triggered it, for the same reason a tool call is: the
668
+ * request is answered either way, and a run started by a schedule or a webhook has a request that says nothing
669
+ * about it. Where the request log answers "was this call served", this answers "what did the flow do".
670
+ *
671
+ * Carries the SHAPE of the run and never its data: which steps ran and how each ended, never what they
672
+ * returned. A refused run is absent by design — a 409 is not a run, and logging one buries the real ones under
673
+ * retries. */
674
+ export type ActionRunLogEvent = ServerLogEventBase & {
675
+ kind: 'run';
676
+ /** The action's identifier, as the space stores it. */
677
+ name: string;
678
+ spaceId: number;
679
+ environment: Environment;
680
+ /** What started it: a page call, a webhook, a schedule, a render, a trigger the deployment mounted. */
681
+ trigger: ActionTriggerType;
682
+ status: ActionRunStatus;
683
+ /** Who asked, when a session carried it. Absent for a webhook, a schedule or an anonymous visitor. */
684
+ userId?: number;
685
+ /** Each step as `action:status`, in order — enough to see where a flow stopped without keeping what it held. */
686
+ steps: string[];
687
+ };
688
+ /** One request that was REFUSED before it became a run — a webhook whose signature did not verify, a caller over
689
+ * its rate limit, an action nobody may start.
690
+ *
691
+ * Separate from {@link ActionRunLogEvent} because it is a different question: that one says what a flow did,
692
+ * this one says why a flow never happened. It is the only place an integration that is failing at the door
693
+ * shows up at all — the request log answers 401 and says nothing about which check refused it.
694
+ *
695
+ * `durationMs` is 0 and `ok` is always false: nothing ran, and a refusal is not a success on anybody's dashboard. */
696
+ export type ActionRejectLogEvent = ServerLogEventBase & {
697
+ kind: 'reject';
698
+ /** The action's identifier, as the request named it — it need not exist. */
699
+ name: string;
700
+ spaceId: number;
701
+ environment: Environment;
702
+ trigger: ActionTriggerType;
703
+ /** Which check refused it. */
704
+ reason: ActionRejectReason;
705
+ /** Who asked, as the transport identifies them — a session subject or an address. */
706
+ callerId?: string;
707
+ };
462
708
  /** Everything a Plitzi server reports about the work it does, as ONE stream: the HTTP requests it answers, plus
463
- * the MCP tool calls and resource reads that happen inside them. Wire a single sink via `SSRServerConfig.logger`
464
- * and switch on `kind` — a consumer can render it, ship it to a dashboard or drop the kinds it does not want.
709
+ * the MCP tool calls, resource reads and server-action runs that happen inside them. Wire a single sink via
710
+ * `SSRServerConfig.logger` and switch on `kind` — a consumer can render it, ship it to a dashboard or drop the
711
+ * kinds it does not want.
465
712
  *
466
713
  * Payload-free by construction: no headers, cookies, tokens nor request body ever reach an event, query values
467
- * are stripped from paths and tool arguments are reduced to their shape. Two fields are NOT anonymous and a
468
- * consumer shipping these events must handle them accordingly: `clientIp` on a request event, and the request
469
- * path, which is kept verbatim because it is what makes the log usable. */
470
- export type ServerLogEvent = ServerRequestLogEvent | McpToolLogEvent | McpResourceLogEvent;
714
+ * are stripped from paths, tool arguments are reduced to their shape and a run to its steps. Two fields are NOT
715
+ * anonymous and a consumer shipping these events must handle them accordingly: `clientIp` on a request event, and
716
+ * the request path, which is kept verbatim because it is what makes the log usable. */
717
+ export type ServerLogEvent = ServerRequestLogEvent | McpToolLogEvent | McpResourceLogEvent | ActionRunLogEvent | ActionRejectLogEvent;
471
718
  /** The sink a consumer provides to receive every {@link ServerLogEvent} (see `SSRServerConfig.logger`). */
472
719
  export type ServerLogger = (event: ServerLogEvent) => void;
473
720
  export type SSRServerConfig = {
@@ -502,6 +749,8 @@ export type SSRServerConfig = {
502
749
  pluginsTtlMs?: number;
503
750
  /** Auto-download and cache plugins declared in the schema's offlineData.plugins list. Default: true. */
504
751
  autoLoadSchemaPlugins?: boolean;
752
+ /** Where this deployment serves the font files a space uploaded — see {@link SSRFontsConfig}. */
753
+ fonts?: SSRFontsConfig;
505
754
  /** Omit client-side JS from the rendered page — useful for verifying SSR HTML without hydration. Default: false. */
506
755
  ssrOnly?: boolean;
507
756
  /** Stream HTML to the client as React renders, reducing TTFB. Default: false. */
@@ -516,6 +765,9 @@ export type SSRServerConfig = {
516
765
  rsc?: SSRRscConfig;
517
766
  /** Write endpoint for server-driven providers. Absent means the server serves reads only. */
518
767
  action?: SSRActionConfig;
768
+ /** Connector manifest and credential lookups — see {@link ConnectorLookupsConfig}. They serve the RSC read path
769
+ * and the `/_action` write endpoint alike; without them neither can reach a connector. */
770
+ connectors?: ConnectorLookupsConfig;
519
771
  /** Receives a {@link ServerLogEvent} for every HTTP request this server answers — whatever stage answered it
520
772
  * and whatever the outcome — plus every MCP tool call and resource read inside those requests. Without it the
521
773
  * server reports nothing per request (the MCP events still reach the console when `MCP_DEBUG=1`). */
@@ -630,14 +882,34 @@ export type OAuthGrantTarget = {
630
882
  * for them. The SDK owns the protocol (discovery, registration, PKCE, code exchange); the consumer owns identity
631
883
  * and issues a token its own `adapters.getGrant` will accept back. */
632
884
  export type OAuthAdapters = {
633
- /** Verify the credentials typed into the consent screen. Return undefined to re-show the form with an error —
634
- * never throw for a wrong password. */
635
- authenticate: (credentials: {
636
- username: string;
637
- password: string;
638
- }) => Promise<OAuthUser | undefined>;
885
+ /**
886
+ * Who the browser making this request already is, from whatever session it carries.
887
+ *
888
+ * This server has no sign-in of its own: it reads an existing session and sends anyone it cannot identify to
889
+ * {@link OAuthConfig.signInUrl}, which brings them back. That is what lets a deployment have ONE sign-in screen
890
+ * for every surface — and it is what makes SSO somebody else's problem, because whatever established the
891
+ * session is no longer this flow's business.
892
+ *
893
+ * It replaced a username/password form this server used to render and check itself. Two sign-ins against one
894
+ * account store is two places to add a provider, to rate-limit, and to get a password check subtly wrong.
895
+ *
896
+ * Return undefined for "nobody is signed in". Never throw for that — an unreadable session is the ordinary
897
+ * first visit.
898
+ */
899
+ identify: (req: SSRRequest) => Promise<OAuthUser | undefined>;
639
900
  /** What this user may grant access to. An empty list ends the flow with `access_denied`. */
640
901
  grantTargets: (user: OAuthUser) => Promise<OAuthGrantTarget[]>;
902
+ /**
903
+ * End whatever session {@link OAuthAdapters.identify} was reading, so the person can connect as somebody else.
904
+ *
905
+ * Optional, and its absence is what takes the offer off the grant screen: a deployment that cannot end a session
906
+ * from here should not show a button that pretends to. Clear the cookies on `res` and revoke at the source —
907
+ * forgetting the cookie alone leaves the credential working for anyone who already copied it.
908
+ *
909
+ * Whoever reaches that screen arrived from another application, so this is the only way out of the wrong account
910
+ * that does not mean abandoning the connection and starting over from the host.
911
+ */
912
+ signOut?: (req: SSRRequest, res: SSRResponseHelpers) => void | Promise<void>;
641
913
  /** Mint the bearer the client will send on every MCP request. Return undefined to deny the grant. */
642
914
  issueToken: (user: OAuthUser, target: OAuthGrantTarget) => Promise<{
643
915
  token: string;
@@ -657,7 +929,7 @@ export type OAuthGuestConfig = {
657
929
  /** Who the grant is issued as. Defaults to `{ id: 'guest', label: 'Guest' }`. */
658
930
  user?: OAuthUser;
659
931
  };
660
- /** What the built-in consent screen shows around the form. Ignored when `renderConsent` replaces the page. */
932
+ /** What the grant screen shows around the form. */
661
933
  export type OAuthBranding = {
662
934
  /** Shown as the heading, e.g. 'Plitzi'. Defaults to 'Plitzi'. */
663
935
  productName?: string;
@@ -666,26 +938,46 @@ export type OAuthBranding = {
666
938
  /** Extra CSS appended to the page's own, for a deployment that wants its own look without replacing the page. */
667
939
  css?: string;
668
940
  };
669
- /** Everything the consent screen needs to render itself, for a deployment that replaces the built-in page. Return
670
- * a full HTML document; the SDK serves it as-is and reads the same form fields back. */
941
+ /**
942
+ * Everything the GRANT screen needs to render itself.
943
+ *
944
+ * One screen, and deliberately only one: this server asks what to connect, never who somebody is. Signing in
945
+ * happens on {@link OAuthConfig.signInUrl}, wherever the deployment keeps it.
946
+ */
671
947
  export type OAuthConsentView = {
672
- /** 'credentials' asks for username + password; 'target' asks which space to grant, after a successful login. */
673
- step: 'credentials' | 'target';
674
948
  /** Where the form must POST to (the authorize endpoint). */
675
949
  action: string;
676
950
  /** Hidden fields the form MUST round-trip verbatim, or the flow cannot be resumed. */
677
951
  hidden: Record<string, string>;
678
- /** Offered on the 'target' step only. */
952
+ /** What this account may grant. Empty for a visitor who has not signed in, who is offered `guest` instead. */
679
953
  targets: OAuthGrantTarget[];
680
- /** Offered on the 'credentials' step when the deployment allows a guest connection. The form must submit a
681
- * `guest` field for it (any non-empty value), which is what tells the server to skip authentication. */
954
+ /**
955
+ * Offered when the deployment allows a guest connection. The form submits a `guest` field for it (any non-empty
956
+ * value), which is what tells the server to grant the guest target rather than a chosen one.
957
+ *
958
+ * It is the reason an unidentified visitor is shown this screen at all rather than being sent straight to
959
+ * sign-in: a guest has no session and never will, so a redirect they cannot come back from would take the
960
+ * option away entirely.
961
+ */
682
962
  guest?: {
683
963
  label: string;
684
964
  description?: string;
685
965
  };
686
- /** Who logged in, on the 'target' step. */
966
+ /** Who is signed in. Absent for a visitor taking the guest connection. */
687
967
  user?: OAuthUser;
688
- /** A message to show the user, e.g. after a failed login. */
968
+ /** Where to send somebody who wants to sign in first. Shown when `user` is absent. */
969
+ signInUrl?: string;
970
+ /**
971
+ * Whether to offer "use another account" — shown only when the deployment can act on it, which means it supplied
972
+ * {@link OAuthAdapters.signOut}.
973
+ *
974
+ * A submit BUTTON in the same form rather than a link, and that is not decoration. Ending a session is a state
975
+ * change, so it may not hang off a URL anything can navigate to: a `<img src>` on any page on the internet would
976
+ * then be able to sign a visitor out. Posting it here keeps it a same-origin form submission carrying the request
977
+ * back, exactly like granting does.
978
+ */
979
+ canSwitchUser?: boolean;
980
+ /** A message to show the user. */
689
981
  error?: string;
690
982
  branding: OAuthBranding;
691
983
  };
@@ -715,16 +1007,60 @@ export type OAuthConfig = {
715
1007
  /** Offer a connection that needs no account — see {@link OAuthGuestConfig}. Omit to require sign-in. */
716
1008
  guest?: OAuthGuestConfig;
717
1009
  branding?: OAuthBranding;
718
- /** Replaces the built-in consent screen — return a full HTML document for the given step. */
719
- renderConsent?: (view: OAuthConsentView) => string | Promise<string>;
1010
+ /**
1011
+ * The sign-in screen this server sends unidentified visitors to.
1012
+ *
1013
+ * The whole authorization request rides along as a `redirect` query param, so signing in is an ordinary
1014
+ * navigation that lands back on the `/authorize` it left — this server keeps no session of its own for a flow
1015
+ * that is one page long.
1016
+ *
1017
+ * Required, because {@link OAuthAdapters.identify} is: a server that does not check passwords and has nowhere
1018
+ * to send somebody who has not signed in cannot start the flow at all.
1019
+ */
1020
+ signInUrl: string;
1021
+ /**
1022
+ * Hand the client the credential `issueToken` minted, instead of an opaque handle to it.
1023
+ *
1024
+ * Off by default, which is right whenever the credential is worth more than the connection — a space token
1025
+ * reaches the platform, and a client that never holds one cannot leak one. Turn it ON when the credential IS
1026
+ * what the client should hold: a native app granted the person's own session is holding exactly what a browser
1027
+ * holds, and wrapping it costs a store read on every request and gives a second thing to revoke.
1028
+ */
1029
+ directTokens?: boolean;
720
1030
  };
721
1031
  /** A short-TTL, one-shot store for unsaved draft offline-data behind a preview token. The SDK ships an
722
1032
  * in-memory default (fine for a single replica); a multi-replica deployment injects a shared (e.g. Redis)
723
1033
  * implementation so a preview URL resolves on whichever replica the browser lands on. `take` consumes the
724
1034
  * token so a preview URL is not replayable. */
1035
+ /**
1036
+ * How long a stashed draft lives, and whether looking at it uses it up.
1037
+ *
1038
+ * `reusable: false` is the capture path: one token, one render, gone — a URL that leaks buys nobody a second look.
1039
+ * `reusable: true` is somebody ITERATING: they reload, they open devtools, they navigate to the next page and back,
1040
+ * and a token consumed by the first of those turns the whole loop into "mint another one". Same store, same secret,
1041
+ * same expiry; the only difference is whether the read deletes it.
1042
+ */
1043
+ export type DraftPutOptions = {
1044
+ ttlMs: number;
1045
+ reusable?: boolean;
1046
+ };
1047
+ /** A stashed draft, and whether the read that resolved it left it there. */
1048
+ export type DraftEntry = {
1049
+ data: OfflineDataRaw;
1050
+ reusable: boolean;
1051
+ };
725
1052
  export type DraftStore = {
726
- put: (token: string, data: OfflineDataRaw, ttlMs: number) => void | Promise<void>;
727
- take: (token: string) => (OfflineDataRaw | undefined) | Promise<OfflineDataRaw | undefined>;
1053
+ put: (token: string, data: OfflineDataRaw, options: DraftPutOptions) => void | Promise<void>;
1054
+ /**
1055
+ * Resolves a draft, consuming it unless it was stored as reusable.
1056
+ *
1057
+ * It answers WHICH it was, rather than leaving the caller to infer it from a second read: the render that resolved
1058
+ * a session is the one that has to remember it for the rest of the visit, and "did this token survive" is a fact
1059
+ * the store holds and nobody else does.
1060
+ */
1061
+ take: (token: string) => (DraftEntry | undefined) | Promise<DraftEntry | undefined>;
1062
+ /** Ends a reusable draft before its TTL — what "stop previewing" does. A token that is not there is not an error. */
1063
+ drop: (token: string) => void | Promise<void>;
728
1064
  };
729
1065
  /** Draft-preview config for the MCP visual-preview tools. When enabled, an internal endpoint at `path`
730
1066
  * (guarded by `secret`) applies unsaved edits to a clone, stashes the resulting offline-data under a
@@ -735,7 +1071,15 @@ export type SSRPreviewConfig = {
735
1071
  path?: string;
736
1072
  /** Shared secret required in the `x-preview-secret` header; requests without it are rejected. */
737
1073
  secret?: string;
738
- /** Token time-to-live in milliseconds. Default 60000. */
1074
+ /** One-shot token time-to-live in milliseconds. Default 60000. */
739
1075
  ttlMs?: number;
1076
+ /**
1077
+ * How long a REUSABLE draft session lives, in milliseconds. Default 900000 (fifteen minutes).
1078
+ *
1079
+ * Longer than a one-shot token because it is measured against a person's attention rather than an HTTP round trip,
1080
+ * and short anyway: a draft is unsaved work that only its author should be looking at, and every minute it stays
1081
+ * resolvable is a minute a copied URL keeps working.
1082
+ */
1083
+ sessionTtlMs?: number;
740
1084
  };
741
1085
  export {};
@@ -1,3 +1,4 @@
1
+ import { ActionDocument } from './ActionTypes';
1
2
  import { Environment } from './CommonTypes';
2
3
  import { ConnectorManifestDraft } from './ConnectorTypes';
3
4
  export type SpaceCredentialProvider = 's3' | 'r2' | 'ssr' | 'custom';
@@ -16,6 +17,21 @@ export type SpaceConnector = {
16
17
  createdAt: number;
17
18
  updatedAt: number;
18
19
  };
20
+ /**
21
+ * A server action as the builder sees it.
22
+ *
23
+ * The document is server-side state — it names credentials, connectors and steps — so this shape exists for the
24
+ * editor and the API that maintains it, never for a published page. What reaches a visitor is the action's
25
+ * identifier and its input/output fields, derived server-side.
26
+ */
27
+ export type SpaceAction = {
28
+ id: number;
29
+ identifier: string;
30
+ name: string;
31
+ document: ActionDocument;
32
+ createdAt: number;
33
+ updatedAt: number;
34
+ };
19
35
  export type SpaceCredential = {
20
36
  identifier: string;
21
37
  name: string;