@foir/sdk 0.4.0 → 0.7.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 (82) hide show
  1. package/CHANGELOG.md +325 -0
  2. package/README.md +63 -0
  3. package/dist/auth/auth.d.ts +140 -0
  4. package/dist/auth/hosted.d.ts +40 -0
  5. package/dist/auth/index.d.ts +15 -0
  6. package/dist/auth/operations.d.ts +123 -0
  7. package/dist/auth/session-client.d.ts +76 -0
  8. package/dist/auth-session.js +137 -0
  9. package/dist/auth.js +238 -26
  10. package/dist/client/platform-client.d.ts +29 -0
  11. package/dist/client.js +290 -32
  12. package/dist/codegen/block-registry.d.ts +4 -8
  13. package/dist/codegen/block-scaffold.d.ts +4 -8
  14. package/dist/codegen/index.d.ts +8 -62
  15. package/dist/codegen/preset.d.ts +60 -0
  16. package/dist/codegen/shared.d.ts +25 -0
  17. package/dist/components/EditorProvider.d.ts +36 -0
  18. package/dist/content/content-renderer.d.ts +47 -0
  19. package/dist/content/index.d.ts +12 -0
  20. package/dist/content/richtext.d.ts +70 -0
  21. package/dist/data/builtins.d.ts +296 -0
  22. package/dist/data/client.d.ts +94 -0
  23. package/dist/data/index.d.ts +54 -0
  24. package/dist/data/manifest.d.ts +161 -0
  25. package/dist/data/schema.d.ts +243 -0
  26. package/dist/data/selection.d.ts +330 -0
  27. package/dist/data/transport.d.ts +107 -0
  28. package/dist/data.js +309 -0
  29. package/dist/graphql/cdn-route.d.ts +42 -0
  30. package/dist/graphql/client.d.ts +30 -0
  31. package/dist/graphql/content-client.d.ts +66 -0
  32. package/dist/graphql/foir-request.d.ts +47 -0
  33. package/dist/hooks/useAutoResize.d.ts +22 -0
  34. package/dist/hooks/useEditor.d.ts +16 -0
  35. package/dist/index.d.ts +10 -394
  36. package/dist/media/BlurhashCanvas.d.ts +10 -0
  37. package/dist/media/ProgressiveMedia.d.ts +56 -0
  38. package/dist/media/index.d.ts +14 -0
  39. package/dist/media/media.d.ts +139 -0
  40. package/dist/media/useProgressiveLoad.d.ts +23 -0
  41. package/dist/media.js +5 -0
  42. package/dist/next/cookieAdapter.d.ts +26 -0
  43. package/dist/next/index.d.ts +15 -0
  44. package/dist/next/middleware.d.ts +49 -0
  45. package/dist/next/session.d.ts +53 -0
  46. package/dist/next.js +230 -26
  47. package/dist/protocol/constants.d.ts +6 -0
  48. package/dist/protocol/messages.d.ts +155 -0
  49. package/dist/protocol/validation.d.ts +8 -0
  50. package/dist/push/index.d.ts +14 -0
  51. package/dist/push/operations.d.ts +22 -0
  52. package/dist/push/web-push.d.ts +39 -0
  53. package/dist/push.js +230 -26
  54. package/dist/react/FoirProvider.d.ts +116 -0
  55. package/dist/react/OAuthButtons.d.ts +20 -0
  56. package/dist/react/SignIn.d.ts +22 -0
  57. package/dist/react/UserButton.d.ts +13 -0
  58. package/dist/react/index.d.ts +19 -0
  59. package/dist/react/localStorageAdapter.d.ts +10 -0
  60. package/dist/react.js +230 -26
  61. package/dist/{realtime.d.ts → realtime/index.d.ts} +35 -16
  62. package/dist/server/callback-client.d.ts +92 -0
  63. package/dist/server/index.d.ts +8 -0
  64. package/dist/server/operation-types.d.ts +134 -0
  65. package/dist/server/require-scoped-token.d.ts +76 -0
  66. package/dist/server/server-client.d.ts +34 -0
  67. package/dist/server/types.d.ts +54 -0
  68. package/dist/server/verify-scoped-token.d.ts +100 -0
  69. package/dist/server/webhook-helpers.d.ts +36 -0
  70. package/dist/transport/foir-http.d.ts +217 -0
  71. package/dist/transport/retry.d.ts +61 -0
  72. package/dist/types/index.d.ts +124 -0
  73. package/package.json +23 -13
  74. package/dist/auth.d.ts +0 -382
  75. package/dist/client.d.ts +0 -83
  76. package/dist/content.d.ts +0 -96
  77. package/dist/media.d.ts +0 -234
  78. package/dist/next.d.ts +0 -178
  79. package/dist/push.d.ts +0 -109
  80. package/dist/react.d.ts +0 -319
  81. package/dist/server.d.ts +0 -534
  82. package/dist/shared-DkWlEd2Z.d.ts +0 -18
package/dist/index.d.ts CHANGED
@@ -1,394 +1,10 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode } from 'react';
3
-
4
- /**
5
- * Public type definitions for the Editor SDK.
6
- */
7
-
8
- /** Data provided by the host when the editor initializes. */
9
- interface EditorInit {
10
- values: Record<string, unknown>;
11
- metadata?: Record<string, unknown>;
12
- modelKey: string;
13
- recordId: string;
14
- naturalKey?: string;
15
- versionId: string | null;
16
- variantKey: string | null;
17
- locale: string | null;
18
- schema: unknown;
19
- mode: 'edit' | 'readonly';
20
- theme: 'light' | 'dark';
21
- /** Secret API key for public API access. */
22
- apiKey: string;
23
- /** Public API GraphQL endpoint URL (provided by host). */
24
- apiUrl: string;
25
- /** Config connection domain (e.g. my-store.myshopify.com) */
26
- connectionDomain?: string;
27
- /** How this editor is rendered in the admin UI. */
28
- configMode?: 'editor' | 'sidebar';
29
- /** Currently selected field keys (sidebar mode). */
30
- selectedFieldKeys?: string[];
31
- /** Available fields for selection (sidebar mode). */
32
- selectableFields?: Array<{
33
- key: string;
34
- label: string;
35
- type: string;
36
- }>;
37
- /** Locale context (sidebar mode). */
38
- localeContext?: LocaleContext | null;
39
- }
40
- /** Locale context passed by the host for sidebar editors. */
41
- interface LocaleContext {
42
- currentLocale: string;
43
- sourceLocale: string;
44
- isTranslating: boolean;
45
- locales: Array<{
46
- code: string;
47
- displayName: string;
48
- isDefault: boolean;
49
- }>;
50
- }
51
- /** Value exposed by the EditorProvider context. */
52
- interface EditorContextValue {
53
- /** Whether the editor has received init data from the host. */
54
- isReady: boolean;
55
- /** Initial data from the host (available after init). */
56
- init: EditorInit | null;
57
- /** Current host theme. */
58
- theme: 'light' | 'dark';
59
- /** Platform GraphQL client (available after init). */
60
- client: PlatformClient;
61
- /** Send complete form values to the host (full replacement). */
62
- updateValues: (values: Record<string, unknown>) => void;
63
- /** Send a single field update to the host. */
64
- updateField: (path: string, value: unknown) => void;
65
- /** Request the host to save the current values. */
66
- requestSave: () => void;
67
- /** Request the host to publish the current version. */
68
- requestPublish: () => void;
69
- /** Report iframe height for auto-resize. */
70
- resize: (height: number) => void;
71
- /** Report dirty state (unsaved changes). */
72
- setDirty: (isDirty: boolean) => void;
73
- /** Request the host to navigate to an admin path. */
74
- requestNavigate: (path: string) => void;
75
- /** Request the host to execute a platform operation. Returns the result.
76
- * `mode` controls whether the platform waits for the operation to complete
77
- * ('sync') or returns immediately after queueing ('async'). Defaults to 'sync'. */
78
- requestOperation: (operationKey: string, input?: Record<string, unknown>, options?: {
79
- mode?: 'sync' | 'async';
80
- }) => Promise<{
81
- success: boolean;
82
- result?: unknown;
83
- error?: {
84
- code: string;
85
- message: string;
86
- };
87
- }>;
88
- /** Editor mode ('editor' or 'sidebar'). */
89
- configMode: 'editor' | 'sidebar';
90
- /** Currently selected field keys (sidebar mode). */
91
- selectedFieldKeys: string[];
92
- /** Available fields for selection (sidebar mode). */
93
- selectableFields: Array<{
94
- key: string;
95
- label: string;
96
- type: string;
97
- }>;
98
- /** Locale context (sidebar mode). */
99
- localeContext: LocaleContext | null;
100
- }
101
- /** Options for creating a PlatformClient. */
102
- interface PlatformClientOptions {
103
- /** GraphQL endpoint URL. */
104
- apiUrl: string;
105
- /** API key for authentication. */
106
- apiKey: string;
107
- }
108
- /** A minimal GraphQL error shape. */
109
- interface GraphQLError {
110
- message: string;
111
- locations?: Array<{
112
- line: number;
113
- column: number;
114
- }>;
115
- path?: Array<string | number>;
116
- extensions?: Record<string, unknown>;
117
- }
118
- /** Response shape from a GraphQL query/mutation. */
119
- interface GraphQLResponse<T = Record<string, unknown>> {
120
- data: T | null;
121
- errors?: GraphQLError[];
122
- }
123
- /** Fetch-based GraphQL client for platform API calls. */
124
- interface PlatformClient {
125
- /** Execute a GraphQL query or mutation. */
126
- query<T = Record<string, unknown>>(query: string, variables?: Record<string, unknown>): Promise<GraphQLResponse<T>>;
127
- }
128
-
129
- interface EditorProviderProps {
130
- children: ReactNode;
131
- /**
132
- * Parent window origin for postMessage validation.
133
- * Must be an explicit origin (e.g., 'https://admin.example.com').
134
- * Falls back to VITE_PARENT_ORIGIN or NEXT_PUBLIC_PARENT_ORIGIN env var.
135
- */
136
- parentOrigin?: string;
137
- /**
138
- * Platform API URL for GraphQL requests.
139
- * Falls back to VITE_API_URL or NEXT_PUBLIC_API_URL env var.
140
- */
141
- apiUrl?: string;
142
- onInit?: (init: EditorInit) => void;
143
- onValuesChanged?: (values: Record<string, unknown>) => void;
144
- onVariantChanged?: (variantKey: string, values: Record<string, unknown>) => void;
145
- onSaveResult?: (success: boolean, error?: string, versionId?: string) => void;
146
- onPublishResult?: (success: boolean, error?: string) => void;
147
- onThemeChanged?: (theme: 'light' | 'dark') => void;
148
- onSelectionChanged?: (keys: string[], fields: Array<{
149
- key: string;
150
- label: string;
151
- type: string;
152
- }>) => void;
153
- onLocaleChanged?: (locale: LocaleContext) => void;
154
- }
155
- declare function EditorProvider({ children, parentOrigin: parentOriginProp, apiUrl: apiUrlProp, onInit, onValuesChanged, onVariantChanged, onSaveResult, onPublishResult, onThemeChanged, onSelectionChanged, onLocaleChanged, }: EditorProviderProps): react_jsx_runtime.JSX.Element;
156
-
157
- /**
158
- * Access the Foir editor context.
159
- *
160
- * Must be used inside an `<EditorProvider>`.
161
- *
162
- * @example
163
- * ```tsx
164
- * function MyEditor() {
165
- * const { isReady, init, client, updateField, setDirty } = useEditor();
166
- * if (!isReady) return <p>Loading...</p>;
167
- * return <form>...</form>;
168
- * }
169
- * ```
170
- */
171
- declare function useEditor(): EditorContextValue;
172
-
173
- interface UseAutoResizeOptions {
174
- /** Minimum height to report (prevents too-small iframes). @default 400 */
175
- minHeight?: number;
176
- /** Debounce delay in milliseconds. @default 100 */
177
- debounce?: number;
178
- /** Whether auto-resize is enabled. @default true */
179
- enabled?: boolean;
180
- }
181
- /**
182
- * Automatically resize the host iframe based on content height.
183
- *
184
- * @returns A callback ref to attach to your root container element.
185
- *
186
- * @example
187
- * ```tsx
188
- * function MyEditor() {
189
- * const containerRef = useAutoResize({ minHeight: 600 });
190
- * return <div ref={containerRef}>...</div>;
191
- * }
192
- * ```
193
- */
194
- declare function useAutoResize(options?: UseAutoResizeOptions): (node: HTMLDivElement | null) => void;
195
-
196
- /**
197
- * Fetch-based GraphQL client for the EIDE platform API.
198
- *
199
- * Zero dependencies — uses the native Fetch API.
200
- */
201
-
202
- /** Error thrown when a platform API request fails. */
203
- declare class PlatformClientError extends Error {
204
- status?: number | undefined;
205
- response?: unknown | undefined;
206
- constructor(message: string, status?: number | undefined, response?: unknown | undefined);
207
- }
208
- /**
209
- * Create a PlatformClient instance.
210
- *
211
- * @example
212
- * ```ts
213
- * const client = createPlatformClient({
214
- * apiUrl: 'https://api.example.com/graphql',
215
- * apiKey: 'sk_test_...',
216
- * });
217
- *
218
- * const { data } = await client.query(
219
- * `query GetProduct($id: ID!) { product(id: $id) { title } }`,
220
- * { id: '123' }
221
- * );
222
- * ```
223
- */
224
- declare function createPlatformClient(options: PlatformClientOptions): PlatformClient;
225
-
226
- /**
227
- * PostMessage protocol types between EIDE platform host and extension iframes.
228
- *
229
- * Inlined from the platform protocol to keep the SDK zero-dep.
230
- */
231
- /**
232
- * Messages sent FROM the platform host TO the iframe extension.
233
- */
234
- type HostToEditorMessage = {
235
- type: 'init';
236
- payload: {
237
- values: Record<string, unknown>;
238
- metadata?: Record<string, unknown>;
239
- modelKey: string;
240
- recordId: string;
241
- naturalKey?: string;
242
- versionId: string | null;
243
- variantKey: string | null;
244
- locale: string | null;
245
- schema: unknown;
246
- mode: 'edit' | 'readonly';
247
- theme: 'light' | 'dark';
248
- apiKey: string;
249
- apiUrl: string;
250
- connectionDomain?: string;
251
- configMode?: 'editor' | 'sidebar';
252
- selectedFieldKeys?: string[];
253
- selectableFields?: Array<{
254
- key: string;
255
- label: string;
256
- type: string;
257
- }>;
258
- localeContext?: {
259
- currentLocale: string;
260
- sourceLocale: string;
261
- isTranslating: boolean;
262
- locales: Array<{
263
- code: string;
264
- displayName: string;
265
- isDefault: boolean;
266
- }>;
267
- };
268
- };
269
- } | {
270
- type: 'values-changed';
271
- payload: {
272
- values: Record<string, unknown>;
273
- source: 'remote';
274
- };
275
- } | {
276
- type: 'variant-changed';
277
- payload: {
278
- variantKey: string;
279
- values: Record<string, unknown>;
280
- };
281
- } | {
282
- type: 'save-result';
283
- payload: {
284
- success: boolean;
285
- error?: string;
286
- versionId?: string;
287
- };
288
- } | {
289
- type: 'publish-result';
290
- payload: {
291
- success: boolean;
292
- error?: string;
293
- };
294
- } | {
295
- type: 'theme-changed';
296
- payload: {
297
- theme: 'light' | 'dark';
298
- };
299
- } | {
300
- type: 'selection-changed';
301
- payload: {
302
- selectedFieldKeys: string[];
303
- fields: Array<{
304
- key: string;
305
- label: string;
306
- type: string;
307
- }>;
308
- };
309
- } | {
310
- type: 'locale-changed';
311
- payload: {
312
- currentLocale: string;
313
- sourceLocale: string;
314
- isTranslating: boolean;
315
- locales: Array<{
316
- code: string;
317
- displayName: string;
318
- isDefault: boolean;
319
- }>;
320
- };
321
- } | {
322
- type: 'operation-result';
323
- payload: {
324
- requestId: string;
325
- success: boolean;
326
- result?: unknown;
327
- error?: {
328
- code: string;
329
- message: string;
330
- };
331
- };
332
- };
333
- /**
334
- * Messages sent FROM the iframe extension TO the platform host.
335
- */
336
- type EditorToHostMessage = {
337
- type: 'ready';
338
- payload: Record<string, never>;
339
- } | {
340
- type: 'update-values';
341
- payload: {
342
- values: Record<string, unknown>;
343
- };
344
- } | {
345
- type: 'update-field';
346
- payload: {
347
- path: string;
348
- value: unknown;
349
- };
350
- } | {
351
- type: 'request-save';
352
- payload: Record<string, never>;
353
- } | {
354
- type: 'request-publish';
355
- payload: Record<string, never>;
356
- } | {
357
- type: 'resize';
358
- payload: {
359
- height: number;
360
- };
361
- } | {
362
- type: 'dirty';
363
- payload: {
364
- isDirty: boolean;
365
- };
366
- } | {
367
- type: 'request-navigate';
368
- payload: {
369
- path: string;
370
- };
371
- } | {
372
- type: 'request-operation';
373
- payload: {
374
- operationKey: string;
375
- input?: Record<string, unknown>;
376
- requestId: string;
377
- /** Execution mode: 'sync' waits for completion, 'async' queues and returns. */
378
- mode?: 'sync' | 'async';
379
- };
380
- };
381
-
382
- /**
383
- * Runtime validation for postMessage payloads.
384
- */
385
-
386
- /** Validate an incoming message from a custom editor iframe (editor -> host). */
387
- declare function isValidEditorMessage(data: unknown): data is EditorToHostMessage;
388
- /** Validate an incoming message from the platform host (host -> editor). */
389
- declare function isValidHostMessage(data: unknown): data is HostToEditorMessage;
390
-
391
- /** Protocol version for compatibility checks. */
392
- declare const PROTOCOL_VERSION = "1.0";
393
-
394
- export { type EditorContextValue, type EditorInit, EditorProvider, type EditorProviderProps, type EditorToHostMessage, type GraphQLError, type GraphQLResponse, type HostToEditorMessage, type LocaleContext, PROTOCOL_VERSION, type PlatformClient, PlatformClientError, type PlatformClientOptions, type UseAutoResizeOptions, createPlatformClient, isValidEditorMessage, isValidHostMessage, useAutoResize, useEditor };
1
+ export { EditorProvider } from './components/EditorProvider';
2
+ export type { EditorProviderProps } from './components/EditorProvider';
3
+ export { useEditor } from './hooks/useEditor';
4
+ export { useAutoResize } from './hooks/useAutoResize';
5
+ export type { UseAutoResizeOptions } from './hooks/useAutoResize';
6
+ export { createPlatformClient, PlatformClientError, } from './client/platform-client';
7
+ export type { EditorInit, EditorContextValue, LocaleContext, PlatformClient, PlatformClientOptions, GraphQLResponse, GraphQLError, } from './types';
8
+ export type { HostToEditorMessage, EditorToHostMessage, } from './protocol/messages';
9
+ export { isValidEditorMessage, isValidHostMessage, } from './protocol/validation';
10
+ export { PROTOCOL_VERSION } from './protocol/constants';
@@ -0,0 +1,10 @@
1
+ export interface BlurhashCanvasProps {
2
+ hash: string;
3
+ className?: string;
4
+ }
5
+ /**
6
+ * Decodes a BlurHash string to a 32×32 canvas. `blurhash` is an optional peer
7
+ * dependency loaded via dynamic import — if it isn't installed the canvas
8
+ * stays blank rather than throwing.
9
+ */
10
+ export declare function BlurhashCanvas({ hash, className }: BlurhashCanvasProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,56 @@
1
+ import type { ProgressiveImageSource, ProgressiveVideoSource, VariantTierKey } from './media';
2
+ export interface ProgressiveMediaProps {
3
+ image?: ProgressiveImageSource;
4
+ video?: ProgressiveVideoSource;
5
+ alt?: string;
6
+ className?: string;
7
+ /** Fill the parent container instead of using intrinsic aspect ratio. */
8
+ fill?: boolean;
9
+ /**
10
+ * Object-fit for fill mode. Defaults to 'cover'. Has no effect when `fill`
11
+ * is false (the image renders at its intrinsic aspect ratio).
12
+ */
13
+ fit?: 'cover' | 'contain';
14
+ /**
15
+ * Solid-colour placeholder mode. When neither image nor video is provided,
16
+ * render a flat coloured div. `aspectRatio` defaults to 16/10.
17
+ */
18
+ placeholder?: {
19
+ color: string;
20
+ aspectRatio?: number;
21
+ };
22
+ /** Video playback — autoplay on mount. Default true. */
23
+ autoplay?: boolean;
24
+ /** Video playback — muted. Default true (required for autoplay). */
25
+ muted?: boolean;
26
+ /** Video playback — loop on end. Default true. */
27
+ loop?: boolean;
28
+ /**
29
+ * When false, skip loading the full media layer (no `<video>`/full `<img>`
30
+ * mount, no HLS attach, no full-image preload). The lower layers — colour,
31
+ * blurhash, thumbnail — still render. Used by responsive dual-render so the
32
+ * CSS-hidden branch doesn't download its full asset. Default true.
33
+ */
34
+ active?: boolean;
35
+ /**
36
+ * `sizes` attribute applied to the full `<img>` — tells the browser how
37
+ * wide the image renders so it can pick the right `srcset` entry. Default
38
+ * `"100vw"`. Has no effect on video sources.
39
+ */
40
+ sizes?: string;
41
+ /**
42
+ * When true, treat this media as the page's LCP candidate: thumbnail and
43
+ * full `<img>` get `fetchpriority="high"` + `loading="eager"`, and any
44
+ * `<video>` mounts immediately. When false (default) below-fold behaviour:
45
+ * `loading="lazy"` on `<img>`, and `<video>` is gated by an
46
+ * IntersectionObserver so its `src`/HLS only attaches near the viewport.
47
+ */
48
+ priority?: boolean;
49
+ /**
50
+ * Tier order, smallest→largest, used to build the `srcset` from a
51
+ * `VariantSet`. Defaults to {@link DEFAULT_VARIANT_TIERS}; override when a
52
+ * project curates a different tier set.
53
+ */
54
+ variantTiers?: readonly VariantTierKey[];
55
+ }
56
+ export declare function ProgressiveMedia({ image, video, alt, className, fill, fit, placeholder, autoplay, muted, loop, active, sizes, priority, variantTiers, }: ProgressiveMediaProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `@foir/sdk/media` — progressive media components + Foir media adapters.
3
+ *
4
+ * `react` is a peer dependency; `hls.js` and `blurhash` are optional peers
5
+ * loaded lazily (video/blurhash gracefully no-op when absent).
6
+ */
7
+ export { ProgressiveMedia } from './ProgressiveMedia';
8
+ export type { ProgressiveMediaProps } from './ProgressiveMedia';
9
+ export { BlurhashCanvas } from './BlurhashCanvas';
10
+ export type { BlurhashCanvasProps } from './BlurhashCanvas';
11
+ export { useProgressiveLoad } from './useProgressiveLoad';
12
+ export type { LoadStage, UseProgressiveLoadOptions } from './useProgressiveLoad';
13
+ export { toProgressiveImage, toProgressiveVideo, effectiveImageDimensions, cropAspectRatio, DEFAULT_VARIANT_TIERS, } from './media';
14
+ export type { VariantTier, VariantSet, VariantTierKey, ProgressiveImageSource, ProgressiveVideoSource, FoirImageInput, FoirVideoInput, } from './media';
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Foir media types + adapters.
3
+ *
4
+ * Framework-agnostic plumbing for rendering Foir image/video fields with
5
+ * `<ProgressiveMedia>`: the canonical source shapes, GraphQL-value → source
6
+ * adapters (crop normalization, per-format variant sets, HLS URL
7
+ * absolutising), and crop-aware dimension maths for CLS-free layout.
8
+ *
9
+ * HLS manifest URLs are absolutised via the shared `absolutiseFoirMediaUrl`
10
+ * from the client transport — not re-implemented here.
11
+ */
12
+ /** One rendered tier: a URL + its TRUE (crop-aware) dims (for srcset `w` + CLS). */
13
+ export interface VariantTier {
14
+ url: string;
15
+ width?: number | null;
16
+ height?: number | null;
17
+ }
18
+ /** Curated tier set in one format (Foir `ImageVariantSet`). */
19
+ export interface VariantSet {
20
+ thumbnail?: VariantTier | null;
21
+ small?: VariantTier | null;
22
+ medium?: VariantTier | null;
23
+ large?: VariantTier | null;
24
+ xlarge?: VariantTier | null;
25
+ _2xl?: VariantTier | null;
26
+ _3xl?: VariantTier | null;
27
+ _4xl?: VariantTier | null;
28
+ }
29
+ /** A `VariantSet` tier name. */
30
+ export type VariantTierKey = keyof VariantSet;
31
+ /**
32
+ * Default tier order, smallest→largest, for walking a `VariantSet`
33
+ * deterministically when building a `srcset`. Override per `<ProgressiveMedia>`
34
+ * via the `variantTiers` prop when a project curates a different tier set.
35
+ */
36
+ export declare const DEFAULT_VARIANT_TIERS: readonly VariantTierKey[];
37
+ /** Image source with progressive loading data. */
38
+ export interface ProgressiveImageSource {
39
+ url: string;
40
+ alt?: string;
41
+ width?: number;
42
+ height?: number;
43
+ blurhash?: string;
44
+ dominantColor?: string;
45
+ thumbnailUrl?: string;
46
+ /** Per-format tier sets (WebP universal; AVIF preferred via `<picture>`). */
47
+ webp?: VariantSet | null;
48
+ avif?: VariantSet | null;
49
+ /**
50
+ * Optional crop rectangle expressed as percentages of the source image
51
+ * (`{x, y, width, height}` in 0–100). Foir's image pipeline serves the
52
+ * cropped file at the cropped dimensions, so consumers need this to compute
53
+ * correct `width`/`height` attrs for CLS-free rendering.
54
+ */
55
+ crop?: {
56
+ x?: number;
57
+ y?: number;
58
+ width?: number;
59
+ height?: number;
60
+ };
61
+ }
62
+ /** Video source with progressive loading data + optional HLS. */
63
+ export interface ProgressiveVideoSource {
64
+ url: string;
65
+ width?: number;
66
+ height?: number;
67
+ blurhash?: string;
68
+ dominantColor?: string;
69
+ thumbnailUrl?: string;
70
+ previewUrl?: string;
71
+ hlsUrl?: string;
72
+ duration?: number;
73
+ }
74
+ /**
75
+ * Foir GraphQL `ImageValue`-like input — the shape a consumer's codegen
76
+ * produces for an image field (intrinsic dims + per-format variant sets +
77
+ * crop as opaque JSON). Structurally compatible with the generated type.
78
+ */
79
+ export interface FoirImageInput {
80
+ url?: string | null;
81
+ alt?: string | null;
82
+ width?: number | null;
83
+ height?: number | null;
84
+ blurhash?: string | null;
85
+ dominantColor?: string | null;
86
+ webp?: VariantSet | null;
87
+ avif?: VariantSet | null;
88
+ crop?: {
89
+ [key: string]: unknown;
90
+ } | null;
91
+ }
92
+ /** Foir GraphQL `VideoValue`-like input. */
93
+ export interface FoirVideoInput {
94
+ url?: string | null;
95
+ width?: number | null;
96
+ height?: number | null;
97
+ hlsManifestUrl?: string | null;
98
+ thumbnailUrl?: string | null;
99
+ previewUrl?: string | null;
100
+ blurhash?: string | null;
101
+ dominantColor?: string | null;
102
+ }
103
+ /**
104
+ * Adapt a Foir image field to the `ProgressiveImageSource` consumed by
105
+ * `<ProgressiveMedia>`. Returns `undefined` when the field is empty (the
106
+ * caller falls back to a placeholder). Narrows the JSON-typed `crop` to the
107
+ * numeric keys; out-of-range values are filtered in `effectiveImageDimensions`.
108
+ */
109
+ export declare function toProgressiveImage(img: FoirImageInput | null | undefined): ProgressiveImageSource | undefined;
110
+ /**
111
+ * Adapt a Foir video field to the `ProgressiveVideoSource` consumed by
112
+ * `<ProgressiveMedia>`. Absolutises the HLS manifest URL (Foir returns it
113
+ * relative to its media proxy). Returns `undefined` when the field is empty.
114
+ */
115
+ export declare function toProgressiveVideo(v: FoirVideoInput | null | undefined): ProgressiveVideoSource | undefined;
116
+ /**
117
+ * Resolve the dimensions of the file Foir's pipeline will actually serve,
118
+ * applying any crop percentages to the source's intrinsic size. Returns null
119
+ * when the source has no width/height. Drives both the wrapper aspect-ratio
120
+ * and the `<img>` `width`/`height` attrs.
121
+ */
122
+ export declare function effectiveImageDimensions(image: {
123
+ width?: number | null;
124
+ height?: number | null;
125
+ crop?: unknown;
126
+ } | null | undefined): {
127
+ width: number;
128
+ height: number;
129
+ } | null;
130
+ /**
131
+ * Compute a CSS `aspect-ratio` string from a Foir image's natural dimensions
132
+ * and optional crop. When a usable crop is set the cropped rectangle's aspect
133
+ * wins; otherwise the intrinsic width/height; otherwise `null`.
134
+ */
135
+ export declare function cropAspectRatio(image: {
136
+ width?: number | null;
137
+ height?: number | null;
138
+ crop?: unknown;
139
+ } | null | undefined): string | null;
@@ -0,0 +1,23 @@
1
+ export type LoadStage = 'color' | 'blurhash' | 'thumbnail' | 'full';
2
+ export interface UseProgressiveLoadOptions {
3
+ blurhash?: string;
4
+ thumbnailUrl?: string;
5
+ fullUrl?: string;
6
+ /**
7
+ * Optional srcset + sizes — when provided to image sources, the preloader
8
+ * matches the variant the visible `<img>` will pick, so the browser dedupes
9
+ * the fetch instead of downloading two sizes.
10
+ */
11
+ fullSrcSet?: string;
12
+ fullSizes?: string;
13
+ isVideo?: boolean;
14
+ }
15
+ /**
16
+ * State machine for progressive media loading.
17
+ * Stages advance forward only: color → blurhash → thumbnail → full.
18
+ * Skips stages when data is missing. Never regresses.
19
+ */
20
+ export declare function useProgressiveLoad({ blurhash, thumbnailUrl, fullUrl, fullSrcSet, fullSizes, isVideo, }: UseProgressiveLoadOptions): {
21
+ stage: LoadStage;
22
+ onFullReady: () => void;
23
+ };
package/dist/media.js CHANGED
@@ -2,7 +2,12 @@
2
2
  import { useRef as useRef2, useState as useState2, useCallback as useCallback2, useEffect as useEffect3 } from "react";
3
3
 
4
4
  // src/graphql/foir-request.ts
5
+ import { print as print2 } from "graphql";
6
+
7
+ // src/graphql/cdn-route.ts
5
8
  import { print } from "graphql";
9
+
10
+ // src/graphql/foir-request.ts
6
11
  var FOIR_CDN_BASE = "https://cdn.foir.dev/";
7
12
  function absolutiseFoirMediaUrl(url, cdnBase = FOIR_CDN_BASE) {
8
13
  if (!url) return void 0;
@@ -0,0 +1,26 @@
1
+ import type { SessionAdapter } from '../auth';
2
+ /**
3
+ * The minimal cookie-store shape this adapter needs. It's satisfied by Next's
4
+ * `cookies()` (route handlers / server actions — `await cookies()` on Next 15),
5
+ * and by `NextResponse.cookies` in middleware. Kept structural on purpose so the
6
+ * SDK doesn't import (or pin a version of) `next/headers`.
7
+ */
8
+ export interface FoirCookieStore {
9
+ get(name: string): {
10
+ value: string;
11
+ } | undefined;
12
+ set(name: string, value: string, options?: Record<string, unknown>): void;
13
+ delete(name: string): void;
14
+ }
15
+ export interface CookieAdapterOptions {
16
+ /** Cookie attributes for writes, merged over the secure defaults. */
17
+ cookieOptions?: Record<string, unknown>;
18
+ }
19
+ export declare function foirCookieName(key: string): string;
20
+ /**
21
+ * A {@link SessionAdapter} backed by an HttpOnly cookie store — the seam for
22
+ * server-side (BFF) Foir sessions in Next.js. Pass the cookie store from a route
23
+ * handler / server action (`await cookies()`) for reads and writes; a Server
24
+ * Component's `cookies()` is read-only, so use it only for reads there.
25
+ */
26
+ export declare function createFoirCookieAdapter(store: FoirCookieStore, opts?: CookieAdapterOptions): SessionAdapter;