@estokad/next 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Copyright © 2026 Samarkand Industries OÜ. All rights reserved.
2
+
3
+ Estokad is proprietary software. Source code in this repository is the
4
+ confidential intellectual property of Samarkand Industries OÜ
5
+ (registered Tallinn, Estonia).
6
+
7
+ Permission to use, copy, modify, or distribute this software is granted
8
+ only under a separate written agreement signed by Samarkand Industries
9
+ OÜ. Without such agreement, no license is granted, expressed or implied.
10
+
11
+ Public-facing components — the SDK packages published to npm under the
12
+ `@estokad/*` scope, and the SDKs' published documentation — are licensed
13
+ separately. See the LICENSE file inside each `packages/*` directory or
14
+ the package's npm registry page for the terms that apply to that
15
+ specific package.
16
+
17
+ Contact: legal@samarkandindustries.com
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # @estokad/next
2
+
3
+ Next.js App Router adapter for Estokad. Type-safe content fetching, draft mode, and visual-edit overlay — all wired through one provider.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @estokad/next @estokad/sdk
9
+ ```
10
+
11
+ ## Set up the provider
12
+
13
+ In your root layout:
14
+
15
+ ```tsx
16
+ // app/layout.tsx
17
+ import { EstokadProvider, getEstokad } from '@estokad/next'
18
+
19
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
20
+ return (
21
+ <html>
22
+ <body>
23
+ <EstokadProvider workspace="my-workspace" apiUrl="https://api.estokad.com">
24
+ {children}
25
+ </EstokadProvider>
26
+ </body>
27
+ </html>
28
+ )
29
+ }
30
+ ```
31
+
32
+ `EstokadProvider` reads draft state from cookies, injects the visual-edit overlay script when draft mode is on, and provides the typed client to server + client components downstream.
33
+
34
+ ## Fetch + render
35
+
36
+ ```tsx
37
+ // app/articles/[slug]/page.tsx
38
+ import { getEstokad, Estokad } from '@estokad/next'
39
+
40
+ export default async function ArticlePage({ params }: { params: { slug: string } }) {
41
+ const cms = getEstokad()
42
+ const article = await cms.fetch('article', params.slug)
43
+
44
+ return (
45
+ <article>
46
+ <Estokad.Text entry={article} field="title" as="h1" />
47
+ <Estokad.RichText entry={article} field="body" />
48
+ <Estokad.Image entry={article} field="hero" alt={article.title} />
49
+ </article>
50
+ )
51
+ }
52
+ ```
53
+
54
+ The `<Estokad.*>` components emit plain HTML in production. In draft mode they add `data-estokad-field` attributes that the visual-edit overlay reads to support click-to-edit.
55
+
56
+ ## Draft mode
57
+
58
+ Wire the route handler that toggles draft mode (called by the Studio's preview link):
59
+
60
+ ```ts
61
+ // app/api/draft/route.ts
62
+ import { estokadDraftMode } from '@estokad/next/draft'
63
+
64
+ export const { GET } = estokadDraftMode.enable({
65
+ workspace: 'my-workspace',
66
+ previewSecret: process.env.ESTOKAD_PREVIEW_SECRET!,
67
+ })
68
+ ```
69
+
70
+ Disable:
71
+
72
+ ```ts
73
+ // app/api/draft/disable/route.ts
74
+ import { estokadDraftMode } from '@estokad/next/draft'
75
+
76
+ export const { GET } = estokadDraftMode.disable()
77
+ ```
78
+
79
+ ## Visual edit overlay
80
+
81
+ Mount the overlay route once:
82
+
83
+ ```ts
84
+ // app/api/estokad/overlay/route.ts
85
+ export { estokadOverlayRoute as GET } from '@estokad/next/overlay-route'
86
+ ```
87
+
88
+ `EstokadProvider` injects the overlay automatically when draft mode is on.
89
+
90
+ ## Documentation
91
+
92
+ Full reference at [`docs.estokad.com/visual-edit`](https://docs.estokad.com/docs/visual-edit) and [`docs.estokad.com/getting-started`](https://docs.estokad.com/docs/getting-started).
93
+
94
+ ## License
95
+
96
+ Apache-2.0. Estokad is a Samarkand Industries OÜ product.
@@ -0,0 +1,58 @@
1
+ import type { ComponentProps, ElementType, ReactElement, ReactNode } from 'react';
2
+ import { type TiptapDoc } from './rich-text.js';
3
+ interface FieldMetadata {
4
+ /** UUID of the entry this field belongs to. */
5
+ readonly entryId: string;
6
+ /** camelCase name of the content type. */
7
+ readonly contentType: string;
8
+ /** camelCase name of the field. */
9
+ readonly fieldName: string;
10
+ }
11
+ interface TextProps<As extends ElementType = 'span'> extends FieldMetadata {
12
+ readonly value: string | null | undefined;
13
+ readonly as?: As;
14
+ readonly className?: string;
15
+ }
16
+ export declare function Text<As extends ElementType = 'span'>(props: TextProps<As> & Omit<ComponentProps<As>, keyof TextProps<As>>): Promise<ReactElement>;
17
+ interface RichTextProps extends FieldMetadata {
18
+ readonly value: TiptapDoc | null | undefined;
19
+ readonly className?: string;
20
+ }
21
+ export declare function RichText(props: RichTextProps): Promise<ReactElement>;
22
+ interface NumberProps extends FieldMetadata {
23
+ readonly value: number | null | undefined;
24
+ readonly format?: (n: number) => string;
25
+ readonly className?: string;
26
+ }
27
+ export declare function Num(props: NumberProps): Promise<ReactElement>;
28
+ interface DateProps extends FieldMetadata {
29
+ readonly value: string | null | undefined;
30
+ readonly format?: (date: Date) => string;
31
+ readonly className?: string;
32
+ }
33
+ export declare function DateField(props: DateProps): Promise<ReactElement>;
34
+ interface ImageProps extends FieldMetadata {
35
+ /** Direct URL to the asset (or null when unset). */
36
+ readonly src: string | null | undefined;
37
+ readonly alt?: string;
38
+ readonly className?: string;
39
+ readonly width?: number;
40
+ readonly height?: number;
41
+ }
42
+ export declare function Image(props: ImageProps): Promise<ReactElement | null>;
43
+ interface FieldProps extends FieldMetadata {
44
+ readonly children: ReactNode;
45
+ readonly as?: ElementType;
46
+ readonly className?: string;
47
+ }
48
+ export declare function Field(props: FieldProps): Promise<ReactElement>;
49
+ export declare const Estokad: {
50
+ readonly Text: typeof Text;
51
+ readonly RichText: typeof RichText;
52
+ readonly Number: typeof Num;
53
+ readonly Date: typeof DateField;
54
+ readonly Image: typeof Image;
55
+ readonly Field: typeof Field;
56
+ };
57
+ export {};
58
+ //# sourceMappingURL=components.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../src/components.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjF,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/D,UAAU,aAAa;IACrB,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,0CAA0C;IAC1C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,mCAAmC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAoBD,UAAU,SAAS,CAAC,EAAE,SAAS,WAAW,GAAG,MAAM,CAAE,SAAQ,aAAa;IACxE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACzC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,CAAA;IAChB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,wBAAsB,IAAI,CAAC,EAAE,SAAS,WAAW,GAAG,MAAM,EACxD,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,EAAE,MAAM,SAAS,CAAC,EAAE,CAAC,CAAC,GACnE,OAAO,CAAC,YAAY,CAAC,CASvB;AAID,UAAU,aAAc,SAAQ,aAAa;IAC3C,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,CAAA;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,wBAAsB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAO1E;AAID,UAAU,WAAY,SAAQ,aAAa;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,wBAAsB,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CASnE;AAID,UAAU,SAAU,SAAQ,aAAa;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAA;IACxC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,wBAAsB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CAcvE;AAID,UAAU,UAAW,SAAQ,aAAa;IACxC,oDAAoD;IACpD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,wBAAsB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CA+B3E;AAID,UAAU,UAAW,SAAQ,aAAa;IACxC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;IAC5B,QAAQ,CAAC,EAAE,CAAC,EAAE,WAAW,CAAA;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,wBAAsB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CAQpE;AAOD,eAAO,MAAM,OAAO;;;;;;;CAOV,CAAA"}
@@ -0,0 +1,87 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // Auto-tagging components for the Studio's draft + visual edit flow.
3
+ //
4
+ // In production (no draft cookie) these components render as plain HTML —
5
+ // no extra attributes, no overhead. In draft mode they emit
6
+ // `data-estokad-*` attributes that the Phase 3 overlay script walks the
7
+ // DOM looking for, to draw editing affordances and bind postMessage
8
+ // events to fields.
9
+ //
10
+ // All components are server components (no 'use client'). They detect
11
+ // draft mode via Next.js's `draftMode()` from `next/headers`, which is
12
+ // only available in App Router. Pages-router consumers use the SDK
13
+ // directly without these wrappers.
14
+ import { draftMode } from 'next/headers';
15
+ import { renderRichText } from './rich-text.js';
16
+ async function getDraftAttrs(meta, fieldType) {
17
+ const { isEnabled } = await draftMode();
18
+ if (!isEnabled)
19
+ return {};
20
+ return {
21
+ 'data-estokad-entry': meta.entryId,
22
+ 'data-estokad-type': meta.contentType,
23
+ 'data-estokad-field': meta.fieldName,
24
+ 'data-estokad-fieldtype': fieldType,
25
+ };
26
+ }
27
+ export async function Text(props) {
28
+ const { value, as, className, entryId, contentType, fieldName, ...rest } = props;
29
+ const Tag = (as ?? 'span');
30
+ const dataAttrs = await getDraftAttrs({ entryId, contentType, fieldName }, 'text');
31
+ return (_jsx(Tag, { className: className, ...dataAttrs, ...rest, children: value ?? '' }));
32
+ }
33
+ export async function RichText(props) {
34
+ const dataAttrs = await getDraftAttrs(props, 'richText');
35
+ return (_jsx("div", { className: props.className, ...dataAttrs, children: props.value ? renderRichText(props.value) : null }));
36
+ }
37
+ export async function Num(props) {
38
+ const dataAttrs = await getDraftAttrs(props, 'number');
39
+ const formatted = props.value == null ? '' : props.format ? props.format(props.value) : String(props.value);
40
+ return (_jsx("span", { className: props.className, ...dataAttrs, children: formatted }));
41
+ }
42
+ export async function DateField(props) {
43
+ const dataAttrs = await getDraftAttrs(props, 'date');
44
+ let formatted = '';
45
+ if (props.value) {
46
+ const d = new Date(props.value);
47
+ if (!Number.isNaN(d.getTime())) {
48
+ formatted = props.format ? props.format(d) : d.toISOString().slice(0, 10);
49
+ }
50
+ }
51
+ return (_jsx("time", { dateTime: props.value ?? undefined, className: props.className, ...dataAttrs, children: formatted }));
52
+ }
53
+ export async function Image(props) {
54
+ const dataAttrs = await getDraftAttrs(props, 'asset');
55
+ if (!props.src) {
56
+ // In draft mode emit a placeholder anchor so the editor can still
57
+ // click an empty asset slot. In production: nothing.
58
+ if (Object.keys(dataAttrs).length === 0)
59
+ return null;
60
+ return (_jsx("span", { ...dataAttrs, className: props.className, style: {
61
+ display: 'inline-block',
62
+ minWidth: 80,
63
+ minHeight: 60,
64
+ background: 'rgba(0,212,255,0.08)',
65
+ border: '1px dashed #00D4FF',
66
+ }, "aria-label": "Choose an asset" }));
67
+ }
68
+ return (_jsx("img", { src: props.src, alt: props.alt ?? '', width: props.width, height: props.height, className: props.className, ...dataAttrs }));
69
+ }
70
+ export async function Field(props) {
71
+ const Tag = (props.as ?? 'span');
72
+ const dataAttrs = await getDraftAttrs(props, 'text');
73
+ return (_jsx(Tag, { className: props.className, ...dataAttrs, children: props.children }));
74
+ }
75
+ // --- Estokad namespace --------------------------------------------------
76
+ //
77
+ // `import { Estokad } from '@estokad/next'` — matches the pattern
78
+ // docs/visual-edit.md § 6 sketches out (`<Estokad.Text>`, etc.).
79
+ export const Estokad = {
80
+ Text,
81
+ RichText,
82
+ Number: Num,
83
+ Date: DateField,
84
+ Image,
85
+ Field,
86
+ };
87
+ //# sourceMappingURL=components.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.js","sourceRoot":"","sources":["../src/components.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { NextResponse, type NextRequest } from 'next/server';
2
+ interface EnableConfig {
3
+ /** Workspace slug. The token's `workspace` field must match. */
4
+ readonly workspace: string;
5
+ /** RS256 JWKS URL. When set, takes precedence over `previewSecret`.
6
+ * Recommended for production — the customer site verifies with the
7
+ * workspace's published public key, no shared secret to leak. */
8
+ readonly jwksUrl?: string;
9
+ /** Shared HMAC secret. Used as a fallback when jwksUrl is unset.
10
+ * Omit only in dev — production must set one of these. */
11
+ readonly previewSecret?: string;
12
+ }
13
+ export declare const estokadDraftMode: {
14
+ /** App Router GET handler that turns draft mode on and redirects.
15
+ * Returns 401 on missing / invalid / expired tokens. */
16
+ enable(config: EnableConfig): (request: NextRequest) => Promise<NextResponse>;
17
+ /** POST handler that turns draft mode off. */
18
+ disable(): Promise<NextResponse>;
19
+ };
20
+ export {};
21
+ //# sourceMappingURL=draft.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAI5D,UAAU,YAAY;IACpB,gEAAgE;IAChE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B;;sEAEkE;IAClE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB;+DAC2D;IAC3D,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAChC;AAED,eAAO,MAAM,gBAAgB;IAC3B;6DACyD;mBAC1C,YAAY,IACK,SAAS,WAAW,KAAG,OAAO,CAAC,YAAY,CAAC;IAsD5E,8CAA8C;eAC7B,OAAO,CAAC,YAAY,CAAC;CAOvC,CAAA"}
package/dist/draft.js ADDED
@@ -0,0 +1,101 @@
1
+ // Draft-mode route handlers for Next.js App Router.
2
+ //
3
+ // Per docs/visual-edit.md § 3.3 the Studio mints a preview token signed
4
+ // for the customer site, then loads `https://customer-site/api/draft?
5
+ // token=...&path=...` in an iframe. This handler validates the token,
6
+ // calls Next.js's draftMode().enable(), and redirects to the path.
7
+ //
8
+ // Customers wire it in their app/api/draft/route.ts:
9
+ //
10
+ // import { estokadDraftMode } from '@estokad/next/draft'
11
+ //
12
+ // export const GET = estokadDraftMode.enable({
13
+ // workspace: process.env.ESTOKAD_WORKSPACE!,
14
+ // previewSecret: process.env.ESTOKAD_PREVIEW_SECRET!,
15
+ // })
16
+ //
17
+ // export const POST = estokadDraftMode.disable
18
+ //
19
+ // M2.3b ships HMAC-SHA256 verification — sufficient for the launch threat
20
+ // model and a real signed-token contract. The KMS-backed asymmetric
21
+ // upgrade (Studio signs with a private key, customer sites verify with
22
+ // the published public key) is Phase 3 work; the wire format stays.
23
+ //
24
+ // If `previewSecret` is omitted we run in dev-permissive mode — any
25
+ // non-empty token unlocks draft. That's loud in the response so it doesn't
26
+ // silently get into production.
27
+ import { draftMode, cookies } from 'next/headers';
28
+ import { NextResponse } from 'next/server';
29
+ import { verifyDraftToken, verifyDraftTokenJwks } from './token.js';
30
+ export const estokadDraftMode = {
31
+ /** App Router GET handler that turns draft mode on and redirects.
32
+ * Returns 401 on missing / invalid / expired tokens. */
33
+ enable(config) {
34
+ return async function handler(request) {
35
+ const url = new URL(request.url);
36
+ const token = url.searchParams.get('token');
37
+ const path = url.searchParams.get('path') ?? '/';
38
+ const studioOrigin = url.searchParams.get('__estokad_studio_origin');
39
+ if (!token) {
40
+ return NextResponse.json({ error: 'missing_token' }, { status: 401 });
41
+ }
42
+ // M3.6d — JWKS verification takes precedence over the HMAC path.
43
+ let result = null;
44
+ if (config.jwksUrl) {
45
+ result = await verifyDraftTokenJwks(config.jwksUrl, token, {
46
+ workspace: config.workspace,
47
+ });
48
+ }
49
+ else if (config.previewSecret) {
50
+ result = verifyDraftToken(config.previewSecret, token, {
51
+ workspace: config.workspace,
52
+ });
53
+ }
54
+ if (result) {
55
+ if (!result.ok) {
56
+ return NextResponse.json({ error: 'invalid_token', reason: result.reason }, { status: 401 });
57
+ }
58
+ // Bind the redirect to the token's signed `path` rather than the
59
+ // query string — prevents an attacker from mailing a valid token
60
+ // and pointing it at an unprotected internal route.
61
+ if (result.payload && result.payload.path) {
62
+ return enableAndRedirect(url, result.payload.path, studioOrigin);
63
+ }
64
+ }
65
+ // No verifier configured: dev mode. Loud-fail in production by
66
+ // checking NODE_ENV; the goal is "you can run locally without
67
+ // secrets" without accidentally shipping that to prod.
68
+ if (!result && process.env['NODE_ENV'] === 'production') {
69
+ return NextResponse.json({
70
+ error: 'preview_signing_not_configured',
71
+ message: 'estokadDraftMode.enable requires jwksUrl or previewSecret in production.',
72
+ }, { status: 500 });
73
+ }
74
+ return enableAndRedirect(url, path, studioOrigin);
75
+ };
76
+ },
77
+ /** POST handler that turns draft mode off. */
78
+ async disable() {
79
+ const draft = await draftMode();
80
+ draft.disable();
81
+ const cookieStore = await cookies();
82
+ cookieStore.delete('__estokad_studio_origin');
83
+ return NextResponse.json({ ok: true });
84
+ },
85
+ };
86
+ async function enableAndRedirect(url, path, studioOrigin) {
87
+ const draft = await draftMode();
88
+ draft.enable();
89
+ if (studioOrigin) {
90
+ const cookieStore = await cookies();
91
+ cookieStore.set('__estokad_studio_origin', studioOrigin, {
92
+ httpOnly: false, // overlay JS reads this client-side
93
+ sameSite: 'lax',
94
+ secure: url.protocol === 'https:',
95
+ path: '/',
96
+ maxAge: 60 * 30,
97
+ });
98
+ }
99
+ return NextResponse.redirect(new URL(path, url));
100
+ }
101
+ //# sourceMappingURL=draft.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft.js","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export { createClient } from '@estokad/sdk';
2
+ export type { Client, ClientOptions, ContentTypes, EntryEnvelope, EntryMeta, ListArgs, } from '@estokad/sdk';
3
+ export { EstokadError, NotFoundError, RateLimitError, UnauthorizedError, ValidationError, WrongRegionError, } from '@estokad/sdk';
4
+ export { DateField, Estokad, Field, Image, Num, RichText, Text } from './components.js';
5
+ export { EstokadProvider } from './provider.js';
6
+ export { renderRichText } from './rich-text.js';
7
+ export type { TiptapDoc, TiptapMark, TiptapNode } from './rich-text.js';
8
+ export { signDraftToken, verifyDraftToken, verifyDraftTokenJwks } from './token.js';
9
+ export type { DraftTokenPayload, VerifyResult } from './token.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,YAAY,EACV,MAAM,EACN,aAAa,EACb,YAAY,EACZ,aAAa,EACb,SAAS,EACT,QAAQ,GACT,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,gBAAgB,GACjB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACvF,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AACvE,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AACnF,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ // Public surface of @estokad/next.
2
+ //
3
+ // Components live under the Estokad namespace; consumers use them like:
4
+ // <Estokad.Text value={post.title} entryId={post.id} contentType="post" fieldName="title" as="h1" />
5
+ //
6
+ // The SDK's createClient is re-exported so consumers don't need to install
7
+ // @estokad/sdk separately.
8
+ export { createClient } from '@estokad/sdk';
9
+ export { EstokadError, NotFoundError, RateLimitError, UnauthorizedError, ValidationError, WrongRegionError, } from '@estokad/sdk';
10
+ export { DateField, Estokad, Field, Image, Num, RichText, Text } from './components.js';
11
+ export { EstokadProvider } from './provider.js';
12
+ export { renderRichText } from './rich-text.js';
13
+ export { signDraftToken, verifyDraftToken, verifyDraftTokenJwks } from './token.js';
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,EAAE;AACF,wEAAwE;AACxE,uGAAuG;AACvG,EAAE;AACF,2EAA2E;AAC3E,2BAA2B;AAE3B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAS3C,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,gBAAgB,GACjB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACvF,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE/C,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,2 @@
1
+ export declare function estokadOverlayRoute(): Promise<Response>;
2
+ //# sourceMappingURL=overlay-route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay-route.d.ts","sourceRoot":"","sources":["../src/overlay-route.ts"],"names":[],"mappings":"AAsBA,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,QAAQ,CAAC,CAoB7D"}
@@ -0,0 +1,38 @@
1
+ // Route handler that streams the visual-edit overlay JS bundle to the
2
+ // customer's site (M3.3 / M3.4). Mounted by the customer at e.g.
3
+ // `app/api/estokad/overlay/route.ts`:
4
+ //
5
+ // import { estokadOverlayRoute } from '@estokad/next/overlay-route'
6
+ // export const GET = estokadOverlayRoute
7
+ //
8
+ // Same-origin hosting avoids the script-src CSP dance. The overlay
9
+ // bundle is vendored into this package's own dist at build time
10
+ // (scripts/vendor-overlay.mjs copies apps/overlay's compiled output to
11
+ // dist/overlay.bundle.js), so there is no @estokad/overlay runtime
12
+ // dependency for the customer to install — @estokad/next is
13
+ // self-contained. We resolve the bundle relative to this compiled
14
+ // module via import.meta.url.
15
+ import { readFile } from 'node:fs/promises';
16
+ import { fileURLToPath } from 'node:url';
17
+ const overlayBundlePath = fileURLToPath(new URL('./overlay.bundle.js', import.meta.url));
18
+ let cached = null;
19
+ export async function estokadOverlayRoute() {
20
+ try {
21
+ if (!cached) {
22
+ cached = await readFile(overlayBundlePath, 'utf8');
23
+ }
24
+ return new Response(cached, {
25
+ status: 200,
26
+ headers: {
27
+ 'Content-Type': 'application/javascript; charset=utf-8',
28
+ // Customers can layer their own CDN cache on top; the script
29
+ // changes only when @estokad/next's version bumps.
30
+ 'Cache-Control': 'public, max-age=300, must-revalidate',
31
+ },
32
+ });
33
+ }
34
+ catch (err) {
35
+ return new Response(`// estokad overlay load failed: ${err instanceof Error ? err.message : 'unknown'}`, { status: 500, headers: { 'Content-Type': 'application/javascript' } });
36
+ }
37
+ }
38
+ //# sourceMappingURL=overlay-route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay-route.js","sourceRoot":"","sources":["../src/overlay-route.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,iEAAiE;AACjE,sCAAsC;AACtC,EAAE;AACF,sEAAsE;AACtE,2CAA2C;AAC3C,EAAE;AACF,mEAAmE;AACnE,gEAAgE;AAChE,uEAAuE;AACvE,mEAAmE;AACnE,4DAA4D;AAC5D,kEAAkE;AAClE,8BAA8B;AAE9B,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,MAAM,iBAAiB,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,qBAAqB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAExF,IAAI,MAAM,GAAkB,IAAI,CAAA;AAEhC,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,IAAI,CAAC;QACH,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,MAAM,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAA;QACpD,CAAC;QACD,OAAO,IAAI,QAAQ,CAAC,MAAM,EAAE;YAC1B,MAAM,EAAE,GAAG;YACX,OAAO,EAAE;gBACP,cAAc,EAAE,uCAAuC;gBACvD,6DAA6D;gBAC7D,mDAAmD;gBACnD,eAAe,EAAE,sCAAsC;aACxD;SACF,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,IAAI,QAAQ,CACjB,mCAAmC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE,EACnF,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,wBAAwB,EAAE,EAAE,CACvE,CAAA;IACH,CAAC;AACH,CAAC"}