@storypane/sdk 0.0.0-stage → 0.1.0-rc.2
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/LICENSE +21 -0
- package/README.md +647 -2
- package/bin/manage.mjs +241 -0
- package/bin/mcp.mjs +80 -0
- package/bin/migrations.mjs +523 -0
- package/bin/storypane.mjs +117 -0
- package/dist/codegen.d.ts +4 -0
- package/dist/codegen.js +145 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +94 -0
- package/dist/management.d.ts +147 -0
- package/dist/management.js +141 -0
- package/dist/media.d.ts +17 -0
- package/dist/media.js +36 -0
- package/dist/migration-warning.d.ts +2 -0
- package/dist/migration-warning.js +13 -0
- package/dist/preview.d.ts +17 -0
- package/dist/preview.js +209 -0
- package/dist/react.d.ts +17 -0
- package/dist/react.js +66 -0
- package/dist/rich-text.d.ts +6 -0
- package/dist/rich-text.js +90 -0
- package/dist/types.d.ts +112 -0
- package/dist/types.js +33 -0
- package/package.json +76 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Loren Kuich
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,648 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Storypane SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `createStorypaneClient` for publications and `useStorypanePreview` for a
|
|
4
|
+
separate preview route. `useStorypaneStory` adds preview to an existing publication.
|
|
5
|
+
|
|
6
|
+
TypeScript SDK for published content and live, read-only draft preview. Works with
|
|
7
|
+
Node, Workers, browsers, Next.js client components, and Astro islands/scripts.
|
|
8
|
+
The delivery and preview entrypoints have no runtime dependencies; React is an optional
|
|
9
|
+
peer dependency. The MCP CLI uses the official MCP server package and Zod.
|
|
10
|
+
|
|
11
|
+
Start with the [Next.js integration guide](https://github.com/lkuich/storypane/blob/main/apps/example-next/README.md) for
|
|
12
|
+
server-side fetching, typed block components, and a separate live preview route.
|
|
13
|
+
An [Astro example](https://github.com/lkuich/storypane/blob/main/apps/example-astro/README.md) is also included.
|
|
14
|
+
|
|
15
|
+
The current version is **0.1.0-rc.2**. Use the SDK tarball attached to the
|
|
16
|
+
[matching GitHub prerelease](https://github.com/lkuich/storypane/releases), or build
|
|
17
|
+
from this monorepo with `pnpm --filter @storypane/sdk build` and pack/link
|
|
18
|
+
`packages/sdk`. npm publication is separately gated on publisher setup; when
|
|
19
|
+
available, select the explicit RC version or `@storypane/sdk@rc`. See the
|
|
20
|
+
[release guide](https://storypane.dev/docs/releases-and-versioning/). Install the release artifact without waiting for npm publication:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npm install https://github.com/lkuich/storypane/releases/download/v0.1.0-rc.2/storypane-sdk-0.1.0-rc.2.tgz
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Entrypoints:
|
|
27
|
+
|
|
28
|
+
- `@storypane/sdk`: delivery client, types, preview detection, editable attributes.
|
|
29
|
+
- `@storypane/sdk/preview`: framework-independent browser subscription and editor bridge.
|
|
30
|
+
- `@storypane/sdk/react`: `useStorypanePreview`, `useStorypaneStory`, and `editable`.
|
|
31
|
+
- `@storypane/sdk/rich-text`: `RichText`, a safe React renderer that works in Server and Client Components.
|
|
32
|
+
|
|
33
|
+
## Configure the CMS
|
|
34
|
+
|
|
35
|
+
For a team, start with [production for editors and a linked dev space per developer](https://github.com/lkuich/storypane/blob/main/docs/development-workflow.md).
|
|
36
|
+
Use production credentials and production-generated types for the live website,
|
|
37
|
+
and a separate dev configuration for schema and frontend testing.
|
|
38
|
+
|
|
39
|
+
In the selected space’s **Settings → API & preview**, create a **Published content** token and
|
|
40
|
+
set the application preview URL, e.g. `https://site.example/{locale}/{slug}`.
|
|
41
|
+
The root locale contributes an empty path segment; the editor removes doubled
|
|
42
|
+
slashes. `{slug}` is the selected locale's URL slug. Query placeholders also work:
|
|
43
|
+
`https://site.example/preview?slug={slug}&locale={locale}`.
|
|
44
|
+
|
|
45
|
+
The default preview parameter is `storypane_preview=1`. You can change its name in
|
|
46
|
+
settings; use the same name in your SDK options. Your app must permit the CMS
|
|
47
|
+
origin in its CSP `frame-ancestors` policy and must not send a conflicting
|
|
48
|
+
`X-Frame-Options` header. An HTTPS CMS requires an HTTPS app; localhost HTTP is
|
|
49
|
+
supported for development.
|
|
50
|
+
|
|
51
|
+
## Published content
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { createStorypaneClient, type StorypaneBlock } from '@storypane/sdk';
|
|
55
|
+
import { storypaneMigrationVersion } from './types/storypane';
|
|
56
|
+
|
|
57
|
+
const storypane = createStorypaneClient({
|
|
58
|
+
url: process.env.STORYPANE_URL!,
|
|
59
|
+
spaceId: process.env.STORYPANE_SPACE_ID!,
|
|
60
|
+
token: process.env.STORYPANE_DELIVERY_TOKEN!,
|
|
61
|
+
migrationVersion: storypaneMigrationVersion,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const page = await storypane.getStory({ slug: 'home' });
|
|
65
|
+
const byId = await storypane.getStory({ id: page.id });
|
|
66
|
+
const { stories, nextOffset } = await storypane.listStories();
|
|
67
|
+
const allStories = await storypane.listAllStories();
|
|
68
|
+
const locales = await storypane.listLocales(); // [{ id, name: 'Default', slug: '', isDefault: true }, ...]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Locale is optional. These calls use the space's configured default locale;
|
|
72
|
+
`listStories()` and `listAllStories()` need no options object. A single-story read
|
|
73
|
+
still needs its ID or slug. Pass `{ locale: 'fr' }` to either list method, or add
|
|
74
|
+
`locale: 'fr'` to a story reference, when you want a specific locale.
|
|
75
|
+
You can also set pagination without a locale: `listStories({ limit: 100 })`.
|
|
76
|
+
An explicit `locale: ''` selects the root locale, even if another locale is the
|
|
77
|
+
configured default. Changing the default does not change URL prefixes.
|
|
78
|
+
|
|
79
|
+
These reads use the CMS delivery API, which queries publication snapshots in
|
|
80
|
+
Postgres. Drafts and unpublished locales never appear. A missing publication in
|
|
81
|
+
the selected locale does not fall back to another locale; a single-story read
|
|
82
|
+
throws `StorypaneError` with `status === 404`.
|
|
83
|
+
Each content block includes `_uid` and `component`; nested block fields contain
|
|
84
|
+
arrays. Schema-specific generics are supported: `storypane.getStory<MyPage>(...)`.
|
|
85
|
+
|
|
86
|
+
`listLocales()` reads the names and prefixes configured in **Space settings →
|
|
87
|
+
Locales** using a published or preview token. It exposes no draft content. Use
|
|
88
|
+
these slugs with `getStory` / `listStories` to build routes; locales with no
|
|
89
|
+
published stories return an empty list. Slug changes preserve content IDs but
|
|
90
|
+
require updating old links and rebuilding static routes.
|
|
91
|
+
|
|
92
|
+
API responses are `no-store` so token revocation and new publications take effect
|
|
93
|
+
on the next request. Your app can deliberately cache/build the published response.
|
|
94
|
+
SSG pages still need a rebuild or framework revalidation after publishing. Preview
|
|
95
|
+
does not modify their static HTML or production cache.
|
|
96
|
+
|
|
97
|
+
## Next.js App Router
|
|
98
|
+
|
|
99
|
+
Keep the client in a module marked `import 'server-only'`. Call `getStory` from
|
|
100
|
+
an async Server Component, then pass the content to your own React components.
|
|
101
|
+
The client accepts a schema generic once; individual methods can still override
|
|
102
|
+
it with `getStory<MyPage>(...)`.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// src/lib/storypane.ts
|
|
106
|
+
import 'server-only';
|
|
107
|
+
import { createStorypaneClient } from '@storypane/sdk';
|
|
108
|
+
import { storypaneMigrationVersion, type StorypaneContentType } from '@/types/storypane';
|
|
109
|
+
|
|
110
|
+
export const storypane = createStorypaneClient<StorypaneContentType>({
|
|
111
|
+
url: process.env.STORYPANE_URL!,
|
|
112
|
+
spaceId: process.env.STORYPANE_SPACE_ID!,
|
|
113
|
+
token: process.env.STORYPANE_DELIVERY_TOKEN!,
|
|
114
|
+
migrationVersion: storypaneMigrationVersion,
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
// src/app/about/page.tsx
|
|
120
|
+
import { storypane } from '@/lib/storypane';
|
|
121
|
+
|
|
122
|
+
export default async function Page() {
|
|
123
|
+
const story = await storypane.getStory({ slug: 'about' });
|
|
124
|
+
return <h1>{story.name}</h1>;
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
For static routes, `listAllStories()` returns every published story in the
|
|
129
|
+
configured default locale, following pagination. To build routes for every locale,
|
|
130
|
+
call `listLocales()` and fetch the others with `listAllStories({ locale: slug })`.
|
|
131
|
+
Each locale includes `isDefault`; the default need not have an empty URL prefix.
|
|
132
|
+
`getStory`, `listStories`, and `listAllStories` accept fetch options as their second
|
|
133
|
+
argument. Choose static rendering or request-time fetching in your Next.js routes;
|
|
134
|
+
the SDK does not impose a cache policy on story reads.
|
|
135
|
+
|
|
136
|
+
### A separate preview route
|
|
137
|
+
|
|
138
|
+
Use `useStorypanePreview` in a Client Component when no initial publication is
|
|
139
|
+
available. It returns `{ story, status, error }`; `story` stays undefined until
|
|
140
|
+
the editor authorizes the connection. Pass an undefined story reference until
|
|
141
|
+
your route has a slug. Without a parent editor or authenticated `getSession`
|
|
142
|
+
callback, the hook stays disabled.
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
'use client';
|
|
146
|
+
import { useStorypanePreview } from '@storypane/sdk/react';
|
|
147
|
+
import { storypaneMigrationVersion, type StorypaneContentType } from '@/types/storypane';
|
|
148
|
+
|
|
149
|
+
export function Preview({
|
|
150
|
+
cmsUrl,
|
|
151
|
+
spaceId,
|
|
152
|
+
slug,
|
|
153
|
+
locale = '',
|
|
154
|
+
}: {
|
|
155
|
+
cmsUrl: string;
|
|
156
|
+
spaceId: string;
|
|
157
|
+
slug: string;
|
|
158
|
+
locale?: string;
|
|
159
|
+
}) {
|
|
160
|
+
const { story, error } = useStorypanePreview<StorypaneContentType>({
|
|
161
|
+
cmsUrl,
|
|
162
|
+
spaceId,
|
|
163
|
+
migrationVersion: storypaneMigrationVersion,
|
|
164
|
+
story: { slug, locale },
|
|
165
|
+
});
|
|
166
|
+
if (error) return <p role="alert">{error.message}</p>;
|
|
167
|
+
if (!story) return <p>Open this page from Storypane's preview.</p>;
|
|
168
|
+
return <h1>{story.name}</h1>; // Render your block components here.
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The [complete Next.js example](https://github.com/lkuich/storypane/blob/main/apps/example-next/README.md) includes routing,
|
|
173
|
+
`Suspense` around `useSearchParams`, block rendering, and referenced footer drafts.
|
|
174
|
+
It keeps published pages on the server and reuses the renderer inside `/preview`.
|
|
175
|
+
Client preview components must not import server-only fetch modules.
|
|
176
|
+
|
|
177
|
+
If you prefer preview on an existing published route, `useStorypaneStory(initial,
|
|
178
|
+
options)` remains available. It starts with the publication and activates drafts
|
|
179
|
+
only when its `mode` matches. For Next.js Draft Mode, pass the server's `isEnabled`
|
|
180
|
+
value as `mode.draft`; never read the HttpOnly draft cookie from browser JavaScript.
|
|
181
|
+
An arbitrary Server Component cannot be re-rendered by changing browser data;
|
|
182
|
+
the live region must have a client-compatible renderer.
|
|
183
|
+
|
|
184
|
+
`editable(block)` supplies selection attributes. Put it on each block's wrapper;
|
|
185
|
+
focusable wrappers also support Enter/Space selection in the editor. The SDK
|
|
186
|
+
cleans up subscriptions, listeners, and highlighting on unmount.
|
|
187
|
+
|
|
188
|
+
### Rich text
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
import { RichText } from '@storypane/sdk/rich-text';
|
|
192
|
+
|
|
193
|
+
<RichText value={block.text} />;
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The renderer supports the editor's nodes and marks, escapes text, and rejects
|
|
197
|
+
unsafe link protocols. It also accepts legacy plain strings and empty draft
|
|
198
|
+
values. It has no client-only hooks, so it works in both Server Components and
|
|
199
|
+
live previews. It does not inject CMS strings as HTML.
|
|
200
|
+
|
|
201
|
+
## Astro
|
|
202
|
+
|
|
203
|
+
Render publications at build time with `@astrojs/react`, without a hydration
|
|
204
|
+
directive. The example's app-owned `publishedPages` helper fetches every configured
|
|
205
|
+
locale through `listAllStories`, validates routes, and supplies the selected story
|
|
206
|
+
and its references:
|
|
207
|
+
|
|
208
|
+
```astro
|
|
209
|
+
---
|
|
210
|
+
import { Content } from '../components/content';
|
|
211
|
+
import { publishedPages } from '../lib/cms';
|
|
212
|
+
export const getStaticPaths = publishedPages;
|
|
213
|
+
const { story, references } = Astro.props;
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
<Content block={story.content} references={references} />
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Keep the delivery token in server-only environment variables. Public pages have
|
|
220
|
+
no draft subscription. Put the interactive preview on a separate `/preview` route:
|
|
221
|
+
|
|
222
|
+
```astro
|
|
223
|
+
---
|
|
224
|
+
import PreviewShell from '../components/PreviewShell';
|
|
225
|
+
import { cmsUrl, spaceId } from '../lib/cms';
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
<PreviewShell client:load cmsUrl={cmsUrl} spaceId={spaceId} />
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The example's [`PreviewShell`](https://github.com/lkuich/storypane/blob/main/apps/example-astro/src/components/PreviewShell.tsx)
|
|
232
|
+
uses `useStorypanePreview` with the URL's `slug` and `locale`, clears stale content
|
|
233
|
+
on navigation, and renders the references supplied by the authenticated editor.
|
|
234
|
+
It also previews stories that have never been published. Configure the CMS preview
|
|
235
|
+
URL as `/preview?slug={slug}&locale={locale}`; no public environment flag is needed.
|
|
236
|
+
|
|
237
|
+
Without React, use a bundled Astro `<script>` and render the subscribed content:
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import { connectPreview } from '@storypane/sdk/preview';
|
|
241
|
+
import { storypaneMigrationVersion } from './types/storypane';
|
|
242
|
+
const stop = connectPreview({
|
|
243
|
+
cmsUrl: 'https://cms.example',
|
|
244
|
+
spaceId: 'your-space-id',
|
|
245
|
+
migrationVersion: storypaneMigrationVersion,
|
|
246
|
+
story: { slug: 'home' },
|
|
247
|
+
mode: { query: { name: 'storypane_preview', value: '1' } },
|
|
248
|
+
onChange: (story) => renderWithYourClientComponents(story.content),
|
|
249
|
+
});
|
|
250
|
+
// Call stop() when the page/island unmounts, including Astro client navigation.
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Pure `.astro` components are server/build-time templates. A browser script must
|
|
254
|
+
update their DOM, or a hydrated island must render the live region. The SDK does
|
|
255
|
+
not inject HTML or guess how your custom components should render.
|
|
256
|
+
|
|
257
|
+
## Standalone preview and authorization
|
|
258
|
+
|
|
259
|
+
Any of these configured signals activates preview: `mode.draft === true`, a true
|
|
260
|
+
env flag (`true`, `'true'`, or `'1'`), a matching cookie, or a matching query param.
|
|
261
|
+
Signals are ORed. Use `query:false` to disable the default URL trigger. A rule
|
|
262
|
+
without `value` checks for presence. HttpOnly cookies require a server-derived
|
|
263
|
+
`draft` boolean. Build-time browser env vars require a rebuild when changed.
|
|
264
|
+
|
|
265
|
+
Inside the CMS iframe, the SDK requests a session from its parent with
|
|
266
|
+
`postMessage`; both sides check the exact origin and source window. The editor
|
|
267
|
+
uses its authenticated session to grant five minutes of read-only access to one
|
|
268
|
+
story locale. The SDK renews that grant while the editor remains authorized.
|
|
269
|
+
Third-party cookies are not needed in the iframe, and credentials are not added
|
|
270
|
+
to the iframe URL. The browser receives the story's authorized preview over a
|
|
271
|
+
WebSocket.
|
|
272
|
+
|
|
273
|
+
Outside the iframe, provide `getSession`. Your app's authenticated server endpoint
|
|
274
|
+
must verify the visitor may preview before using a **Preview · server only** token:
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
// YOUR APP SERVER: only after checking your editor session/authorization.
|
|
278
|
+
const previewClient = createStorypaneClient({
|
|
279
|
+
url: process.env.STORYPANE_URL!,
|
|
280
|
+
spaceId: process.env.STORYPANE_SPACE_ID!,
|
|
281
|
+
token: process.env.STORYPANE_PREVIEW_TOKEN!,
|
|
282
|
+
});
|
|
283
|
+
const session = await previewClient.createPreviewSession({ slug: 'home' });
|
|
284
|
+
// Return session as JSON with Cache-Control: no-store.
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
`createPreviewSession` also uses the configured default when you omit `locale`.
|
|
288
|
+
Include it in the story reference to request a specific locale.
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
// In connectPreview(...) or useStorypaneStory(...) options:
|
|
292
|
+
getSession: async () => {
|
|
293
|
+
const response = await fetch('/api/my-authenticated-preview-session', { method: 'POST' });
|
|
294
|
+
if (!response.ok) throw new Error('Sign in to preview');
|
|
295
|
+
return response.json();
|
|
296
|
+
};
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Use a stable callback for the React hook (module function or `useCallback`). Your
|
|
300
|
+
endpoint must validate the requested story and CSRF protections appropriate to
|
|
301
|
+
your authentication. A query parameter or an unsigned cookie alone must never
|
|
302
|
+
authorize that endpoint. Never put the long-lived preview token in browser code,
|
|
303
|
+
public env vars, static JSON, or URLs. Only the short-lived session reaches the
|
|
304
|
+
browser. The application origin must match the configured preview URL.
|
|
305
|
+
|
|
306
|
+
Sessions are checked on connect, draft broadcasts, and heartbeats. Revoking a key,
|
|
307
|
+
removing a member, or expiring their login stops subsequent draft delivery.
|
|
308
|
+
Preview sockets accept only sync/heartbeat requests and never hold editing leases.
|
|
309
|
+
|
|
310
|
+
Preview stories include a `references` array for direct story-backed option,
|
|
311
|
+
multi-option, and story-link fields. These contain current drafts from the same
|
|
312
|
+
space, preferring the preview locale with Default as the fallback. References stay
|
|
313
|
+
as IDs in `content`; renderers can look them up in `references`. Referenced-story
|
|
314
|
+
edits refresh on the 15-second heartbeat even if the parent revision is unchanged.
|
|
315
|
+
References are resolved one level deep and are not added to public delivery responses.
|
|
316
|
+
After interruptions the SDK reconnects and reads the latest durable draft; older
|
|
317
|
+
revisions are ignored. Errors are exposed through `onStatus` / the React hook.
|
|
318
|
+
|
|
319
|
+
## Current limits
|
|
320
|
+
|
|
321
|
+
- Live changes arrive after autosave commits (not on each uncommitted keystroke).
|
|
322
|
+
- Media and link fields retain their structured references. Use `publicAssetUrl`
|
|
323
|
+
for opted-in public media or `client.getAsset` for server-side static export.
|
|
324
|
+
- Component rendering, authentication for standalone preview, application CSP,
|
|
325
|
+
published-site rebuild/revalidation, and unpublished route handling belong to
|
|
326
|
+
the consuming app. The Astro example is built and tested through its real React
|
|
327
|
+
island; the Next.js example remains an integration recipe.
|
|
328
|
+
|
|
329
|
+
## Management API
|
|
330
|
+
|
|
331
|
+
Use `@storypane/sdk/management` in server code to read and edit drafts, publish
|
|
332
|
+
checkpoints, and manage assets and configuration. Create a scoped token in
|
|
333
|
+
**Space settings → API & preview → Management API**. Tokens and writes obey the
|
|
334
|
+
issuer's current permissions, content restrictions, and active editor locks.
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
import { createManagementClient } from '@storypane/sdk/management';
|
|
338
|
+
|
|
339
|
+
const management = createManagementClient({
|
|
340
|
+
url: process.env.STORYPANE_URL!,
|
|
341
|
+
spaceId: process.env.STORYPANE_SPACE_ID!,
|
|
342
|
+
token: process.env.STORYPANE_MANAGEMENT_TOKEN!,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
const stories = await management.stories.list({ limit: 20 });
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
The CLI uses the same environment variables:
|
|
349
|
+
|
|
350
|
+
```sh
|
|
351
|
+
storypane stories list --json
|
|
352
|
+
storypane content get STORY_ID LOCALE_ID --json
|
|
353
|
+
storypane assets upload ./cover.jpg --mime image/jpeg
|
|
354
|
+
storypane api GET /delivery-settings
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
See the [management API guide](https://github.com/lkuich/storypane/blob/main/docs/management-api.md) for safe edits,
|
|
358
|
+
publishing, token permissions, pagination, HTTP operations, and CLI JSON input.
|
|
359
|
+
The delivery SDK remains the entrypoint for public sites.
|
|
360
|
+
|
|
361
|
+
### Content releases
|
|
362
|
+
|
|
363
|
+
Use the management client's existing `request()` method to manage releases:
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
const releases = await management.request('/releases');
|
|
367
|
+
const release = await management.request('/releases', {
|
|
368
|
+
method: 'POST',
|
|
369
|
+
body: {
|
|
370
|
+
requestId: crypto.randomUUID(),
|
|
371
|
+
name: 'Summer launch',
|
|
372
|
+
localTime: '2027-06-01T09:00',
|
|
373
|
+
timezone: 'America/Vancouver',
|
|
374
|
+
dueAt: '2027-06-01T16:00:00.000Z',
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Choose a future date; retain the same request ID and payload on retries. Add
|
|
380
|
+
approved page revisions through the scheduling endpoint, using the release ID and
|
|
381
|
+
reviewed revision. See [API contracts](https://github.com/lkuich/storypane/blob/main/docs/management-api.md#content-releases)
|
|
382
|
+
and [the release guide](https://github.com/lkuich/storypane/blob/main/docs/releases.md). Releases publish approved nested
|
|
383
|
+
content together; later drafts stay unpublished. Delivery and preview calls are
|
|
384
|
+
unchanged. No release-specific CLI command is required or provided.
|
|
385
|
+
|
|
386
|
+
## Schema migrations and generated types
|
|
387
|
+
|
|
388
|
+
A space has one shared active migration. CMS schema saves and CLI pushes update
|
|
389
|
+
that migration; page editing and delivery keep using deployed schemas until an
|
|
390
|
+
admin deploys it. New block types must be deployed before they can be inserted.
|
|
391
|
+
|
|
392
|
+
In your dev space, open **Blocks → Schema migrations → CLI access**. An admin can issue a
|
|
393
|
+
management token with read, stage, or deploy capability. Set `STORYPANE_URL`,
|
|
394
|
+
`STORYPANE_SPACE_ID`, and `STORYPANE_MANAGEMENT_TOKEN` in your terminal or CI secret
|
|
395
|
+
store. Management tokens are never browser credentials. Revocation and the
|
|
396
|
+
issuer's current membership/permissions are checked on every operation.
|
|
397
|
+
|
|
398
|
+
```sh
|
|
399
|
+
storypane migrations pull
|
|
400
|
+
storypane migrations create "Rename hero title"
|
|
401
|
+
# Edit storypane/migrations/active.json. Keep existing field IDs when renaming.
|
|
402
|
+
storypane migrations push
|
|
403
|
+
# Or review and deploy from an admin's terminal:
|
|
404
|
+
storypane migrations push --deploy
|
|
405
|
+
storypane migrations pull
|
|
406
|
+
storypane generate-types --out src/types/storypane.ts
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
If the CMS already has an active migration, `pull` downloads it: edit that file
|
|
410
|
+
instead of running `create`. Migration files target exactly one space. Commit the
|
|
411
|
+
`storypane/migrations` directory with application code. Numbered JSON files are
|
|
412
|
+
immutable deployed history; `active.json` is the editable target schema. New
|
|
413
|
+
fields and definitions may omit their IDs; push assigns and persists IDs before
|
|
414
|
+
sending them. Existing IDs preserve renames, including nested blocks. Field order
|
|
415
|
+
in the array controls editor order. Definition API names cannot be renamed. Version 2 migration files also carry
|
|
416
|
+
`parentDefinitionId` beside each definition’s `schema`; preserve these IDs to keep
|
|
417
|
+
the library hierarchy. Version 1 files remain supported and omitted parent metadata
|
|
418
|
+
preserves the current parent. Removing a definition is allowed only when the
|
|
419
|
+
existing content, folder, and schema-reference checks permit it.
|
|
420
|
+
|
|
421
|
+
Local edits and deployed history are never silently overwritten. A stale push or
|
|
422
|
+
divergent pull fails with a conflict. Use `storypane migrations pull --out
|
|
423
|
+
/tmp/storypane-remote` to inspect a separate remote copy, reconcile your changes
|
|
424
|
+
into its active file while preserving its remote revision/source hash, then copy
|
|
425
|
+
that reconciled file into your project. Pull the deployed history too if the base
|
|
426
|
+
version changed. There is no force-push switch.
|
|
427
|
+
|
|
428
|
+
Deployment prints the schema diff and affected draft/publication counts. Deleted
|
|
429
|
+
fields lose their active values; removing a nested-block field removes its
|
|
430
|
+
subtree. Type changes reset values to a valid target default or unset them.
|
|
431
|
+
Renames and plain text format changes preserve values. Defaults do not overwrite
|
|
432
|
+
unchanged fields. Invalid resulting publications, constraints, references, or
|
|
433
|
+
nesting block deployment. Drafts may still omit fields required for publication.
|
|
434
|
+
Interactive deployment requires typing `DEPLOY <version>` when the review
|
|
435
|
+
includes data loss. In the CMS, **Preview destructive deployment** can additionally
|
|
436
|
+
remove disallowed/excess nested blocks and reset invalid field values. It still
|
|
437
|
+
validates the resulting content and requires explicit confirmation. The CLI uses
|
|
438
|
+
standard deployment and does not automatically select this destructive mode.
|
|
439
|
+
|
|
440
|
+
Drafts and live publications migrate separately in one transaction, so deploying
|
|
441
|
+
schemas never publishes unfinished edits. Old values remain in history. Restores
|
|
442
|
+
apply intervening migrations; restore an older checkpoint before publishing it.
|
|
443
|
+
|
|
444
|
+
### Linked development and production spaces
|
|
445
|
+
|
|
446
|
+
For new teams, create **Production** first for content editors, keep its schema
|
|
447
|
+
read-only, and create a dedicated **Development** space per developer using
|
|
448
|
+
production as the base. Follow the [UI setup and promotion walkthrough](https://github.com/lkuich/storypane/blob/main/docs/development-workflow.md).
|
|
449
|
+
An installation admin can clone a space or choose a base when creating one.
|
|
450
|
+
One production space can have multiple linked dev spaces. Starting in dev is supported: creating
|
|
451
|
+
production from an unlinked dev space establishes the link. New production spaces
|
|
452
|
+
created with an explicit environment start with **Schema read-only** enabled;
|
|
453
|
+
existing spaces retain their previous editing behavior.
|
|
454
|
+
|
|
455
|
+
Cloning takes deployed schemas, block-library nesting, nested-block restrictions,
|
|
456
|
+
locales, and required datasource option lists. It excludes bulk content and media,
|
|
457
|
+
but reviews and copies the stories and original media required by schema defaults,
|
|
458
|
+
including their transitive dependencies (up to 100 stories). Required stories use
|
|
459
|
+
published content where available, otherwise their drafts; copies are always
|
|
460
|
+
unpublished. Memberships, tokens, integrations, and external option request headers
|
|
461
|
+
are not copied. Configure external credentials separately.
|
|
462
|
+
|
|
463
|
+
In **Settings → General**, admins can make schemas read-only. This blocks local
|
|
464
|
+
schema edits, block moves, CLI pushes, and direct deployment. Schema and migration
|
|
465
|
+
history stay visible. Reviewed promotion from a linked dev space is still allowed.
|
|
466
|
+
|
|
467
|
+
Keep `STORYPANE_SPACE_ID` and `STORYPANE_MANAGEMENT_TOKEN` pointed at dev. Set
|
|
468
|
+
`STORYPANE_PRODUCTION_MANAGEMENT_TOKEN` to a separate production token. Sync and
|
|
469
|
+
promotion require a deploy-capable dev token; production needs read access for
|
|
470
|
+
sync and deploy access for promotion. Deploy-capable management tokens need `read`
|
|
471
|
+
and `admin`; add `schemas:write` to the dev token for local migration authoring. Both issuers’ current
|
|
472
|
+
membership and permissions are checked. Production is resolved from the link,
|
|
473
|
+
within the same installation.
|
|
474
|
+
|
|
475
|
+
```sh
|
|
476
|
+
storypane migrations pull
|
|
477
|
+
storypane migrations create "Add hero caption"
|
|
478
|
+
# Edit storypane/migrations/active.json.
|
|
479
|
+
storypane migrations push --deploy
|
|
480
|
+
# Test the deployed schema in dev, then review actual production impacts.
|
|
481
|
+
storypane migrations status
|
|
482
|
+
storypane migrations promote
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Promotion preserves the order of deployed dev migrations and applies the reviewed
|
|
486
|
+
batch in one production transaction. It can copy missing required dependencies as
|
|
487
|
+
unpublished drafts. Dependency copying does not overwrite existing production
|
|
488
|
+
stories or media; schema migrations still transform affected content.
|
|
489
|
+
A required datasource with different option values blocks the transfer until its
|
|
490
|
+
configuration is reconciled explicitly. Active schema drafts are not promoted.
|
|
491
|
+
|
|
492
|
+
Other dev spaces show **Behind production**. Use **Sync from production** in the
|
|
493
|
+
Blocks screen or `storypane migrations sync`. Sync preserves deployed history,
|
|
494
|
+
replays incoming production transformations, and reconciles local changes by block
|
|
495
|
+
and field ID. The UI offers a choice for each conflict. If independent changes
|
|
496
|
+
create invalid nesting or references, choose the complete dev or production schema
|
|
497
|
+
for that step. The CLI prints conflict
|
|
498
|
+
paths; put the choices in a JSON object mapping each path to `"dev"` or `"prod"`,
|
|
499
|
+
then run `storypane migrations sync --input resolutions.json`. Review data loss
|
|
500
|
+
before applying. Unresolved conflicts and stale reviews block application.
|
|
501
|
+
|
|
502
|
+
`--yes` accepts the current review for CI, including reported data loss. The CLI
|
|
503
|
+
keeps an interrupted operation in `storypane/migrations/.transfer.json`; rerun the
|
|
504
|
+
same command to resume it safely. Do not commit this temporary file. A stale
|
|
505
|
+
review is discarded so the next invocation can obtain a new review. Pull dev
|
|
506
|
+
migrations after sync; divergent local active files must still be reconciled using
|
|
507
|
+
a separate `--out` directory.
|
|
508
|
+
|
|
509
|
+
Production and dev migration numbers can differ. Set `PRODUCTION_SPACE_ID` to
|
|
510
|
+
production’s UUID from **Settings → General**. Generate production application
|
|
511
|
+
types from production, keeping its files separate from dev:
|
|
512
|
+
|
|
513
|
+
```sh
|
|
514
|
+
STORYPANE_SPACE_ID="$PRODUCTION_SPACE_ID" \
|
|
515
|
+
STORYPANE_MANAGEMENT_TOKEN="$STORYPANE_PRODUCTION_MANAGEMENT_TOKEN" \
|
|
516
|
+
storypane migrations pull --out storypane/production-migrations
|
|
517
|
+
# Substitute the latest numbered production JSON file returned by this pull.
|
|
518
|
+
storypane generate-types --input storypane/production-migrations/3.json \
|
|
519
|
+
--out src/types/storypane.ts
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### CI/CD
|
|
523
|
+
|
|
524
|
+
Use separate stages for dev validation and production promotion. The authoring
|
|
525
|
+
stage targets dev with its deploy-capable token:
|
|
526
|
+
|
|
527
|
+
```sh
|
|
528
|
+
storypane migrations push --deploy --yes
|
|
529
|
+
storypane migrations pull
|
|
530
|
+
storypane generate-types --out src/types/storypane.ts
|
|
531
|
+
# Run your application's build/tests against dev.
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
After review, a release stage keeps `STORYPANE_SPACE_ID` pointed at dev and adds
|
|
535
|
+
`STORYPANE_PRODUCTION_MANAGEMENT_TOKEN` from the CI secret store:
|
|
536
|
+
|
|
537
|
+
```sh
|
|
538
|
+
storypane migrations status
|
|
539
|
+
storypane migrations promote --yes
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
If production has advanced, sync and resolve conflicts before this stage. Generate
|
|
543
|
+
production types using the separate pull shown above, then build/deploy the live
|
|
544
|
+
frontend with production's delivery configuration. Direct `push --deploy` to a
|
|
545
|
+
read-only production space is rejected.
|
|
546
|
+
|
|
547
|
+
`--yes` explicitly accepts the reviewed data loss for noninteractive deployment;
|
|
548
|
+
validation and conflict checks still
|
|
549
|
+
apply. A stale impact review or failed deployment exits nonzero. Retrying the
|
|
550
|
+
same migration is safe, including after a lost response. The CLI does not commit
|
|
551
|
+
files or deploy your application. A successful `push --deploy` turns the local
|
|
552
|
+
active file into a numbered deployed snapshot. After promotion, pull again to
|
|
553
|
+
refresh dev's files before further edits.
|
|
554
|
+
|
|
555
|
+
### Runtime version warnings
|
|
556
|
+
|
|
557
|
+
Codegen exports `storypaneMigrationVersion` with the block types. Pass that
|
|
558
|
+
constant as `migrationVersion` to `createStorypaneClient`, `connectPreview`, and
|
|
559
|
+
`useStorypaneStory`. Published responses and preview updates include their actual
|
|
560
|
+
version. A mismatch logs one warning per observed version per client/connection;
|
|
561
|
+
**content is still returned and preview remains connected**. Unversioned clients
|
|
562
|
+
also keep working. Existing caching options remain available; cached/static
|
|
563
|
+
content has no separate freshness check.
|
|
564
|
+
|
|
565
|
+
Warnings do not adapt newer content to older application code. Removed/reset
|
|
566
|
+
fields can require renderer changes even though requests succeed. Types describe
|
|
567
|
+
the locally pulled deployment; draft values can be incomplete. Rebuild static
|
|
568
|
+
sites after publishing or deploying schemas.
|
|
569
|
+
|
|
570
|
+
### Existing schema-pull commands
|
|
571
|
+
|
|
572
|
+
The original commands still work with `STORYPANE_PREVIEW_TOKEN`:
|
|
573
|
+
|
|
574
|
+
```sh
|
|
575
|
+
storypane pull-components --out storypane/components.json
|
|
576
|
+
storypane generate-types --input storypane/components.json --out src/types/storypane.ts
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
The schema endpoint returns only the latest deployed metadata, without content,
|
|
580
|
+
defaults, or source URLs. A published-only token cannot read schemas. Codegen is
|
|
581
|
+
offline and writes atomically; invalid input preserves the previous output.
|
|
582
|
+
Both workflows require the initial schema migration to be deployed first.
|
|
583
|
+
|
|
584
|
+
## Media
|
|
585
|
+
|
|
586
|
+
`client.getAsset(assetId)` returns a `Response` with the original asset bytes for
|
|
587
|
+
server-side rendering or static export. It requires a valid delivery token and
|
|
588
|
+
only serves assets referenced by a current publication in that token's space;
|
|
589
|
+
draft-only and historical references do not grant access. Keep the token on the
|
|
590
|
+
server. Static copies must be rebuilt or removed when their publications change.
|
|
591
|
+
|
|
592
|
+
### Public media URLs
|
|
593
|
+
|
|
594
|
+
An administrator can enable **API & preview → Public media delivery** for a space.
|
|
595
|
+
It is disabled by default. Only assets referenced by an active current publication
|
|
596
|
+
become public; draft-only and historical references do not qualify. Archiving an
|
|
597
|
+
asset preserves its live references and original bytes.
|
|
598
|
+
|
|
599
|
+
`publicAssetUrl` is browser-safe and does not construct a client, make a request,
|
|
600
|
+
or accept a delivery token. For example, in a React renderer:
|
|
601
|
+
|
|
602
|
+
```tsx
|
|
603
|
+
import { publicAssetUrl, type PublicImageOptions } from '@storypane/sdk';
|
|
604
|
+
|
|
605
|
+
const source = { url: 'https://cms.example.com', spaceId, assetId: hero.image.assetId };
|
|
606
|
+
const image = (width: PublicImageOptions['width']) =>
|
|
607
|
+
publicAssetUrl(source, { width, aspect: '16:9' });
|
|
608
|
+
|
|
609
|
+
<img
|
|
610
|
+
src={image(640)}
|
|
611
|
+
srcSet={`${image(320)} 320w, ${image(640)} 640w, ${image(1280)} 1280w`}
|
|
612
|
+
sizes="(max-width: 640px) 100vw, 50vw"
|
|
613
|
+
width={1280}
|
|
614
|
+
height={720}
|
|
615
|
+
alt={hero.image.alt ?? ''}
|
|
616
|
+
/>;
|
|
617
|
+
|
|
618
|
+
const originalDownload = publicAssetUrl(source);
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
Widths are limited to 160, 320, 640, 1280, 1920 and 2560. Optional aspect ratios are
|
|
622
|
+
`original`, `1:1`, `4:3`, `3:4`, `16:9` and `9:16`. Explicit ratios support `cover`
|
|
623
|
+
(default) or `contain`; original aspect uses scale-down. Output formats are `webp`
|
|
624
|
+
(default), `jpeg` and `png`. Lossy output accepts quality 60, 80 (default) or 90;
|
|
625
|
+
PNG rejects a quality option. The helper normalizes defaults and rejects unsupported
|
|
626
|
+
options. Originals support HEAD, ETags, and a single byte range. Variants support
|
|
627
|
+
HEAD and ETags; they do not expose partial image ranges.
|
|
628
|
+
|
|
629
|
+
Cover crops use the asset's saved focal point, defaulting to center. Saving focal
|
|
630
|
+
metadata changes every public crop of that asset immediately; it does not wait for
|
|
631
|
+
story publication. The `asset.updated` webhook includes the metadata revision so
|
|
632
|
+
static exporters can rebuild. Transformed animations use a still frame; originals
|
|
633
|
+
preserve animation. Images larger than 20 MiB, unsupported inputs, or unavailable
|
|
634
|
+
image processing return an error rather than substituting the original.
|
|
635
|
+
|
|
636
|
+
Public responses use `Cache-Control: no-store`. The CMS checks live eligibility
|
|
637
|
+
before serving each request. The Node runtime computes image variants on demand.
|
|
638
|
+
Disabling the setting or removing the last live reference returns 404 on the next
|
|
639
|
+
request, including cached and conditional requests.
|
|
640
|
+
Previously downloaded files and static copies cannot be recalled. Keep the existing
|
|
641
|
+
authenticated `getAsset` integration for static export when public media is disabled.
|
|
642
|
+
|
|
643
|
+
## MCP
|
|
644
|
+
|
|
645
|
+
Run `storypane mcp` (Node 20+) with `STORYPANE_URL`, `STORYPANE_SPACE_ID`, and a
|
|
646
|
+
private `STORYPANE_MANAGEMENT_TOKEN`. The stdio adapter exposes read and write
|
|
647
|
+
tools over the management API; the same scopes, issuer permissions and revocation
|
|
648
|
+
apply. See [MCP configuration and contracts](https://github.com/lkuich/storypane/blob/main/docs/management-api.md#mcp-adapter).
|