@einblick/sdk 0.3.21 → 0.3.22

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/README.md CHANGED
@@ -4,8 +4,9 @@ Typed client and code generator for the Einblick public API.
4
4
 
5
5
  Framework support:
6
6
 
7
- - `@einblick/sdk`: framework-agnostic typed read client and code generator
8
- - `@einblick/sdk/react`: React-based in-page editing provider, wrappers, and login runtime
7
+ - `@einblick/sdk`: framework-agnostic typed read client, asset URL helpers, and code generator
8
+ - `@einblick/sdk/react`: React-based in-page editing provider, the `<EinblickImage>` asset component, and login runtime
9
+ - `@einblick/sdk/next`: Next.js App Router helpers — cache-tag builder, revalidation route handler, and the `<EinblickNextEditBoot>` provider
9
10
 
10
11
  The raw HTTP API is documented as OpenAPI 3.1 at `/api/v1/schema` and returns
11
12
  JSON:API documents for resource requests. The SDK normalizes that into ergonomic
@@ -294,6 +295,82 @@ and a single DOM selector can't represent an array of assets. The
294
295
  - **Omitting `router.refresh()` in `onSave`.** The server cache will be
295
296
  correct, but the user will stare at stale DOM until they navigate.
296
297
 
298
+ ## `@einblick/sdk/next` — App Router helpers
299
+
300
+ For Next.js App Router projects the `next` subpath ships drop-ins that
301
+ replace the cache-tag, revalidate-route, and edit-boot boilerplate every
302
+ einblick consumer used to copy by hand:
303
+
304
+ ```ts
305
+ // lib/einblick-cache.ts
306
+ import { createEinblickCmsTags } from '@einblick/sdk/next'
307
+
308
+ export const einblickTags = createEinblickCmsTags({
309
+ fanOut: { 'site-settings': ['gallery-slides'] },
310
+ })
311
+ ```
312
+
313
+ ```ts
314
+ // app/api/einblick/revalidate/route.ts
315
+ import { createEinblickRevalidateHandler } from '@einblick/sdk/next'
316
+ import { einblickTags } from '@/lib/einblick-cache'
317
+
318
+ export const POST = createEinblickRevalidateHandler({ tags: einblickTags })
319
+ ```
320
+
321
+ ```tsx
322
+ // app/layout.tsx
323
+ import { EinblickNextEditBoot } from '@einblick/sdk/next'
324
+
325
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
326
+ return (
327
+ <html><body>
328
+ <EinblickNextEditBoot>{children}</EinblickNextEditBoot>
329
+ </body></html>
330
+ )
331
+ }
332
+ ```
333
+
334
+ The boot reads `NEXT_PUBLIC_EINBLICK_SITE_KEY` automatically; on save it
335
+ posts `{ resourceSlug }` to `/api/einblick/revalidate` (configurable) and
336
+ calls `router.refresh()` for a soft re-render.
337
+
338
+ ## Asset helpers
339
+
340
+ The core package ships ergonomic image-CDN helpers so consumers don't
341
+ hand-roll URL composition. Common cases:
342
+
343
+ ```ts
344
+ import {
345
+ getEinblickAssetCdnUrl,
346
+ getEinblickAssetOgUrl,
347
+ getEinblickAssetPreviewUrl,
348
+ getEinblickAssetAlt,
349
+ isEinblickVideoAsset,
350
+ } from '@einblick/sdk'
351
+
352
+ const heroSrc = getEinblickAssetCdnUrl(asset, { width: 1800, format: 'webp', quality: 84 })
353
+ const ogSrc = getEinblickAssetOgUrl(asset) // 1200×630, cover, jpeg
354
+ const preview = getEinblickAssetPreviewUrl(asset) // long-edge 1800, webp
355
+ const alt = getEinblickAssetAlt(asset, 'Fallback alt text')
356
+ ```
357
+
358
+ For React consumers, drop-in `<EinblickImage>` from `@einblick/sdk/react`
359
+ renders the asset with sensible defaults:
360
+
361
+ ```tsx
362
+ import { EinblickImage } from '@einblick/sdk/react'
363
+
364
+ <EinblickImage
365
+ asset={tour.image_banner}
366
+ alt={getEinblickAssetAlt(tour.image_banner, tour.name)}
367
+ transform={{ width: 1600, format: 'webp', quality: 82 }}
368
+ />
369
+ ```
370
+
371
+ The component returns `null` when the asset is missing — no broken-image
372
+ placeholder. Videos are passed through untransformed.
373
+
297
374
  ## Raw API
298
375
 
299
376
  - `GET /api/v1/schema` returns the key-scoped OpenAPI 3.1 document
@@ -0,0 +1,39 @@
1
+ import { type EinblickAsset, type EinblickAssetTransform } from './client.js';
2
+ /** Whether the asset's MIME type indicates a video resource. */
3
+ export declare function isEinblickVideoAsset(asset: EinblickAsset | null | undefined): boolean;
4
+ /**
5
+ * Whether the asset is image-like. Returns true when the MIME type starts
6
+ * with `image/` or when no MIME type is set (default assumption).
7
+ */
8
+ export declare function isEinblickImageAsset(asset: EinblickAsset | null | undefined): boolean;
9
+ /**
10
+ * Pick a human-readable alt label for an asset. Prefers `description` over
11
+ * `title`, then falls back to the supplied default.
12
+ */
13
+ export declare function getEinblickAssetAlt(asset: EinblickAsset | null | undefined, fallback: string): string;
14
+ /**
15
+ * Higher-level transform helper: returns `''` (not `undefined`) when no
16
+ * asset, skips transforms for videos (CDN can't transcode them on the fly),
17
+ * and accepts sharp-style `inside`/`outside` fits by mapping them to
18
+ * `contain`. Wraps {@link getEinblickAssetUrl} from the client.
19
+ */
20
+ export declare function getEinblickAssetCdnUrl(asset: EinblickAsset | null | undefined, transform?: EinblickAssetTransform | string): string;
21
+ /**
22
+ * Build a 1200×630 cover-fit JPEG URL suitable for Open Graph / Twitter
23
+ * cards. Returns `''` when the asset is missing.
24
+ */
25
+ export declare function getEinblickAssetOgUrl(asset: EinblickAsset | null | undefined, options?: {
26
+ width?: number;
27
+ height?: number;
28
+ quality?: number;
29
+ }): string;
30
+ /**
31
+ * Build a long-edge-bounded WebP preview URL. Picks the longer edge based
32
+ * on the asset's intrinsic aspect ratio when available; defaults to width.
33
+ */
34
+ export declare function getEinblickAssetPreviewUrl(asset: EinblickAsset | null | undefined, options?: {
35
+ longEdge?: number;
36
+ quality?: number;
37
+ format?: EinblickAssetTransform['format'];
38
+ }): string;
39
+ //# sourceMappingURL=assets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../src/assets.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,sBAAsB,EAC5B,MAAM,aAAa,CAAA;AAEpB,gEAAgE;AAChE,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,GACtC,OAAO,CAET;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,GACtC,OAAO,CAET;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,EACvC,QAAQ,EAAE,MAAM,GACf,MAAM,CAGR;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,EACvC,SAAS,CAAC,EAAE,sBAAsB,GAAG,MAAM,GAC1C,MAAM,CAUR;AAoBD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,EACvC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAClE,MAAM,CAQR;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,EACvC,OAAO,GAAE;IACP,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,sBAAsB,CAAC,QAAQ,CAAC,CAAA;CACrC,GACL,MAAM,CAcR"}
package/dist/assets.js ADDED
@@ -0,0 +1,80 @@
1
+ import { getEinblickAssetUrl as baseGetEinblickAssetUrl, } from './client.js';
2
+ /** Whether the asset's MIME type indicates a video resource. */
3
+ export function isEinblickVideoAsset(asset) {
4
+ return asset?.mimeType?.startsWith('video/') ?? false;
5
+ }
6
+ /**
7
+ * Whether the asset is image-like. Returns true when the MIME type starts
8
+ * with `image/` or when no MIME type is set (default assumption).
9
+ */
10
+ export function isEinblickImageAsset(asset) {
11
+ return !asset?.mimeType || asset.mimeType.startsWith('image/');
12
+ }
13
+ /**
14
+ * Pick a human-readable alt label for an asset. Prefers `description` over
15
+ * `title`, then falls back to the supplied default.
16
+ */
17
+ export function getEinblickAssetAlt(asset, fallback) {
18
+ if (!asset)
19
+ return fallback;
20
+ return asset.description ?? asset.title ?? fallback;
21
+ }
22
+ /**
23
+ * Higher-level transform helper: returns `''` (not `undefined`) when no
24
+ * asset, skips transforms for videos (CDN can't transcode them on the fly),
25
+ * and accepts sharp-style `inside`/`outside` fits by mapping them to
26
+ * `contain`. Wraps {@link getEinblickAssetUrl} from the client.
27
+ */
28
+ export function getEinblickAssetCdnUrl(asset, transform) {
29
+ if (!asset?.url)
30
+ return '';
31
+ if (isEinblickVideoAsset(asset))
32
+ return asset.url;
33
+ const normalized = typeof transform === 'object' && transform !== null
34
+ ? normalizeTransform(transform)
35
+ : transform;
36
+ return baseGetEinblickAssetUrl(asset, normalized) ?? asset.url;
37
+ }
38
+ function normalizeTransform(transform) {
39
+ const next = { ...transform };
40
+ if (next.fit === 'inside' ||
41
+ next.fit === 'outside') {
42
+ next.fit = 'contain';
43
+ }
44
+ if (next.format === 'jpg') {
45
+ next.format = 'jpeg';
46
+ }
47
+ return next;
48
+ }
49
+ const OG_IMAGE_DEFAULT_WIDTH = 1200;
50
+ const OG_IMAGE_DEFAULT_HEIGHT = 630;
51
+ const PREVIEW_DEFAULT_LONG_EDGE = 1800;
52
+ /**
53
+ * Build a 1200×630 cover-fit JPEG URL suitable for Open Graph / Twitter
54
+ * cards. Returns `''` when the asset is missing.
55
+ */
56
+ export function getEinblickAssetOgUrl(asset, options = {}) {
57
+ return getEinblickAssetCdnUrl(asset, {
58
+ width: options.width ?? OG_IMAGE_DEFAULT_WIDTH,
59
+ height: options.height ?? OG_IMAGE_DEFAULT_HEIGHT,
60
+ fit: 'cover',
61
+ format: 'jpeg',
62
+ quality: options.quality ?? 82,
63
+ });
64
+ }
65
+ /**
66
+ * Build a long-edge-bounded WebP preview URL. Picks the longer edge based
67
+ * on the asset's intrinsic aspect ratio when available; defaults to width.
68
+ */
69
+ export function getEinblickAssetPreviewUrl(asset, options = {}) {
70
+ if (!asset)
71
+ return '';
72
+ const longEdge = options.longEdge ?? PREVIEW_DEFAULT_LONG_EDGE;
73
+ const quality = options.quality ?? 84;
74
+ const format = options.format ?? 'webp';
75
+ const isLandscape = asset.width && asset.height ? asset.width >= asset.height : true;
76
+ return getEinblickAssetCdnUrl(asset, isLandscape
77
+ ? { width: longEdge, quality, format }
78
+ : { height: longEdge, quality, format });
79
+ }
80
+ //# sourceMappingURL=assets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets.js","sourceRoot":"","sources":["../src/assets.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,IAAI,uBAAuB,GAG/C,MAAM,aAAa,CAAA;AAEpB,gEAAgE;AAChE,MAAM,UAAU,oBAAoB,CAClC,KAAuC;IAEvC,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAA;AACvD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAuC;IAEvC,OAAO,CAAC,KAAK,EAAE,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;AAChE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAuC,EACvC,QAAgB;IAEhB,IAAI,CAAC,KAAK;QAAE,OAAO,QAAQ,CAAA;IAC3B,OAAO,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,KAAK,IAAI,QAAQ,CAAA;AACrD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CACpC,KAAuC,EACvC,SAA2C;IAE3C,IAAI,CAAC,KAAK,EAAE,GAAG;QAAE,OAAO,EAAE,CAAA;IAC1B,IAAI,oBAAoB,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAA;IAEjD,MAAM,UAAU,GACd,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,IAAI;QACjD,CAAC,CAAC,kBAAkB,CAAC,SAAS,CAAC;QAC/B,CAAC,CAAC,SAAS,CAAA;IAEf,OAAO,uBAAuB,CAAC,KAAK,EAAE,UAAU,CAAC,IAAI,KAAK,CAAC,GAAG,CAAA;AAChE,CAAC;AAED,SAAS,kBAAkB,CACzB,SAAiC;IAEjC,MAAM,IAAI,GAA2B,EAAE,GAAG,SAAS,EAAE,CAAA;IACrD,IAAI,IAAI,CAAC,GAAG,KAAM,QAA0C;QACxD,IAAI,CAAC,GAAG,KAAM,SAA2C,EAAE,CAAC;QAC9D,IAAI,CAAC,GAAG,GAAG,SAAS,CAAA;IACtB,CAAC;IACD,IAAK,IAAI,CAAC,MAAiB,KAAK,KAAK,EAAE,CAAC;QACtC,IAAI,CAAC,MAAM,GAAG,MAA0C,CAAA;IAC1D,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,sBAAsB,GAAG,IAAI,CAAA;AACnC,MAAM,uBAAuB,GAAG,GAAG,CAAA;AACnC,MAAM,yBAAyB,GAAG,IAAI,CAAA;AAEtC;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAuC,EACvC,UAAiE,EAAE;IAEnE,OAAO,sBAAsB,CAAC,KAAK,EAAE;QACnC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,sBAAsB;QAC9C,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,uBAAuB;QACjD,GAAG,EAAE,OAAO;QACZ,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE;KAC/B,CAAC,CAAA;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CACxC,KAAuC,EACvC,UAII,EAAE;IAEN,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAA;IACrB,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,yBAAyB,CAAA;IAC9D,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAA;IACrC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,CAAA;IACvC,MAAM,WAAW,GACf,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;IAElE,OAAO,sBAAsB,CAC3B,KAAK,EACL,WAAW;QACT,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE;QACtC,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,CAC1C,CAAA;AACH,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './client.js';
2
+ export * from './assets.js';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './client.js';
2
+ export * from './assets.js';
2
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
@@ -0,0 +1,42 @@
1
+ export declare const EINBLICK_CMS_BASE_TAG = "einblick-cms";
2
+ export interface EinblickCmsTagsOptions {
3
+ /** Override the base tag. Useful when running multiple einblick clients in one app. */
4
+ baseTag?: string;
5
+ /**
6
+ * Map a resource slug to extra resource slugs whose tags should also be
7
+ * revalidated. Use to express "editing X invalidates Y" relationships,
8
+ * e.g. `{ 'site-settings': ['gallery-slides'] }`.
9
+ */
10
+ fanOut?: Record<string, readonly string[]>;
11
+ }
12
+ export interface EinblickCmsTags {
13
+ /** The base tag (`einblick-cms` by default) — applies to all CMS reads. */
14
+ readonly base: string;
15
+ /** Tag for a single resource slug (`einblick-cms:projects`). */
16
+ forResource(resourceSlug: string): string;
17
+ /** Tag for a single record under a resource (`einblick-cms:projects:my-slug`). */
18
+ forRecord(resourceSlug: string, recordSlug: string): string;
19
+ /**
20
+ * All tags that should fire for a write to `resourceSlug`. Includes the base
21
+ * tag, the resource tag, and any fan-out resources.
22
+ *
23
+ * Pass `undefined` for "any change" (returns just the base tag).
24
+ */
25
+ for(resourceSlug?: string | null): string[];
26
+ }
27
+ /**
28
+ * Build a Next.js cache-tag helper for an einblick-backed app. Every tag
29
+ * starts with the configured base (`einblick-cms` by default).
30
+ *
31
+ * ```ts
32
+ * const tags = createEinblickCmsTags({ fanOut: { 'site-settings': ['gallery-slides'] } })
33
+ *
34
+ * tags.for('site-settings')
35
+ * // => ['einblick-cms', 'einblick-cms:site-settings', 'einblick-cms:gallery-slides']
36
+ *
37
+ * tags.for(null)
38
+ * // => ['einblick-cms']
39
+ * ```
40
+ */
41
+ export declare function createEinblickCmsTags(options?: EinblickCmsTagsOptions): EinblickCmsTags;
42
+ //# sourceMappingURL=cache-tags.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-tags.d.ts","sourceRoot":"","sources":["../../src/next/cache-tags.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,iBAAiB,CAAA;AAEnD,MAAM,WAAW,sBAAsB;IACrC,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAA;CAC3C;AAED,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,gEAAgE;IAChE,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAA;IACzC,kFAAkF;IAClF,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAAA;IAC3D;;;;;OAKG;IACH,GAAG,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,EAAE,CAAA;CAC5C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,sBAA2B,GACnC,eAAe,CAsBjB"}
@@ -0,0 +1,37 @@
1
+ export const EINBLICK_CMS_BASE_TAG = 'einblick-cms';
2
+ /**
3
+ * Build a Next.js cache-tag helper for an einblick-backed app. Every tag
4
+ * starts with the configured base (`einblick-cms` by default).
5
+ *
6
+ * ```ts
7
+ * const tags = createEinblickCmsTags({ fanOut: { 'site-settings': ['gallery-slides'] } })
8
+ *
9
+ * tags.for('site-settings')
10
+ * // => ['einblick-cms', 'einblick-cms:site-settings', 'einblick-cms:gallery-slides']
11
+ *
12
+ * tags.for(null)
13
+ * // => ['einblick-cms']
14
+ * ```
15
+ */
16
+ export function createEinblickCmsTags(options = {}) {
17
+ const base = options.baseTag ?? EINBLICK_CMS_BASE_TAG;
18
+ const fanOut = options.fanOut ?? {};
19
+ const forResource = (resourceSlug) => `${base}:${resourceSlug}`;
20
+ const forRecord = (resourceSlug, recordSlug) => `${base}:${resourceSlug}:${recordSlug}`;
21
+ return {
22
+ base,
23
+ forResource,
24
+ forRecord,
25
+ for(resourceSlug) {
26
+ if (!resourceSlug)
27
+ return [base];
28
+ const fanned = fanOut[resourceSlug] ?? [];
29
+ const tags = [base, forResource(resourceSlug)];
30
+ for (const extra of fanned) {
31
+ tags.push(forResource(extra));
32
+ }
33
+ return tags;
34
+ },
35
+ };
36
+ }
37
+ //# sourceMappingURL=cache-tags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-tags.js","sourceRoot":"","sources":["../../src/next/cache-tags.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,qBAAqB,GAAG,cAAc,CAAA;AA6BnD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAkC,EAAE;IAEpC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,IAAI,qBAAqB,CAAA;IACrD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAA;IAEnC,MAAM,WAAW,GAAG,CAAC,YAAoB,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,YAAY,EAAE,CAAA;IACvE,MAAM,SAAS,GAAG,CAAC,YAAoB,EAAE,UAAkB,EAAE,EAAE,CAC7D,GAAG,IAAI,IAAI,YAAY,IAAI,UAAU,EAAE,CAAA;IAEzC,OAAO;QACL,IAAI;QACJ,WAAW;QACX,SAAS;QACT,GAAG,CAAC,YAAY;YACd,IAAI,CAAC,YAAY;gBAAE,OAAO,CAAC,IAAI,CAAC,CAAA;YAChC,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,CAAA;YACzC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC,CAAA;YAC9C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAA;YAC/B,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,49 @@
1
+ import * as React from 'react';
2
+ import type { EinblickEditProviderProps } from '../react/types.js';
3
+ export interface EinblickNextEditBootProps {
4
+ children: React.ReactNode;
5
+ /**
6
+ * The site key to mount the editor with. Falls back to
7
+ * `process.env.NEXT_PUBLIC_EINBLICK_SITE_KEY`. When neither is set, the
8
+ * component renders children without mounting the provider.
9
+ */
10
+ siteKey?: string;
11
+ /**
12
+ * Path of the revalidation route this site exposes. Defaults to
13
+ * `/api/einblick/revalidate`.
14
+ */
15
+ revalidatePath?: string;
16
+ /**
17
+ * Optional pre-revalidation hook. Called BEFORE the revalidate fetch and
18
+ * the soft refresh. If your handler throws, refresh still runs.
19
+ */
20
+ onSave?: EinblickEditProviderProps['onSave'];
21
+ /** Forwarded to `EinblickEditProvider`. */
22
+ locale?: EinblickEditProviderProps['locale'];
23
+ /** Forwarded to `EinblickEditProvider`. */
24
+ appOrigin?: EinblickEditProviderProps['appOrigin'];
25
+ }
26
+ /**
27
+ * Mount this once at the root of your Next.js App Router app to enable
28
+ * in-page editing. On every save it pings the configured revalidate route
29
+ * and triggers a soft refresh so server components re-render with fresh
30
+ * CMS data.
31
+ *
32
+ * ```tsx
33
+ * // app/layout.tsx
34
+ * import { EinblickNextEditBoot } from '@einblick/sdk/next'
35
+ *
36
+ * export default function RootLayout({ children }) {
37
+ * return (
38
+ * <html><body>
39
+ * <EinblickNextEditBoot>{children}</EinblickNextEditBoot>
40
+ * </body></html>
41
+ * )
42
+ * }
43
+ * ```
44
+ *
45
+ * Reads the site key from `NEXT_PUBLIC_EINBLICK_SITE_KEY` by default; when
46
+ * unset the component is a transparent passthrough.
47
+ */
48
+ export declare function EinblickNextEditBoot({ children, siteKey, revalidatePath, onSave, locale, appOrigin, }: EinblickNextEditBootProps): React.ReactElement;
49
+ //# sourceMappingURL=edit-boot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit-boot.d.ts","sourceRoot":"","sources":["../../src/next/edit-boot.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,OAAO,KAAK,EACV,yBAAyB,EAE1B,MAAM,mBAAmB,CAAA;AAE1B,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,MAAM,CAAC,EAAE,yBAAyB,CAAC,QAAQ,CAAC,CAAA;IAC5C,2CAA2C;IAC3C,MAAM,CAAC,EAAE,yBAAyB,CAAC,QAAQ,CAAC,CAAA;IAC5C,2CAA2C;IAC3C,SAAS,CAAC,EAAE,yBAAyB,CAAC,WAAW,CAAC,CAAA;CACnD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,OAAO,EACP,cAA2C,EAC3C,MAAM,EACN,MAAM,EACN,SAAS,GACV,EAAE,yBAAyB,GAAG,KAAK,CAAC,YAAY,CAiDhD"}
@@ -0,0 +1,61 @@
1
+ 'use client';
2
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ import { EinblickEditProvider } from '../react/provider.js';
6
+ /**
7
+ * Mount this once at the root of your Next.js App Router app to enable
8
+ * in-page editing. On every save it pings the configured revalidate route
9
+ * and triggers a soft refresh so server components re-render with fresh
10
+ * CMS data.
11
+ *
12
+ * ```tsx
13
+ * // app/layout.tsx
14
+ * import { EinblickNextEditBoot } from '@einblick/sdk/next'
15
+ *
16
+ * export default function RootLayout({ children }) {
17
+ * return (
18
+ * <html><body>
19
+ * <EinblickNextEditBoot>{children}</EinblickNextEditBoot>
20
+ * </body></html>
21
+ * )
22
+ * }
23
+ * ```
24
+ *
25
+ * Reads the site key from `NEXT_PUBLIC_EINBLICK_SITE_KEY` by default; when
26
+ * unset the component is a transparent passthrough.
27
+ */
28
+ export function EinblickNextEditBoot({ children, siteKey, revalidatePath = '/api/einblick/revalidate', onSave, locale, appOrigin, }) {
29
+ const resolvedSiteKey = siteKey ??
30
+ (typeof process !== 'undefined'
31
+ ? process.env.NEXT_PUBLIC_EINBLICK_SITE_KEY
32
+ : undefined);
33
+ const router = useRouter();
34
+ const handleSave = React.useCallback(async (event) => {
35
+ try {
36
+ await onSave?.(event);
37
+ }
38
+ catch {
39
+ // Don't let user-supplied onSave block the cache refresh.
40
+ }
41
+ try {
42
+ await fetch(revalidatePath, {
43
+ method: 'POST',
44
+ headers: { 'content-type': 'application/json' },
45
+ body: JSON.stringify({
46
+ resourceSlug: event.binding.resourceSlug,
47
+ }),
48
+ cache: 'no-store',
49
+ });
50
+ }
51
+ catch {
52
+ // Preserve optimistic edit state even if revalidation fails.
53
+ }
54
+ router.refresh();
55
+ }, [onSave, revalidatePath, router]);
56
+ if (!resolvedSiteKey) {
57
+ return _jsx(_Fragment, { children: children });
58
+ }
59
+ return (_jsx(EinblickEditProvider, { siteKey: resolvedSiteKey, onSave: handleSave, ...(locale ? { locale } : {}), ...(appOrigin ? { appOrigin } : {}), children: children }));
60
+ }
61
+ //# sourceMappingURL=edit-boot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit-boot.js","sourceRoot":"","sources":["../../src/next/edit-boot.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AA8B3D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,oBAAoB,CAAC,EACnC,QAAQ,EACR,OAAO,EACP,cAAc,GAAG,0BAA0B,EAC3C,MAAM,EACN,MAAM,EACN,SAAS,GACiB;IAC1B,MAAM,eAAe,GACnB,OAAO;QACP,CAAC,OAAO,OAAO,KAAK,WAAW;YAC7B,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,6BAA6B;YAC3C,CAAC,CAAC,SAAS,CAAC,CAAA;IAEhB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAE1B,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,CAClC,KAAK,EAAE,KAA4B,EAAE,EAAE;QACrC,IAAI,CAAC;YACH,MAAM,MAAM,EAAE,CAAC,KAAK,CAAC,CAAA;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,0DAA0D;QAC5D,CAAC;QAED,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,cAAc,EAAE;gBAC1B,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,YAAY;iBACzC,CAAC;gBACF,KAAK,EAAE,UAAU;aAClB,CAAC,CAAA;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,6DAA6D;QAC/D,CAAC;QAED,MAAM,CAAC,OAAO,EAAE,CAAA;IAClB,CAAC,EACD,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,CACjC,CAAA;IAED,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,OAAO,4BAAG,QAAQ,GAAI,CAAA;IACxB,CAAC;IAED,OAAO,CACL,KAAC,oBAAoB,IACnB,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,UAAU,KACd,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAC1B,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YAEnC,QAAQ,GACY,CACxB,CAAA;AACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { createEinblickCmsTags, EINBLICK_CMS_BASE_TAG, } from './cache-tags.js';
2
+ export type { EinblickCmsTags, EinblickCmsTagsOptions, } from './cache-tags.js';
3
+ export { createEinblickRevalidateHandler } from './revalidate.js';
4
+ export type { CreateEinblickRevalidateHandlerOptions, EinblickRevalidateProfile, } from './revalidate.js';
5
+ export { EinblickNextEditBoot } from './edit-boot.js';
6
+ export type { EinblickNextEditBootProps } from './edit-boot.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,iBAAiB,CAAA;AACxB,YAAY,EACV,eAAe,EACf,sBAAsB,GACvB,MAAM,iBAAiB,CAAA;AAExB,OAAO,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAA;AACjE,YAAY,EACV,sCAAsC,EACtC,yBAAyB,GAC1B,MAAM,iBAAiB,CAAA;AAExB,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AACrD,YAAY,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,4 @@
1
+ export { createEinblickCmsTags, EINBLICK_CMS_BASE_TAG, } from './cache-tags.js';
2
+ export { createEinblickRevalidateHandler } from './revalidate.js';
3
+ export { EinblickNextEditBoot } from './edit-boot.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,iBAAiB,CAAA;AAMxB,OAAO,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAA;AAMjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,42 @@
1
+ import type { EinblickCmsTags } from './cache-tags.js';
2
+ /**
3
+ * Optional revalidation lifetime accepted by Next.js >= 16's `revalidateTag`
4
+ * second argument. Older Next.js ignores this.
5
+ */
6
+ export type EinblickRevalidateProfile = 'max' | 'default';
7
+ interface RevalidateTagFunction {
8
+ (tag: string): void;
9
+ (tag: string, profile: EinblickRevalidateProfile): void;
10
+ }
11
+ export interface CreateEinblickRevalidateHandlerOptions {
12
+ /** The tag helper to fire. Falls back to a base-only helper if omitted. */
13
+ tags?: EinblickCmsTags;
14
+ /**
15
+ * Inject your `revalidateTag` from `next/cache`. Defaulted at runtime via
16
+ * a dynamic import so this module is import-safe outside of Next.
17
+ */
18
+ revalidateTag?: RevalidateTagFunction;
19
+ /** Forwarded as the second arg to Next.js >= 16's `revalidateTag`. */
20
+ profile?: EinblickRevalidateProfile;
21
+ /**
22
+ * Optional secret. When set, requests must carry `x-einblick-revalidate-secret`
23
+ * matching this value (or `?secret=...`); otherwise the handler 401s.
24
+ */
25
+ secret?: string;
26
+ }
27
+ /**
28
+ * Drop-in `app/api/einblick/revalidate/route.ts` POST handler. Reads the
29
+ * incoming `{ resourceSlug }` body and revalidates every cache tag the
30
+ * configured helper returns.
31
+ *
32
+ * ```ts
33
+ * import { createEinblickRevalidateHandler, createEinblickCmsTags } from '@einblick/sdk/next'
34
+ *
35
+ * export const POST = createEinblickRevalidateHandler({
36
+ * tags: createEinblickCmsTags(),
37
+ * })
38
+ * ```
39
+ */
40
+ export declare function createEinblickRevalidateHandler(options?: CreateEinblickRevalidateHandlerOptions): (request: Request) => Promise<Response>;
41
+ export {};
42
+ //# sourceMappingURL=revalidate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revalidate.d.ts","sourceRoot":"","sources":["../../src/next/revalidate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEtD;;;GAGG;AACH,MAAM,MAAM,yBAAyB,GAAG,KAAK,GAAG,SAAS,CAAA;AAEzD,UAAU,qBAAqB;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,yBAAyB,GAAG,IAAI,CAAA;CACxD;AAED,MAAM,WAAW,sCAAsC;IACrD,2EAA2E;IAC3E,IAAI,CAAC,EAAE,eAAe,CAAA;IACtB;;;OAGG;IACH,aAAa,CAAC,EAAE,qBAAqB,CAAA;IACrC,sEAAsE;IACtE,OAAO,CAAC,EAAE,yBAAyB,CAAA;IACnC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAkBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,sCAA2C,GACnD,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA6CzC"}
@@ -0,0 +1,66 @@
1
+ async function loadDefaultRevalidateTag() {
2
+ const mod = (await import('next/cache'));
3
+ return mod.revalidateTag;
4
+ }
5
+ function timingSafeEqual(left, right) {
6
+ if (left.length !== right.length)
7
+ return false;
8
+ let mismatch = 0;
9
+ for (let index = 0; index < left.length; index += 1) {
10
+ mismatch |= left.charCodeAt(index) ^ right.charCodeAt(index);
11
+ }
12
+ return mismatch === 0;
13
+ }
14
+ /**
15
+ * Drop-in `app/api/einblick/revalidate/route.ts` POST handler. Reads the
16
+ * incoming `{ resourceSlug }` body and revalidates every cache tag the
17
+ * configured helper returns.
18
+ *
19
+ * ```ts
20
+ * import { createEinblickRevalidateHandler, createEinblickCmsTags } from '@einblick/sdk/next'
21
+ *
22
+ * export const POST = createEinblickRevalidateHandler({
23
+ * tags: createEinblickCmsTags(),
24
+ * })
25
+ * ```
26
+ */
27
+ export function createEinblickRevalidateHandler(options = {}) {
28
+ const tags = options.tags;
29
+ const profile = options.profile;
30
+ return async function einblickRevalidateHandler(request) {
31
+ if (options.secret) {
32
+ const url = new URL(request.url);
33
+ const provided = request.headers.get('x-einblick-revalidate-secret') ??
34
+ url.searchParams.get('secret') ??
35
+ '';
36
+ if (!timingSafeEqual(provided, options.secret)) {
37
+ return new Response(JSON.stringify({ ok: false, error: 'unauthorized' }), {
38
+ status: 401,
39
+ headers: { 'content-type': 'application/json' },
40
+ });
41
+ }
42
+ }
43
+ let resourceSlug;
44
+ try {
45
+ const body = (await request.json());
46
+ if (body && typeof body.resourceSlug === 'string') {
47
+ resourceSlug = body.resourceSlug;
48
+ }
49
+ }
50
+ catch {
51
+ // Empty / invalid body — fall through to "all" revalidation.
52
+ }
53
+ const revalidate = options.revalidateTag ?? (await loadDefaultRevalidateTag());
54
+ const fired = tags
55
+ ? tags.for(resourceSlug)
56
+ : ['einblick-cms'];
57
+ for (const tag of fired) {
58
+ if (profile)
59
+ revalidate(tag, profile);
60
+ else
61
+ revalidate(tag);
62
+ }
63
+ return new Response(JSON.stringify({ ok: true, tags: fired }), { headers: { 'content-type': 'application/json' } });
64
+ };
65
+ }
66
+ //# sourceMappingURL=revalidate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revalidate.js","sourceRoot":"","sources":["../../src/next/revalidate.ts"],"names":[],"mappings":"AA8BA,KAAK,UAAU,wBAAwB;IACrC,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,YAAY,CAAC,CAEtC,CAAA;IACD,OAAO,GAAG,CAAC,aAAa,CAAA;AAC1B,CAAC;AAED,SAAS,eAAe,CAAC,IAAY,EAAE,KAAa;IAClD,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAC9C,IAAI,QAAQ,GAAG,CAAC,CAAA;IAChB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACpD,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;IAC9D,CAAC;IACD,OAAO,QAAQ,KAAK,CAAC,CAAA;AACvB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,+BAA+B,CAC7C,UAAkD,EAAE;IAEpD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAA;IACzB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAA;IAE/B,OAAO,KAAK,UAAU,yBAAyB,CAAC,OAAO;QACrD,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YAChC,MAAM,QAAQ,GACZ,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC;gBACnD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAC9B,EAAE,CAAA;YACJ,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC/C,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,EAAE;oBACxE,MAAM,EAAE,GAAG;oBACX,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;iBAChD,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;QAED,IAAI,YAAgC,CAAA;QACpC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,MAAM,OAAO,CAAC,IAAI,EAAE,CAAqC,CAAA;YACvE,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;gBAClD,YAAY,GAAG,IAAI,CAAC,YAAY,CAAA;YAClC,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,6DAA6D;QAC/D,CAAC;QAED,MAAM,UAAU,GACd,OAAO,CAAC,aAAa,IAAI,CAAC,MAAM,wBAAwB,EAAE,CAAC,CAAA;QAC7D,MAAM,KAAK,GAAG,IAAI;YAChB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC;YACxB,CAAC,CAAC,CAAC,cAAc,CAAC,CAAA;QAEpB,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;YACxB,IAAI,OAAO;gBAAE,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;;gBAChC,UAAU,CAAC,GAAG,CAAC,CAAA;QACtB,CAAC;QAED,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EACzC,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CACpD,CAAA;IACH,CAAC,CAAA;AACH,CAAC"}
@@ -0,0 +1,29 @@
1
+ import * as React from 'react';
2
+ import type { EinblickAsset, EinblickAssetTransform } from '../client.js';
3
+ export interface EinblickImageProps extends Omit<React.ComponentPropsWithoutRef<'img'>, 'src' | 'alt'> {
4
+ /** The CMS asset to render. When `null`/`undefined` the component renders nothing. */
5
+ asset: EinblickAsset | null | undefined;
6
+ /** Required alt text. Pass an empty string for decorative images. */
7
+ alt: string;
8
+ /**
9
+ * Image-CDN transform query params (width/height/quality/format/fit), or
10
+ * a server-defined preset name. When omitted the asset's plain URL is
11
+ * used.
12
+ */
13
+ transform?: EinblickAssetTransform | string;
14
+ /** Show a pulsing skeleton until the image's `onLoad` fires. */
15
+ useSkeleton?: boolean;
16
+ classNames?: {
17
+ image?: string;
18
+ blur?: string;
19
+ };
20
+ }
21
+ /**
22
+ * Native `<img>` rendered from an Einblick CMS asset. Accepts an optional
23
+ * `transform` (preset name or transform object) that's appended to the
24
+ * asset URL as image-CDN query params.
25
+ *
26
+ * Returns `null` when the asset is missing — no broken-image placeholder.
27
+ */
28
+ export declare const EinblickImage: React.ForwardRefExoticComponent<EinblickImageProps & React.RefAttributes<HTMLImageElement>>;
29
+ //# sourceMappingURL=image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/react/image.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,KAAK,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAEzE,MAAM,WAAW,kBACf,SAAQ,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAClE,sFAAsF;IACtF,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,CAAA;IACvC,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAA;IACX;;;;OAIG;IACH,SAAS,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAAA;IAC3C,gEAAgE;IAChE,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,UAAU,CAAC,EAAE;QACX,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAMD;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,6FA+DxB,CAAA"}
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { getEinblickAssetCdnUrl } from '../assets.js';
4
+ function classes(...values) {
5
+ return values.filter(Boolean).join(' ');
6
+ }
7
+ /**
8
+ * Native `<img>` rendered from an Einblick CMS asset. Accepts an optional
9
+ * `transform` (preset name or transform object) that's appended to the
10
+ * asset URL as image-CDN query params.
11
+ *
12
+ * Returns `null` when the asset is missing — no broken-image placeholder.
13
+ */
14
+ export const EinblickImage = React.forwardRef(function EinblickImage({ asset, alt, width, height, transform, useSkeleton = false, className, classNames, onLoad, ...rest }, ref) {
15
+ const [status, setStatus] = React.useState(useSkeleton ? 'loading' : 'complete');
16
+ if (!asset?.url)
17
+ return null;
18
+ const resolvedTransform = typeof transform === 'string'
19
+ ? transform
20
+ : {
21
+ ...(typeof width === 'number' && transform?.width === undefined
22
+ ? { width }
23
+ : {}),
24
+ ...(typeof height === 'number' && transform?.height === undefined
25
+ ? { height }
26
+ : {}),
27
+ ...transform,
28
+ };
29
+ const src = resolvedTransform
30
+ ? getEinblickAssetCdnUrl(asset, resolvedTransform)
31
+ : asset.url;
32
+ return (_jsx("img", { ref: ref, src: src, alt: alt, width: width ?? asset.width, height: height ?? asset.height, loading: rest.loading ?? 'lazy', decoding: "async", className: classes(classNames?.image, status === 'loading' &&
33
+ ['animate-pulse', classNames?.blur].filter(Boolean).join(' '), className), onLoad: (event) => {
34
+ setStatus('complete');
35
+ onLoad?.(event);
36
+ }, ...rest }));
37
+ });
38
+ //# sourceMappingURL=image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/react/image.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAuBrD,SAAS,OAAO,CAAC,GAAG,MAAyC;IAC3D,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACzC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC,UAAU,CAG3C,SAAS,aAAa,CACtB,EACE,KAAK,EACL,GAAG,EACH,KAAK,EACL,MAAM,EACN,SAAS,EACT,WAAW,GAAG,KAAK,EACnB,SAAS,EACT,UAAU,EACV,MAAM,EACN,GAAG,IAAI,EACR,EACD,GAAG;IAEH,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC,QAAQ,CACxC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CACrC,CAAA;IAED,IAAI,CAAC,KAAK,EAAE,GAAG;QAAE,OAAO,IAAI,CAAA;IAE5B,MAAM,iBAAiB,GACrB,OAAO,SAAS,KAAK,QAAQ;QAC3B,CAAC,CAAC,SAAS;QACX,CAAC,CAAC;YACE,GAAG,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,SAAS,EAAE,KAAK,KAAK,SAAS;gBAC7D,CAAC,CAAC,EAAE,KAAK,EAAE;gBACX,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,OAAO,MAAM,KAAK,QAAQ,IAAI,SAAS,EAAE,MAAM,KAAK,SAAS;gBAC/D,CAAC,CAAC,EAAE,MAAM,EAAE;gBACZ,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,SAAS;SACb,CAAA;IAEP,MAAM,GAAG,GAAG,iBAAiB;QAC3B,CAAC,CAAC,sBAAsB,CAAC,KAAK,EAAE,iBAAiB,CAAC;QAClD,CAAC,CAAC,KAAK,CAAC,GAAG,CAAA;IAEb,OAAO,CACL,cACE,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,KAAK,IAAI,KAAK,CAAC,KAAK,EAC3B,MAAM,EAAE,MAAM,IAAI,KAAK,CAAC,MAAM,EAC9B,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,MAAM,EAC/B,QAAQ,EAAC,OAAO,EAChB,SAAS,EAAE,OAAO,CAChB,UAAU,EAAE,KAAK,EACjB,MAAM,KAAK,SAAS;YAClB,CAAC,eAAe,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAC/D,SAAS,CACV,EACD,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;YAChB,SAAS,CAAC,UAAU,CAAC,CAAA;YACrB,MAAM,EAAE,CAAC,KAAK,CAAC,CAAA;QACjB,CAAC,KACG,IAAI,GACR,CACH,CAAA;AACH,CAAC,CAAC,CAAA"}
@@ -1,4 +1,6 @@
1
1
  export { EditableImage, EditableRegion, EditableText, } from './markers.js';
2
+ export { EinblickImage } from './image.js';
3
+ export type { EinblickImageProps } from './image.js';
2
4
  export { EinblickEditProvider, EinblickLoginButton, useEinblickEdit, } from './provider.js';
3
5
  export { createEditableBinding, DEFAULT_EINBLICK_APP_ORIGIN, } from './types.js';
4
6
  export type { CreateEinblickEditableBindingInput, EinblickEditableBinding, EinblickEditableDisplayMode, EinblickEditableFieldType, EinblickEditableKind, EinblickEditProviderProps, EinblickEditSaveEvent, EinblickEditState, EinblickEditStatus, EinblickLoginButtonProps, } from './types.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,cAAc,EACd,YAAY,GACb,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,GAChB,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,qBAAqB,EACrB,2BAA2B,GAC5B,MAAM,YAAY,CAAA;AACnB,YAAY,EACV,kCAAkC,EAClC,uBAAuB,EACvB,2BAA2B,EAC3B,yBAAyB,EACzB,oBAAoB,EACpB,yBAAyB,EACzB,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,GACzB,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,cAAc,EACd,YAAY,GACb,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC1C,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,GAChB,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,qBAAqB,EACrB,2BAA2B,GAC5B,MAAM,YAAY,CAAA;AACnB,YAAY,EACV,kCAAkC,EAClC,uBAAuB,EACvB,2BAA2B,EAC3B,yBAAyB,EACzB,oBAAoB,EACpB,yBAAyB,EACzB,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,GACzB,MAAM,YAAY,CAAA"}
@@ -1,4 +1,5 @@
1
1
  export { EditableImage, EditableRegion, EditableText, } from './markers.js';
2
+ export { EinblickImage } from './image.js';
2
3
  export { EinblickEditProvider, EinblickLoginButton, useEinblickEdit, } from './provider.js';
3
4
  export { createEditableBinding, DEFAULT_EINBLICK_APP_ORIGIN, } from './types.js';
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,cAAc,EACd,YAAY,GACb,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,GAChB,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,qBAAqB,EACrB,2BAA2B,GAC5B,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,cAAc,EACd,YAAY,GACb,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,GAChB,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,qBAAqB,EACrB,2BAA2B,GAC5B,MAAM,YAAY,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@einblick/sdk",
3
- "version": "0.3.21",
3
+ "version": "0.3.22",
4
4
  "description": "Typed client and code generator for the Einblick public API.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -15,6 +15,10 @@
15
15
  "import": "./dist/react/index.js",
16
16
  "types": "./dist/react/index.d.ts"
17
17
  },
18
+ "./next": {
19
+ "import": "./dist/next/index.js",
20
+ "types": "./dist/next/index.d.ts"
21
+ },
18
22
  "./codegen": {
19
23
  "import": "./dist/codegen.js",
20
24
  "types": "./dist/codegen.d.ts"
@@ -39,9 +43,15 @@
39
43
  "node": ">=24.0.0"
40
44
  },
41
45
  "peerDependencies": {
46
+ "next": ">=14.0.0",
42
47
  "react": ">=18.2.0",
43
48
  "react-dom": ">=18.2.0"
44
49
  },
50
+ "peerDependenciesMeta": {
51
+ "next": {
52
+ "optional": true
53
+ }
54
+ },
45
55
  "dependencies": {
46
56
  "dotenv": "^17.2.3"
47
57
  },
@@ -49,6 +59,7 @@
49
59
  "@types/node": "^25.1.0",
50
60
  "@types/react": "^19.2.2",
51
61
  "@types/react-dom": "^19.2.2",
62
+ "next": "^16.0.10",
52
63
  "react": "^19.2.0",
53
64
  "react-dom": "^19.2.0",
54
65
  "typescript": "^5.9.3"