@storystick/next 0.0.0-stage → 0.2.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +112 -0
  2. package/PUBLIC-API.md +31 -0
  3. package/README.md +522 -2
  4. package/dist/.source-hash +1 -0
  5. package/dist/cache-tags.d.ts +2 -0
  6. package/dist/cache-tags.js +12 -0
  7. package/dist/clean.d.ts +1 -0
  8. package/dist/clean.js +1 -0
  9. package/dist/code-version.d.ts +29 -0
  10. package/dist/code-version.js +48 -0
  11. package/dist/file.d.ts +1 -0
  12. package/dist/file.js +1 -0
  13. package/dist/i18n-seo.d.ts +1 -0
  14. package/dist/i18n-seo.js +1 -0
  15. package/dist/image.d.ts +11 -0
  16. package/dist/image.js +22 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +3 -0
  19. package/dist/index.react-server.d.ts +3 -0
  20. package/dist/index.react-server.js +12 -0
  21. package/dist/link.d.ts +10 -0
  22. package/dist/link.js +8 -0
  23. package/dist/preview/index.d.ts +29 -0
  24. package/dist/preview/index.js +24 -0
  25. package/dist/preview/internal.d.ts +61 -0
  26. package/dist/preview/internal.js +97 -0
  27. package/dist/preview/preview-request-identity.d.ts +4 -0
  28. package/dist/preview/preview-request-identity.js +63 -0
  29. package/dist/preview/provenance-config.d.ts +42 -0
  30. package/dist/preview/provenance-config.js +132 -0
  31. package/dist/preview/provenance-frame-builder.d.ts +6 -0
  32. package/dist/preview/provenance-frame-builder.js +10 -0
  33. package/dist/preview/provenance-response.d.ts +5 -0
  34. package/dist/preview/provenance-response.js +9 -0
  35. package/dist/preview/provenance-runtime-adapter.d.ts +9 -0
  36. package/dist/preview/provenance-runtime-adapter.js +42 -0
  37. package/dist/preview/render-frame-provenance.d.ts +23 -0
  38. package/dist/preview/render-frame-provenance.js +47 -0
  39. package/dist/preview/route-authoring.d.ts +3 -0
  40. package/dist/preview/route-authoring.js +80 -0
  41. package/dist/preview/route-context.d.ts +3 -0
  42. package/dist/preview/route-context.js +38 -0
  43. package/dist/preview/route-disable.d.ts +7 -0
  44. package/dist/preview/route-disable.js +63 -0
  45. package/dist/preview/route-document-provenance.d.ts +23 -0
  46. package/dist/preview/route-document-provenance.js +46 -0
  47. package/dist/preview/route-errors.d.ts +1 -0
  48. package/dist/preview/route-errors.js +1 -0
  49. package/dist/preview/route-handler.d.ts +36 -0
  50. package/dist/preview/route-handler.js +139 -0
  51. package/dist/preview/route-initial.d.ts +9 -0
  52. package/dist/preview/route-initial.js +141 -0
  53. package/dist/preview/route-provenance-runtime.d.ts +34 -0
  54. package/dist/preview/route-provenance-runtime.js +90 -0
  55. package/dist/preview/route-token.d.ts +6 -0
  56. package/dist/preview/route-token.js +46 -0
  57. package/dist/preview/server-preview-internals.d.ts +3 -0
  58. package/dist/preview/server-preview-internals.js +3 -0
  59. package/dist/preview/slice-locale.d.ts +5 -0
  60. package/dist/preview/slice-locale.js +15 -0
  61. package/dist/preview-error-surface.d.ts +7 -0
  62. package/dist/preview-error-surface.js +24 -0
  63. package/dist/preview-exit.d.ts +7 -0
  64. package/dist/preview-exit.js +71 -0
  65. package/dist/preview-metadata.d.ts +15 -0
  66. package/dist/preview-metadata.js +13 -0
  67. package/dist/preview-mode-latch.d.ts +133 -0
  68. package/dist/preview-mode-latch.js +231 -0
  69. package/dist/preview-nav-location.d.ts +12 -0
  70. package/dist/preview-nav-location.js +149 -0
  71. package/dist/preview-nav.d.ts +94 -0
  72. package/dist/preview-nav.js +523 -0
  73. package/dist/preview-refresh-coordinator.d.ts +26 -0
  74. package/dist/preview-refresh-coordinator.js +93 -0
  75. package/dist/preview-render-error.d.ts +11 -0
  76. package/dist/preview-render-error.js +36 -0
  77. package/dist/preview-render-recovery.d.ts +9 -0
  78. package/dist/preview-render-recovery.js +42 -0
  79. package/dist/preview-session-recovery.d.ts +1 -0
  80. package/dist/preview-session-recovery.js +1 -0
  81. package/dist/preview-standalone-host.d.ts +3 -0
  82. package/dist/preview-standalone-host.js +55 -0
  83. package/dist/preview-text.d.ts +2 -0
  84. package/dist/preview-text.js +1 -0
  85. package/dist/preview-view-bootstrap.d.ts +5 -0
  86. package/dist/preview-view-bootstrap.js +13 -0
  87. package/dist/public-delivery.d.ts +6 -0
  88. package/dist/public-delivery.js +7 -0
  89. package/dist/query-fetch-options.d.ts +7 -0
  90. package/dist/query-fetch-options.js +193 -0
  91. package/dist/query.d.ts +2 -0
  92. package/dist/query.js +1 -0
  93. package/dist/rendering.d.ts +33 -0
  94. package/dist/rendering.js +42 -0
  95. package/dist/revalidate.d.ts +12 -0
  96. package/dist/revalidate.js +33 -0
  97. package/dist/rich-text.d.ts +2 -0
  98. package/dist/rich-text.js +1 -0
  99. package/dist/server-runtime.d.ts +208 -0
  100. package/dist/server-runtime.js +1584 -0
  101. package/dist/server.d.ts +8 -0
  102. package/dist/server.js +6 -0
  103. package/dist/slice-zone.d.ts +2 -0
  104. package/dist/slice-zone.js +1 -0
  105. package/dist/storystick-component-types.d.ts +26 -0
  106. package/dist/storystick-component-types.js +1 -0
  107. package/dist/storystick-component.d.ts +10 -0
  108. package/dist/storystick-component.js +45 -0
  109. package/dist/storystick-error-recovery.d.ts +12 -0
  110. package/dist/storystick-error-recovery.js +11 -0
  111. package/dist/storystick-preview-runtime.d.ts +30 -0
  112. package/dist/storystick-preview-runtime.js +561 -0
  113. package/dist/storystick.d.ts +24 -0
  114. package/dist/storystick.js +23 -0
  115. package/dist/testing.d.ts +9 -0
  116. package/dist/testing.js +40 -0
  117. package/dist/video.d.ts +1 -0
  118. package/dist/video.js +1 -0
  119. package/package.json +128 -3
  120. package/recipes/README.md +91 -0
  121. package/recipes/resource-mirror.ts +49 -0
@@ -0,0 +1,36 @@
1
+ // Next preserves only a server error's digest in production Flight responses.
2
+ // Keep this marker closed and free of upstream messages, URLs, or credentials.
3
+ export const PREVIEW_READ_UNAVAILABLE_DIGEST = 'storystick.preview.read-unavailable.v1';
4
+ export const PREVIEW_CONTEXT_SUPERSEDED_DIGEST = 'storystick.preview.context-superseded.v1';
5
+ export const CUSTOMER_RENDER_ERROR = 'runtime.customer_render_failed';
6
+ export const PREVIEW_READ_UNAVAILABLE = 'runtime.preview_read_unavailable';
7
+ export function previewReadUnavailableDigest(retryAfterMs, now) {
8
+ const retryNotBefore = now + (retryAfterMs ?? 0);
9
+ return retryAfterMs !== undefined && retryAfterMs >= 0 && Number.isSafeInteger(retryNotBefore)
10
+ ? `${PREVIEW_READ_UNAVAILABLE_DIGEST}:${retryNotBefore}`
11
+ : PREVIEW_READ_UNAVAILABLE_DIGEST;
12
+ }
13
+ export function previewRenderError(digest) {
14
+ const suffix = digest?.startsWith(`${PREVIEW_READ_UNAVAILABLE_DIGEST}:`)
15
+ ? digest.slice(PREVIEW_READ_UNAVAILABLE_DIGEST.length + 1) : undefined;
16
+ const retryNotBefore = suffix && /^\d+$/.test(suffix) && Number.isSafeInteger(Number(suffix))
17
+ ? Number(suffix) : undefined;
18
+ return digest === PREVIEW_CONTEXT_SUPERSEDED_DIGEST
19
+ ? {
20
+ code: 'runtime.preview_context_superseded',
21
+ message: 'The Preview selection changed while this page was loading. Retry to display the latest selection.',
22
+ retryable: true,
23
+ }
24
+ : digest === PREVIEW_READ_UNAVAILABLE_DIGEST || retryNotBefore !== undefined
25
+ ? {
26
+ code: PREVIEW_READ_UNAVAILABLE,
27
+ message: 'Preview content is temporarily unavailable. Retry when the content service is reachable again.',
28
+ retryable: true,
29
+ ...(retryNotBefore !== undefined ? { retryNotBefore } : {}),
30
+ }
31
+ : {
32
+ code: CUSTOMER_RENDER_ERROR,
33
+ message: 'The customer page could not render. Open another document or retry after correcting the page error.',
34
+ retryable: false,
35
+ };
36
+ }
@@ -0,0 +1,9 @@
1
+ /** Retry the whole render once per accepted context, never individual queries
2
+ * under another revision. A persistently failing context stays inspectable. */
3
+ export declare function recoverSupersededPreviewRender(contextKey: string, retry: () => void): boolean;
4
+ export declare function registerPreviewRenderFailure(reset: () => void, code?: string): () => void;
5
+ export declare function resetPreviewRenderFailure(): void;
6
+ /** A layout receipt cannot attest a page currently replaced by an error boundary. */
7
+ export declare function hasPreviewRenderFailure(): boolean;
8
+ /** Called only at a framework commit, never merely after sending navigation. */
9
+ export declare function confirmPreviewRender(): boolean;
@@ -0,0 +1,42 @@
1
+ import { emitRuntimeRecovered } from '@storystick/preview/runtime/emit-runtime-error';
2
+ import { CUSTOMER_RENDER_ERROR } from './preview-render-error.js';
3
+ // The Next error fallback can replace the layout that owned the adapter.
4
+ // Keep the failure across that handoff, but never let an old cleanup remove
5
+ // the reset callback installed by a newer fallback.
6
+ const boundaries = new Set();
7
+ const failures = new Set();
8
+ const supersededRetries = new Set();
9
+ /** Retry the whole render once per accepted context, never individual queries
10
+ * under another revision. A persistently failing context stays inspectable. */
11
+ export function recoverSupersededPreviewRender(contextKey, retry) {
12
+ if (supersededRetries.has(contextKey))
13
+ return false;
14
+ supersededRetries.add(contextKey);
15
+ retry();
16
+ return true;
17
+ }
18
+ export function registerPreviewRenderFailure(reset, code = CUSTOMER_RENDER_ERROR) {
19
+ const owner = { reset };
20
+ boundaries.add(owner);
21
+ failures.add(code);
22
+ return () => { boundaries.delete(owner); };
23
+ }
24
+ export function resetPreviewRenderFailure() {
25
+ for (const boundary of [...boundaries])
26
+ boundary.reset();
27
+ }
28
+ /** A layout receipt cannot attest a page currently replaced by an error boundary. */
29
+ export function hasPreviewRenderFailure() {
30
+ return boundaries.size > 0;
31
+ }
32
+ /** Called only at a framework commit, never merely after sending navigation. */
33
+ export function confirmPreviewRender() {
34
+ if (hasPreviewRenderFailure())
35
+ return false;
36
+ supersededRetries.clear();
37
+ const recovered = [...failures];
38
+ failures.clear();
39
+ for (const code of recovered)
40
+ emitRuntimeRecovered(code);
41
+ return true;
42
+ }
@@ -0,0 +1 @@
1
+ export { appendSearchParamsToRedirect } from '@storystick/client/adapter';
@@ -0,0 +1 @@
1
+ export { appendSearchParamsToRedirect } from '@storystick/client/adapter';
@@ -0,0 +1,3 @@
1
+ import { type PreviewStandaloneControlsProps } from '@storystick/react/internal/preview-host';
2
+ /** The framework owns routing; the active Preview view owns its editor. */
3
+ export declare function PreviewStandaloneHost(props: PreviewStandaloneControlsProps): null;
@@ -0,0 +1,55 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useLayoutEffect, useState } from 'react';
4
+ import { createRoot } from 'react-dom/client';
5
+ import { usePathname } from 'next/navigation';
6
+ import { PreviewStandaloneControls, } from '@storystick/react/internal/preview-host';
7
+ const attachments = new Map();
8
+ let host = null;
9
+ let scheduled = false;
10
+ function renderHost() {
11
+ if (scheduled)
12
+ return;
13
+ scheduled = true;
14
+ // React can replace a layout (or its error boundary) in one commit. Transfer
15
+ // ownership after that commit, without tying the editor to either React tree.
16
+ queueMicrotask(() => {
17
+ scheduled = false;
18
+ const props = Array.from(attachments.values()).at(-1);
19
+ if (!props) {
20
+ host?.root.unmount();
21
+ host?.container.remove();
22
+ host = null;
23
+ return;
24
+ }
25
+ if (!host) {
26
+ const container = document.createElement('div');
27
+ container.dataset.storystickStandaloneHost = '';
28
+ host = { container, root: createRoot(container) };
29
+ }
30
+ // Only our own DOM is moved, including when a root layout replaces body.
31
+ if (!host.container.isConnected)
32
+ document.body.append(host.container);
33
+ const scope = JSON.stringify([
34
+ props.projectId, props.state.sessionId, props.basePath,
35
+ ]);
36
+ host.root.render(_jsx(PreviewStandaloneControls, { ...props }, scope));
37
+ });
38
+ }
39
+ /** The framework owns routing; the active Preview view owns its editor. */
40
+ export function PreviewStandaloneHost(props) {
41
+ const pathname = usePathname();
42
+ const [owner] = useState(() => Symbol('standalone-preview'));
43
+ useLayoutEffect(() => {
44
+ return () => {
45
+ attachments.delete(owner);
46
+ renderHost();
47
+ };
48
+ }, [owner]);
49
+ useLayoutEffect(() => {
50
+ // Updating an older adapter must not take ownership from its replacement.
51
+ attachments.set(owner, { ...props, pathname });
52
+ renderHost();
53
+ }, [owner, props, pathname]);
54
+ return null;
55
+ }
@@ -0,0 +1,2 @@
1
+ export { previewTextProps } from '@storystick/react/preview-text';
2
+ export type { PreviewTextProps, PreviewTextValue } from '@storystick/react/preview-text';
@@ -0,0 +1 @@
1
+ export { previewTextProps } from '@storystick/react/preview-text';
@@ -0,0 +1,5 @@
1
+ export declare class PreviewViewRecoveryError extends Error {
2
+ }
3
+ /** Ordinary navigation inherits the admitted perspective on the server. The
4
+ * tab's stored view is only for its toolbar/selection, never a reason to reload. */
5
+ export declare function ensureStandalonePreviewView(projectId: string): Promise<void>;
@@ -0,0 +1,13 @@
1
+ import { readStandalonePreviewSessionId } from '@storystick/preview/identity/browser-context';
2
+ export class PreviewViewRecoveryError extends Error {
3
+ }
4
+ /** Ordinary navigation inherits the admitted perspective on the server. The
5
+ * tab's stored view is only for its toolbar/selection, never a reason to reload. */
6
+ export async function ensureStandalonePreviewView(projectId) {
7
+ if (typeof window === 'undefined' || window.parent !== window)
8
+ return;
9
+ if (new URL(window.location.href).searchParams.has('_ss_session') &&
10
+ !readStandalonePreviewSessionId(projectId)) {
11
+ throw new PreviewViewRecoveryError('Open Preview from Studio again.');
12
+ }
13
+ }
@@ -0,0 +1,6 @@
1
+ import 'server-only';
2
+ import { type PublicRenderReceiptV1 } from '@storystick/client';
3
+ /** Explicit renderer-owned JSON; pass the receipt before crossing an RSC boundary. */
4
+ export declare function PublicDeliveryReceipt({ receipt }: {
5
+ receipt: PublicRenderReceiptV1 | null;
6
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import 'server-only';
3
+ import { PUBLIC_RENDER_RECEIPT_ID, serializePublicRenderReceipt } from '@storystick/client';
4
+ /** Explicit renderer-owned JSON; pass the receipt before crossing an RSC boundary. */
5
+ export function PublicDeliveryReceipt({ receipt }) {
6
+ return receipt ? _jsx("script", { id: PUBLIC_RENDER_RECEIPT_ID, type: "application/json", dangerouslySetInnerHTML: { __html: serializePublicRenderReceipt(receipt) } }) : null;
7
+ }
@@ -0,0 +1,7 @@
1
+ import type { Transport, TransportRequest } from '@storystick/client/internal';
2
+ export declare const NEXT_QUERY_TAG_MAX_LENGTH = 256;
3
+ export declare const NEXT_QUERY_TAG_MAX_COUNT = 128;
4
+ export declare function withNextQueryOptions(projectId: string, transport: Transport, isPreviewContextSuperseded?: () => Promise<boolean>, staticRendering?: boolean): Transport;
5
+ export declare function normalizeNextQueryRequest(projectId: string, input: TransportRequest, staticRendering?: boolean): TransportRequest;
6
+ /** Scope Next's persistent Data Cache without changing the upstream identity. */
7
+ export declare function deploymentScopedFetch(deploymentId: string | undefined, fetchImpl?: typeof fetch): typeof fetch;
@@ -0,0 +1,193 @@
1
+ import { storystickCacheTag } from './cache-tags.js';
2
+ import { StorystickError } from '@storystick/client';
3
+ import { PREVIEW_CONTEXT_SUPERSEDED_DIGEST, previewReadUnavailableDigest } from './preview-render-error.js';
4
+ export const NEXT_QUERY_TAG_MAX_LENGTH = 256;
5
+ export const NEXT_QUERY_TAG_MAX_COUNT = 128;
6
+ export function withNextQueryOptions(projectId, transport, isPreviewContextSuperseded, staticRendering = false) {
7
+ return {
8
+ async request(input) {
9
+ const normalized = normalizeNextQueryRequest(projectId, input, staticRendering);
10
+ try {
11
+ return await transport.request(normalized);
12
+ }
13
+ catch (error) {
14
+ // A context command may supersede an in-flight RSC read, including
15
+ // its final receipt write. A fresh authority check below keeps the
16
+ // failed render atomic: retry the render, never individual reads in a
17
+ // different perspective.
18
+ if (input.requestContext?.previewToken && isCacheableRead(input.udfPath) &&
19
+ error instanceof StorystickError &&
20
+ ['storystick/PREVIEW_CONTEXT_INVALID', 'storystick/PREVIEW_RECEIPT_UNAVAILABLE'].includes(error.code ?? '') &&
21
+ await isPreviewContextSuperseded?.().catch(() => false)) {
22
+ Object.assign(error, { digest: PREVIEW_CONTEXT_SUPERSEDED_DIGEST });
23
+ throw error;
24
+ }
25
+ // Only SDK reads can attest to an upstream outage. Do not infer it
26
+ // from customer exception text or change public-page error handling.
27
+ if (input.requestContext?.previewToken && isCacheableRead(input.udfPath) &&
28
+ error instanceof StorystickError &&
29
+ error.category === 'system' && error.retryable === true) {
30
+ Object.assign(error, { digest: previewReadUnavailableDigest(error.retryAfterMs, Date.now()) });
31
+ }
32
+ throw error;
33
+ }
34
+ },
35
+ };
36
+ }
37
+ export function normalizeNextQueryRequest(projectId, input, staticRendering = false) {
38
+ if (!isCacheableRead(input.udfPath))
39
+ return input;
40
+ if (input.requestContext?.previewToken || input.options?.deliveryCache === 'bypass') {
41
+ return {
42
+ ...input,
43
+ options: {
44
+ ...input.options,
45
+ cache: 'no-store',
46
+ next: undefined,
47
+ },
48
+ };
49
+ }
50
+ const next = input.options?.next;
51
+ if (next?.revalidate === false || (typeof next?.revalidate === 'number' &&
52
+ (!Number.isFinite(next.revalidate) || next.revalidate < 0))) {
53
+ throw queryInvalid('Public framework caching requires a finite positive revalidate TTL.');
54
+ }
55
+ if (input.options?.cache === 'force-cache' && (next?.revalidate === undefined || next.revalidate === 0)) {
56
+ throw queryInvalid('force-cache requires a finite positive revalidate TTL.');
57
+ }
58
+ if (input.options?.cache === 'no-store' && next?.revalidate !== undefined && next.revalidate !== 0) {
59
+ throw queryInvalid('no-store cannot be combined with a non-zero revalidate value.');
60
+ }
61
+ if (input.options?.cache === 'force-cache' && next?.revalidate === 0) {
62
+ throw queryInvalid('force-cache cannot be combined with revalidate: 0.');
63
+ }
64
+ // Auto preserves Next's build-time prerendering and draftMode. Static sites
65
+ // must also namespace their fetch cache by deployment (see server-runtime).
66
+ if (staticRendering && input.options?.cache === undefined && next?.revalidate === undefined) {
67
+ return { ...input, options: { ...input.options, next: undefined } };
68
+ }
69
+ if (input.options?.cache === 'no-store' || next?.revalidate === 0 || next?.revalidate === undefined) {
70
+ return {
71
+ ...input,
72
+ options: {
73
+ ...input.options,
74
+ cache: 'no-store',
75
+ next: undefined,
76
+ },
77
+ };
78
+ }
79
+ const automatic = automaticTags(projectId, input);
80
+ const custom = dedupeAndValidateTags(next?.tags ?? []);
81
+ const projectTag = storystickCacheTag(`storystick:project:${projectId}`);
82
+ // Every read must retain project invalidation. Extra automatic tags are
83
+ // optional precision; they must not make a large story set unreadable.
84
+ const required = dedupeAndValidateTags([...custom, projectTag]);
85
+ const tags = Array.from(new Set([...required, ...automatic.map(storystickCacheTag)]))
86
+ .slice(0, NEXT_QUERY_TAG_MAX_COUNT);
87
+ return {
88
+ ...input,
89
+ options: {
90
+ ...input.options,
91
+ cache: input.options?.cache ?? 'force-cache',
92
+ next: {
93
+ ...(next?.revalidate !== undefined ? { revalidate: next.revalidate } : {}),
94
+ tags,
95
+ },
96
+ },
97
+ };
98
+ }
99
+ /** Scope Next's persistent Data Cache without changing the upstream identity. */
100
+ export function deploymentScopedFetch(deploymentId, fetchImpl = globalThis.fetch) {
101
+ if (!deploymentId)
102
+ return fetchImpl;
103
+ return (input, init) => {
104
+ const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined));
105
+ headers.set('x-storystick-deployment', deploymentId);
106
+ return fetchImpl(input, { ...init, headers });
107
+ };
108
+ }
109
+ function isCacheableRead(udfPath) {
110
+ return udfPath === 'queries:queryV2' ||
111
+ udfPath === 'queries:getRouteByPathname' ||
112
+ udfPath === 'queries:listPublishedLocaleRoutes' ||
113
+ udfPath === 'queries:schemas.current' ||
114
+ udfPath === 'locales:get';
115
+ }
116
+ function automaticTags(projectId, input) {
117
+ const tags = [`storystick:project:${projectId}`];
118
+ if (input.udfPath === 'queries:getRouteByPathname') {
119
+ if (typeof input.args.pathname === 'string') {
120
+ tags.push(`storystick:pathname:${input.args.pathname}`);
121
+ }
122
+ if (Array.isArray(input.args.storyTypes)) {
123
+ for (const storyType of input.args.storyTypes) {
124
+ if (typeof storyType === 'string') {
125
+ tags.push(`storystick:type:${storyType}`);
126
+ }
127
+ }
128
+ }
129
+ return tags;
130
+ }
131
+ if (input.udfPath === 'queries:listPublishedLocaleRoutes') {
132
+ if (Array.isArray(input.args.storyTypes)) {
133
+ for (const storyType of input.args.storyTypes) {
134
+ if (typeof storyType === 'string') {
135
+ tags.push(`storystick:type:${storyType}`);
136
+ }
137
+ }
138
+ }
139
+ return tags;
140
+ }
141
+ if (input.udfPath !== 'queries:queryV2')
142
+ return tags;
143
+ const query = input.args.query && typeof input.args.query === 'object'
144
+ ? input.args.query
145
+ : undefined;
146
+ const storyType = query?.target?.storyType;
147
+ if (typeof storyType === 'string') {
148
+ tags.push(`storystick:type:${storyType}`);
149
+ }
150
+ const locale = query?.locale?.code;
151
+ if (typeof locale === 'string') {
152
+ tags.push(`storystick:locale:${locale}`);
153
+ }
154
+ const addressKind = query?.address?.kind;
155
+ const addressValue = query?.address?.value;
156
+ if (typeof addressKind === 'string' && typeof addressValue === 'string') {
157
+ if (addressKind === 'id') {
158
+ tags.push(`storystick:doc:${addressValue}`);
159
+ }
160
+ else if (addressKind === 'pathname') {
161
+ tags.push(`storystick:pathname:${addressValue}`);
162
+ }
163
+ else if (addressKind === 'slug' && typeof storyType === 'string') {
164
+ tags.push(`storystick:slug:${storyType}:${addressValue}`);
165
+ }
166
+ }
167
+ return tags;
168
+ }
169
+ function dedupeAndValidateTags(tags) {
170
+ const output = [];
171
+ const seen = new Set();
172
+ for (const tag of tags) {
173
+ if (typeof tag !== 'string' || tag.length === 0 || tag.length > NEXT_QUERY_TAG_MAX_LENGTH) {
174
+ throw queryInvalid(`Next cache tags must contain 1-${NEXT_QUERY_TAG_MAX_LENGTH} characters.`);
175
+ }
176
+ if (seen.has(tag))
177
+ continue;
178
+ seen.add(tag);
179
+ output.push(tag);
180
+ if (output.length > NEXT_QUERY_TAG_MAX_COUNT) {
181
+ throw queryInvalid(`Next cache tags cannot exceed ${NEXT_QUERY_TAG_MAX_COUNT} entries.`);
182
+ }
183
+ }
184
+ return output;
185
+ }
186
+ function queryInvalid(message) {
187
+ return new StorystickError(message, {
188
+ code: 'storystick/QUERY_INVALID',
189
+ category: 'validation',
190
+ recoverable: true,
191
+ retryable: false,
192
+ });
193
+ }
@@ -0,0 +1,2 @@
1
+ export { useQuery } from '@storystick/react/query';
2
+ export type { UseQueryOptions, UseQueryResult } from '@storystick/react/query';
package/dist/query.js ADDED
@@ -0,0 +1 @@
1
+ export { useQuery } from '@storystick/react/query';
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @storystick/next — Next.js rendering helpers and the global Preview runtime.
3
+ *
4
+ * Configure `createSite({ definition })` from `@storystick/next/server` once.
5
+ * Ordinary async components read with `await site.client()` and Query V2.
6
+ * Keep `<Storystick />` in a shared native layout and export `site.handlers`
7
+ * from the SDK HTTP route. CMS routing is optional: `site.resolvePage`,
8
+ * `site.metadata` and `site.params` compose with Next's native page exports.
9
+ *
10
+ * Server-only request APIs live at `./server` and `./preview`; this root stays
11
+ * client-compatible. See the README for the complete installation.
12
+ */
13
+ export { createClient, QueryExecutionError, resolveReference, resolveReferences, resolveSafeHref, safeHref, StorystickError, } from '@storystick/client';
14
+ export type { Client, ClientOptions, QueryResult, SelectedDocument, StorystickErrorCategory, StorystickErrorOptions, } from '@storystick/client';
15
+ export { prefixHref, publicPathnameForLocale, readDocumentLocaleRouting, resolveLocalePathname, } from '@storystick/client/locale-routing';
16
+ export type { DocumentLocaleRouting, LocalePathnameResolution, LocaleRouting, LocaleRoutingSettings, RoutingLocale, } from '@storystick/client/locale-routing';
17
+ export { stripPreviewProvenance } from './clean.js';
18
+ export { RichText } from './rich-text.js';
19
+ export { SliceZone, ZoneSlot, Slice, previewTextProps, resolveLink, toText, } from '@storystick/react';
20
+ export { StorystickImage, getNextImageProps, resolveImage, } from './image.js';
21
+ export type { GetNextImagePropsOptions, ResolvedStorystickImage, StorystickAsset, StorystickAssetSidecar, StorystickImageComponentProps, StorystickImageField, StorystickImageFailure, StorystickImageFallback, } from './image.js';
22
+ export { StorystickLink } from './link.js';
23
+ export type { StorystickLinkDocument, StorystickLinkOptions, StorystickLinkProps, StorystickLinkValue, } from './link.js';
24
+ export { StorystickFile, resolveFile } from './file.js';
25
+ export type { StorystickFileComponentProps, StorystickFileFailure, StorystickFileFallback, } from './file.js';
26
+ export { StorystickVideo, resolveVideo } from './video.js';
27
+ export type { StorystickVideoComponentProps, StorystickVideoFailure, StorystickVideoFallback, } from './video.js';
28
+ export type { UseQueryOptions, UseQueryResult } from './query.js';
29
+ export type { PreviewTextProps, PreviewTextValue, SliceElements, SliceRootProps, LinkOptions, LinkProps, LinkStatus, LinkValue, ReferencedDocumentFailureReason, ReferencedDocumentPolicy, ReferencedDocumentSidecarEntry, ReferencedDocumentSidecarFailure, ReferencedDocumentsSidecar, ResolvedLink, RichTextCustomMarkComponentProps, RichTextBlockComponentProps, RichTextCodeComponentProps, RichTextComponents, RichTextInlineProps, RichTextLinkMarkComponentProps, RichTextListComponentProps, RichTextMark, RichTextMarkComponentProps, RichTextNode, RichTextOrderedListComponentProps, RichTextProps, RichTextRenderMode, SliceComponentRegistry, SliceComponentProps, SliceFallback, SliceData, SliceZoneProps, ZoneSlotDescriptor, ZoneSlotKind, ZoneSlotProps, } from '@storystick/react';
30
+ export { Storystick } from './storystick.js';
31
+ export { StorystickErrorRecovery } from './storystick-error-recovery.js';
32
+ export type { StorystickProps } from './storystick.js';
33
+ export { STORYSTICK_PREVIEW_ROBOTS } from './preview-metadata.js';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @storystick/next — Next.js rendering helpers and the global Preview runtime.
3
+ *
4
+ * Configure `createSite({ definition })` from `@storystick/next/server` once.
5
+ * Ordinary async components read with `await site.client()` and Query V2.
6
+ * Keep `<Storystick />` in a shared native layout and export `site.handlers`
7
+ * from the SDK HTTP route. CMS routing is optional: `site.resolvePage`,
8
+ * `site.metadata` and `site.params` compose with Next's native page exports.
9
+ *
10
+ * Server-only request APIs live at `./server` and `./preview`; this root stays
11
+ * client-compatible. See the README for the complete installation.
12
+ */
13
+ // `StorystickError` is the source-compat re-export from `@storystick/client`
14
+ // (ADR-091 Consequences > @storystick/next). `instanceof` checks resolve
15
+ // to the same class identity across `@storystick/next` and `@storystick/client`
16
+ // import paths.
17
+ export { createClient, QueryExecutionError, resolveReference, resolveReferences, resolveSafeHref, safeHref, StorystickError, } from '@storystick/client';
18
+ export { prefixHref, publicPathnameForLocale, readDocumentLocaleRouting, resolveLocalePathname, } from '@storystick/client/locale-routing';
19
+ export { stripPreviewProvenance } from './clean.js';
20
+ export { RichText } from './rich-text.js';
21
+ // Re-export react bindings for convenience.
22
+ export { SliceZone, ZoneSlot, Slice, previewTextProps, resolveLink, toText, } from '@storystick/react';
23
+ export { StorystickImage, getNextImageProps, resolveImage, } from './image.js';
24
+ export { StorystickLink } from './link.js';
25
+ export { StorystickFile, resolveFile } from './file.js';
26
+ export { StorystickVideo, resolveVideo } from './video.js';
27
+ // Mount once in a shared layout, alongside its children. The Next wrapper
28
+ // supplies the deploy code version without introducing a render provider.
29
+ export { Storystick } from './storystick.js';
30
+ export { StorystickErrorRecovery } from './storystick-error-recovery.js';
31
+ export { STORYSTICK_PREVIEW_ROBOTS } from './preview-metadata.js';
32
+ // ADR-087 Lane D' — the Next-specific preview navigation adapter
33
+ // lives at the `@storystick/next/preview-nav` subpath. We do NOT
34
+ // re-export it from this root entry because the root entry is
35
+ // imported by client components and server components alike, and
36
+ // the hook drags `next/compat/router` into both.
37
+ //
38
+ // Consumers import the hook explicitly :
39
+ //
40
+ // import { useStorystickPreviewNavigation } from '@storystick/next/preview-nav'
41
+ //
42
+ // See `README.md` "Preview navigation" section for usage.
@@ -0,0 +1,12 @@
1
+ import 'server-only';
2
+ import { signWebhookBody } from '@storystick/client/adapter';
3
+ export interface StorystickRevalidateHandlerOptions {
4
+ secret?: string | (() => string | undefined);
5
+ projectId?: string | (() => string | undefined);
6
+ signatureHeader?: string;
7
+ }
8
+ /** Next 15 revalidateTag expires the project's Data Cache synchronously. This
9
+ * acknowledges the effect only; the publisher separately verifies rendered HTML. */
10
+ export declare function createStorystickRevalidateHandler(options?: StorystickRevalidateHandlerOptions): (request: Request) => Promise<Response>;
11
+ export declare const POST: (request: Request) => Promise<Response>;
12
+ export declare const createStorystickWebhookSignature: typeof signWebhookBody;
@@ -0,0 +1,33 @@
1
+ import 'server-only';
2
+ import { storystickCacheTag } from './cache-tags.js';
3
+ import { readSignedWebhook, signWebhookBody } from '@storystick/client/adapter';
4
+ import { isPublicationEvent } from '@storystick/types/publication';
5
+ import { revalidateTag } from 'next/cache';
6
+ /** Next 15 revalidateTag expires the project's Data Cache synchronously. This
7
+ * acknowledges the effect only; the publisher separately verifies rendered HTML. */
8
+ export function createStorystickRevalidateHandler(options = {}) {
9
+ return async (request) => {
10
+ const json = (value, status = 200) => Response.json(value, { status, headers: { 'Cache-Control': 'no-store' } });
11
+ if (request.method !== 'POST')
12
+ return new Response(null, { status: 405, headers: { Allow: 'POST', 'Cache-Control': 'no-store' } });
13
+ const secret = (typeof options.secret === 'function' ? options.secret() : options.secret) ?? process.env.STORYSTICK_WEBHOOK_SECRET;
14
+ const projectId = (typeof options.projectId === 'function' ? options.projectId() : options.projectId) ?? process.env.STORYSTICK_PROJECT_ID;
15
+ if (!secret || !projectId)
16
+ return json({ error: 'webhook_not_configured' }, 500);
17
+ const result = await readSignedWebhook(request, { secret, signatureHeader: options.signatureHeader });
18
+ if (!result.ok)
19
+ return json({ error: result.error }, result.status);
20
+ const event = result.payload;
21
+ if (!isPublicationEvent(event) || event.projectId !== projectId)
22
+ return json({ error: 'publication_invalid' }, 400);
23
+ try {
24
+ revalidateTag(storystickCacheTag(`storystick:project:${projectId}`));
25
+ }
26
+ catch {
27
+ return json({ error: 'publication_effect_failed' }, 503);
28
+ }
29
+ return json({ version: 2, projectId, id: event.id, effect: 'completed' });
30
+ };
31
+ }
32
+ export const POST = createStorystickRevalidateHandler();
33
+ export const createStorystickWebhookSignature = signWebhookBody;
@@ -0,0 +1,2 @@
1
+ export { RichText, toText } from '@storystick/react/rich-text';
2
+ export type { RichTextCustomMarkComponentProps, RichTextBlockComponentProps, RichTextCodeComponentProps, RichTextComponents, RichTextInlineProps, RichTextLinkMarkComponentProps, RichTextListComponentProps, RichTextMark, RichTextMarkComponentProps, RichTextNode, RichTextOrderedListComponentProps, RichTextProps, RichTextRenderMode, } from '@storystick/react/rich-text';
@@ -0,0 +1 @@
1
+ export { RichText, toText } from '@storystick/react/rich-text';