@createcms/core 0.1.1

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 (83) hide show
  1. package/README.md +169 -0
  2. package/dist/ab-edge/index.cjs +214 -0
  3. package/dist/ab-edge/index.d.cts +121 -0
  4. package/dist/ab-edge/index.d.ts +121 -0
  5. package/dist/ab-edge/index.js +205 -0
  6. package/dist/bin/createcms.js +3082 -0
  7. package/dist/db.cjs +496 -0
  8. package/dist/db.d.cts +128 -0
  9. package/dist/db.d.ts +128 -0
  10. package/dist/db.js +488 -0
  11. package/dist/index.cjs +13789 -0
  12. package/dist/index.d.cts +10277 -0
  13. package/dist/index.d.ts +10277 -0
  14. package/dist/index.js +13737 -0
  15. package/dist/nanoid.cjs +50 -0
  16. package/dist/nanoid.d.cts +29 -0
  17. package/dist/nanoid.d.ts +29 -0
  18. package/dist/nanoid.js +47 -0
  19. package/dist/next/index.cjs +60 -0
  20. package/dist/next/index.d.cts +141 -0
  21. package/dist/next/index.d.ts +141 -0
  22. package/dist/next/index.js +58 -0
  23. package/dist/next/middleware.cjs +113 -0
  24. package/dist/next/middleware.d.cts +77 -0
  25. package/dist/next/middleware.d.ts +77 -0
  26. package/dist/next/middleware.js +111 -0
  27. package/dist/plugins/ab-test/analytics/upstash.cjs +345 -0
  28. package/dist/plugins/ab-test/analytics/upstash.d.cts +193 -0
  29. package/dist/plugins/ab-test/analytics/upstash.d.ts +193 -0
  30. package/dist/plugins/ab-test/analytics/upstash.js +343 -0
  31. package/dist/plugins/ab-test/client.cjs +686 -0
  32. package/dist/plugins/ab-test/client.d.cts +233 -0
  33. package/dist/plugins/ab-test/client.d.ts +233 -0
  34. package/dist/plugins/ab-test/client.js +684 -0
  35. package/dist/plugins/ab-test/index.cjs +3400 -0
  36. package/dist/plugins/ab-test/index.d.cts +1131 -0
  37. package/dist/plugins/ab-test/index.d.ts +1131 -0
  38. package/dist/plugins/ab-test/index.js +3367 -0
  39. package/dist/plugins/client.cjs +20 -0
  40. package/dist/plugins/client.d.cts +3 -0
  41. package/dist/plugins/client.d.ts +3 -0
  42. package/dist/plugins/client.js +3 -0
  43. package/dist/plugins/consent/client.cjs +315 -0
  44. package/dist/plugins/consent/client.d.cts +145 -0
  45. package/dist/plugins/consent/client.d.ts +145 -0
  46. package/dist/plugins/consent/client.js +313 -0
  47. package/dist/plugins/consent/index.cjs +267 -0
  48. package/dist/plugins/consent/index.d.cts +618 -0
  49. package/dist/plugins/consent/index.d.ts +618 -0
  50. package/dist/plugins/consent/index.js +258 -0
  51. package/dist/plugins/i18n/index.cjs +2177 -0
  52. package/dist/plugins/i18n/index.d.cts +562 -0
  53. package/dist/plugins/i18n/index.d.ts +562 -0
  54. package/dist/plugins/i18n/index.js +2150 -0
  55. package/dist/plugins/media-optimize/index.cjs +315 -0
  56. package/dist/plugins/media-optimize/index.d.cts +144 -0
  57. package/dist/plugins/media-optimize/index.d.ts +144 -0
  58. package/dist/plugins/media-optimize/index.js +311 -0
  59. package/dist/plugins/multi-tenant/index.cjs +210 -0
  60. package/dist/plugins/multi-tenant/index.d.cts +431 -0
  61. package/dist/plugins/multi-tenant/index.d.ts +431 -0
  62. package/dist/plugins/multi-tenant/index.js +207 -0
  63. package/dist/plugins/server.cjs +24 -0
  64. package/dist/plugins/server.d.cts +3 -0
  65. package/dist/plugins/server.d.ts +3 -0
  66. package/dist/plugins/server.js +3 -0
  67. package/dist/react/blocks.cjs +233 -0
  68. package/dist/react/blocks.d.cts +320 -0
  69. package/dist/react/blocks.d.ts +320 -0
  70. package/dist/react/blocks.js +226 -0
  71. package/dist/react/index.cjs +901 -0
  72. package/dist/react/index.d.cts +992 -0
  73. package/dist/react/index.d.ts +992 -0
  74. package/dist/react/index.js +872 -0
  75. package/dist/react/tracking.cjs +243 -0
  76. package/dist/react/tracking.d.cts +364 -0
  77. package/dist/react/tracking.d.ts +364 -0
  78. package/dist/react/tracking.js +216 -0
  79. package/dist/react/variant.cjs +59 -0
  80. package/dist/react/variant.d.cts +26 -0
  81. package/dist/react/variant.d.ts +26 -0
  82. package/dist/react/variant.js +57 -0
  83. package/package.json +303 -0
@@ -0,0 +1,315 @@
1
+ Object.defineProperty(exports, '__esModule', { value: true });
2
+
3
+ var react = require('react');
4
+
5
+ function isImageFile(file) {
6
+ return file.type.startsWith('image/');
7
+ }
8
+ function loadImage(file) {
9
+ return new Promise((resolve, reject)=>{
10
+ const img = new Image();
11
+ const url = URL.createObjectURL(file);
12
+ img.onload = ()=>{
13
+ URL.revokeObjectURL(url);
14
+ resolve(img);
15
+ };
16
+ img.onerror = ()=>{
17
+ URL.revokeObjectURL(url);
18
+ reject(new Error(`Failed to load image: ${file.name}`));
19
+ };
20
+ img.src = url;
21
+ });
22
+ }
23
+ function calculateDimensions(width, height, maxSize) {
24
+ if (width <= maxSize && height <= maxSize) {
25
+ return {
26
+ width,
27
+ height
28
+ };
29
+ }
30
+ const ratio = width / height;
31
+ if (width > height) {
32
+ return {
33
+ width: maxSize,
34
+ height: Math.round(maxSize / ratio)
35
+ };
36
+ }
37
+ return {
38
+ width: Math.round(maxSize * ratio),
39
+ height: maxSize
40
+ };
41
+ }
42
+ function drawToCanvas(img, width, height) {
43
+ const canvas = document.createElement('canvas');
44
+ canvas.width = width;
45
+ canvas.height = height;
46
+ const ctx = canvas.getContext('2d');
47
+ if (!ctx) throw new Error('Failed to get canvas 2d context');
48
+ ctx.imageSmoothingEnabled = true;
49
+ ctx.imageSmoothingQuality = 'high';
50
+ ctx.drawImage(img, 0, 0, width, height);
51
+ return canvas;
52
+ }
53
+ function canvasToBlobAsync(canvas, mimeType, quality) {
54
+ return new Promise((resolve, reject)=>{
55
+ canvas.toBlob((blob)=>blob ? resolve(blob) : reject(new Error(`canvas.toBlob failed for ${mimeType}`)), mimeType, quality);
56
+ });
57
+ }
58
+ let _nativeWebpSupported = null;
59
+ async function supportsNativeWebp() {
60
+ if (_nativeWebpSupported !== null) return _nativeWebpSupported;
61
+ const c = document.createElement('canvas');
62
+ c.width = 1;
63
+ c.height = 1;
64
+ const blob = await new Promise((r)=>c.toBlob((b)=>r(b), 'image/webp', 0.5));
65
+ _nativeWebpSupported = blob?.type === 'image/webp';
66
+ return _nativeWebpSupported;
67
+ }
68
+ async function encodeWebp(canvas, quality) {
69
+ if (await supportsNativeWebp()) {
70
+ return canvasToBlobAsync(canvas, 'image/webp', quality);
71
+ }
72
+ try {
73
+ const { encode } = await import(/* webpackIgnore: true */ '@jsquash/webp');
74
+ const ctx = canvas.getContext('2d');
75
+ if (!ctx) throw new Error('Failed to get canvas 2d context');
76
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
77
+ const buffer = await encode(imageData, {
78
+ quality: quality * 100
79
+ });
80
+ return new Blob([
81
+ buffer
82
+ ], {
83
+ type: 'image/webp'
84
+ });
85
+ } catch {
86
+ throw new Error('WebP encoding not supported natively and @jsquash/webp is not installed. ' + 'Install it as a dependency: npm install @jsquash/webp');
87
+ }
88
+ }
89
+ function inferMimeFromName(name) {
90
+ const ext = name.split('.').pop()?.toLowerCase();
91
+ const map = {
92
+ png: 'image/png',
93
+ jpg: 'image/jpeg',
94
+ jpeg: 'image/jpeg',
95
+ gif: 'image/gif',
96
+ webp: 'image/webp',
97
+ svg: 'image/svg+xml',
98
+ bmp: 'image/bmp',
99
+ avif: 'image/avif'
100
+ };
101
+ return ext && map[ext] || 'image/jpeg';
102
+ }
103
+ function replaceExtension(filename, newExt) {
104
+ const dot = filename.lastIndexOf('.');
105
+ const base = dot > 0 ? filename.slice(0, dot) : filename;
106
+ return `${base}.${newExt}`;
107
+ }
108
+ /**
109
+ * Optimize an image file on the client before uploading.
110
+ *
111
+ * - **Resize**: Downscale if either dimension exceeds `config.resize.maxSize`
112
+ * - **Compress**: Reduce quality via `config.compress.quality` (1-100)
113
+ * - **Convert**: Convert to WebP when `config.convert.format` is set
114
+ * - **storeOriginal**: When `convert.storeOriginal` is true, also returns an
115
+ * `originalVariant` -- same resize + compress but kept in the original format
116
+ *
117
+ * Non-image files are returned unchanged with `optimized: false`.
118
+ */ async function optimizeImage(file, config) {
119
+ if (!isImageFile(file)) {
120
+ return {
121
+ file,
122
+ optimized: false
123
+ };
124
+ }
125
+ const hasResize = !!config.resize?.maxSize;
126
+ const hasCompress = config.compress?.quality != null;
127
+ const hasConvert = config.convert?.format === 'webp';
128
+ if (!hasResize && !hasCompress && !hasConvert) {
129
+ return {
130
+ file,
131
+ optimized: false
132
+ };
133
+ }
134
+ const img = await loadImage(file);
135
+ const maxSize = config.resize?.maxSize ?? Math.max(img.naturalWidth, img.naturalHeight);
136
+ const quality = config.compress?.quality != null ? config.compress.quality / 100 : 0.8;
137
+ const { width, height } = calculateDimensions(img.naturalWidth, img.naturalHeight, maxSize);
138
+ const canvas = drawToCanvas(img, width, height);
139
+ let originalVariant;
140
+ const originalMime = file.type || inferMimeFromName(file.name);
141
+ if (hasConvert && config.convert.storeOriginal) {
142
+ const originalBlob = await canvasToBlobAsync(canvas, originalMime, quality);
143
+ originalVariant = new File([
144
+ originalBlob
145
+ ], file.name, {
146
+ type: originalMime,
147
+ lastModified: Date.now()
148
+ });
149
+ }
150
+ let primaryBlob;
151
+ let primaryName;
152
+ let primaryType;
153
+ if (hasConvert) {
154
+ primaryBlob = await encodeWebp(canvas, quality);
155
+ primaryName = replaceExtension(file.name, 'webp');
156
+ primaryType = 'image/webp';
157
+ } else {
158
+ primaryBlob = await canvasToBlobAsync(canvas, originalMime, quality);
159
+ primaryName = file.name;
160
+ primaryType = originalMime;
161
+ }
162
+ const primaryFile = new File([
163
+ primaryBlob
164
+ ], primaryName, {
165
+ type: primaryType,
166
+ lastModified: Date.now()
167
+ });
168
+ return {
169
+ file: primaryFile,
170
+ originalVariant,
171
+ optimized: true
172
+ };
173
+ }
174
+
175
+ const INITIAL_STATE = {
176
+ results: null,
177
+ isOptimizing: false,
178
+ error: null
179
+ };
180
+ /**
181
+ * React hook that optimizes image files on the client.
182
+ *
183
+ * Accepts a single `File` or `File[]` and an `OptimizationConfig`.
184
+ * Runs `optimizeImage` automatically whenever the input reference changes.
185
+ * Non-image files pass through with `optimized: false`.
186
+ *
187
+ * ```tsx
188
+ * const { results, isOptimizing, error } = cmsClient.optimize.useOptimize(file, {
189
+ * compress: { quality: 80 },
190
+ * resize: { maxSize: 1200 },
191
+ * });
192
+ * ```
193
+ */ function useOptimize(input, config) {
194
+ const [state, setState] = react.useState(INITIAL_STATE);
195
+ const abortRef = react.useRef(false);
196
+ const runIdRef = react.useRef(0);
197
+ const files = react.useMemo(()=>Array.isArray(input) ? input : [
198
+ input
199
+ ], // Stabilize: same File instances → same array
200
+ // eslint-disable-next-line react-hooks/exhaustive-deps
201
+ [
202
+ Array.isArray(input) ? input.length : input
203
+ ]);
204
+ const configKey = JSON.stringify(config);
205
+ const configRef = react.useRef(config);
206
+ configRef.current = config;
207
+ react.useEffect(()=>{
208
+ if (files.length === 0) {
209
+ setState((prev)=>prev.results?.length === 0 && !prev.isOptimizing ? prev : {
210
+ results: [],
211
+ isOptimizing: false,
212
+ error: null
213
+ });
214
+ return;
215
+ }
216
+ abortRef.current = false;
217
+ const currentRun = ++runIdRef.current;
218
+ setState({
219
+ results: null,
220
+ isOptimizing: true,
221
+ error: null
222
+ });
223
+ Promise.all(files.map((file)=>optimizeImage(file, configRef.current))).then((results)=>{
224
+ if (abortRef.current || runIdRef.current !== currentRun) return;
225
+ setState({
226
+ results,
227
+ isOptimizing: false,
228
+ error: null
229
+ });
230
+ }).catch((err)=>{
231
+ if (abortRef.current || runIdRef.current !== currentRun) return;
232
+ setState({
233
+ results: null,
234
+ isOptimizing: false,
235
+ error: err instanceof Error ? err.message : 'Optimization failed'
236
+ });
237
+ });
238
+ return ()=>{
239
+ abortRef.current = true;
240
+ };
241
+ // eslint-disable-next-line react-hooks/exhaustive-deps
242
+ }, [
243
+ files,
244
+ configKey
245
+ ]);
246
+ return state;
247
+ }
248
+
249
+ const PLUGIN_ID = 'media-optimize';
250
+ const $ERROR_CODES = {
251
+ OPTIMIZATION_FAILED: {
252
+ status: 422,
253
+ message: 'Image optimization failed'
254
+ },
255
+ WEBP_NOT_SUPPORTED: {
256
+ status: 422,
257
+ message: 'WebP encoding is not supported in this browser and @jsquash/webp is not installed'
258
+ },
259
+ CANVAS_CONTEXT_FAILED: {
260
+ status: 500,
261
+ message: 'Failed to acquire canvas 2D context for image processing'
262
+ }
263
+ };
264
+ /**
265
+ * Client plugin that adds image optimization under its own namespace.
266
+ *
267
+ * Exposes `cmsClient.optimize.useOptimize(file, config)` for client-side
268
+ * image optimization. Optimized files can then be passed to
269
+ * `cmsClient.media.useUploadAssets().upload(files)`.
270
+ *
271
+ * ```ts
272
+ * import { mediaOptimizeClient } from '@createcms/core/plugins/media-optimize';
273
+ *
274
+ * const client = createCMSClient<typeof cms>({
275
+ * baseURL: '/api/cms',
276
+ * plugins: [
277
+ * mediaOptimizeClient({
278
+ * compress: { quality: 90 },
279
+ * resize: { maxSize: 2000 },
280
+ * convert: { format: 'webp', storeOriginal: true },
281
+ * }),
282
+ * ],
283
+ * });
284
+ *
285
+ * // In a component:
286
+ * const { results, isOptimizing } = client.optimize.useOptimize(file, config);
287
+ * ```
288
+ */ function mediaOptimizeClient(config) {
289
+ return {
290
+ id: PLUGIN_ID,
291
+ $ERROR_CODES,
292
+ async init (_$fetch, _$store) {
293
+ return {
294
+ context: {
295
+ [`${PLUGIN_ID}:config`]: config
296
+ }
297
+ };
298
+ },
299
+ getActions: ()=>({
300
+ optimize: {
301
+ useOptimize: (input, overrideConfig)=>useOptimize(input, overrideConfig ?? config)
302
+ }
303
+ }),
304
+ atomListeners: [
305
+ {
306
+ matcher: (path)=>path.startsWith('/media/createSignedUpload'),
307
+ signal: '$mediaSignal'
308
+ }
309
+ ]
310
+ };
311
+ }
312
+
313
+ exports.mediaOptimizeClient = mediaOptimizeClient;
314
+ exports.optimizeImage = optimizeImage;
315
+ exports.useOptimize = useOptimize;
@@ -0,0 +1,144 @@
1
+ import { WritableAtom } from 'nanostores';
2
+
3
+ type CompressOptions = {
4
+ /** JPEG/PNG quality (1-100). @default 80 */
5
+ quality?: number;
6
+ };
7
+ type ResizeOptions = {
8
+ /** Maximum width/height in pixels. @default 2000 */
9
+ maxSize?: number;
10
+ };
11
+ type ConvertOptions = {
12
+ /** Only WebP conversion is supported. */
13
+ format: 'webp';
14
+ /**
15
+ * When true, also store a copy in the original format (resized + compressed
16
+ * but not converted). Useful for email clients that don't support WebP.
17
+ * @default false
18
+ */
19
+ storeOriginal?: boolean;
20
+ };
21
+ type OptimizationConfig = {
22
+ /** Image compression options. Omit to disable. */
23
+ compress?: CompressOptions;
24
+ /** Resize options. Omit to disable. */
25
+ resize?: ResizeOptions;
26
+ /** Format conversion options. Omit to disable. */
27
+ convert?: ConvertOptions;
28
+ };
29
+
30
+ interface OptimizeResult {
31
+ file: File;
32
+ originalVariant?: File;
33
+ optimized: boolean;
34
+ }
35
+ /**
36
+ * Optimize an image file on the client before uploading.
37
+ *
38
+ * - **Resize**: Downscale if either dimension exceeds `config.resize.maxSize`
39
+ * - **Compress**: Reduce quality via `config.compress.quality` (1-100)
40
+ * - **Convert**: Convert to WebP when `config.convert.format` is set
41
+ * - **storeOriginal**: When `convert.storeOriginal` is true, also returns an
42
+ * `originalVariant` -- same resize + compress but kept in the original format
43
+ *
44
+ * Non-image files are returned unchanged with `optimized: false`.
45
+ */
46
+ declare function optimizeImage(file: File, config: OptimizationConfig): Promise<OptimizeResult>;
47
+
48
+ type OptimizeState = {
49
+ results: OptimizeResult[] | null;
50
+ isOptimizing: boolean;
51
+ error: string | null;
52
+ };
53
+ /**
54
+ * React hook that optimizes image files on the client.
55
+ *
56
+ * Accepts a single `File` or `File[]` and an `OptimizationConfig`.
57
+ * Runs `optimizeImage` automatically whenever the input reference changes.
58
+ * Non-image files pass through with `optimized: false`.
59
+ *
60
+ * ```tsx
61
+ * const { results, isOptimizing, error } = cmsClient.optimize.useOptimize(file, {
62
+ * compress: { quality: 80 },
63
+ * resize: { maxSize: 1200 },
64
+ * });
65
+ * ```
66
+ */
67
+ declare function useOptimize(input: File | File[], config: OptimizationConfig): OptimizeState;
68
+
69
+ type CMSFetch = (path: string, options?: {
70
+ method?: string;
71
+ body?: unknown;
72
+ query?: unknown;
73
+ /**
74
+ * Forwarded to the underlying `fetch` (better-call → @better-fetch → native
75
+ * `fetch`). Set for fire-and-forget analytics beacons (the A/B event ingest)
76
+ * so the POST is NOT cancelled when the page unloads/navigates mid-request.
77
+ */
78
+ keepalive?: boolean;
79
+ }) => Promise<unknown>;
80
+ interface CMSClientStore {
81
+ notify: (signal: string) => void;
82
+ listen: (signal: string, listener: () => void) => void;
83
+ atoms: Record<string, WritableAtom<unknown>>;
84
+ }
85
+
86
+ /**
87
+ * Client plugin that adds image optimization under its own namespace.
88
+ *
89
+ * Exposes `cmsClient.optimize.useOptimize(file, config)` for client-side
90
+ * image optimization. Optimized files can then be passed to
91
+ * `cmsClient.media.useUploadAssets().upload(files)`.
92
+ *
93
+ * ```ts
94
+ * import { mediaOptimizeClient } from '@createcms/core/plugins/media-optimize';
95
+ *
96
+ * const client = createCMSClient<typeof cms>({
97
+ * baseURL: '/api/cms',
98
+ * plugins: [
99
+ * mediaOptimizeClient({
100
+ * compress: { quality: 90 },
101
+ * resize: { maxSize: 2000 },
102
+ * convert: { format: 'webp', storeOriginal: true },
103
+ * }),
104
+ * ],
105
+ * });
106
+ *
107
+ * // In a component:
108
+ * const { results, isOptimizing } = client.optimize.useOptimize(file, config);
109
+ * ```
110
+ */
111
+ declare function mediaOptimizeClient(config: OptimizationConfig): {
112
+ id: "media-optimize";
113
+ $ERROR_CODES: {
114
+ readonly OPTIMIZATION_FAILED: {
115
+ readonly status: 422;
116
+ readonly message: "Image optimization failed";
117
+ };
118
+ readonly WEBP_NOT_SUPPORTED: {
119
+ readonly status: 422;
120
+ readonly message: "WebP encoding is not supported in this browser and @jsquash/webp is not installed";
121
+ };
122
+ readonly CANVAS_CONTEXT_FAILED: {
123
+ readonly status: 500;
124
+ readonly message: "Failed to acquire canvas 2D context for image processing";
125
+ };
126
+ };
127
+ init(_$fetch: CMSFetch, _$store: CMSClientStore): Promise<{
128
+ context: {
129
+ [x: string]: OptimizationConfig;
130
+ };
131
+ }>;
132
+ getActions: () => {
133
+ optimize: {
134
+ useOptimize: (input: File | File[], overrideConfig?: OptimizationConfig) => OptimizeState;
135
+ };
136
+ };
137
+ atomListeners: {
138
+ matcher: (path: string) => boolean;
139
+ signal: "$mediaSignal";
140
+ }[];
141
+ };
142
+
143
+ export { mediaOptimizeClient, optimizeImage, useOptimize };
144
+ export type { OptimizeResult, OptimizeState };
@@ -0,0 +1,144 @@
1
+ import { WritableAtom } from 'nanostores';
2
+
3
+ type CompressOptions = {
4
+ /** JPEG/PNG quality (1-100). @default 80 */
5
+ quality?: number;
6
+ };
7
+ type ResizeOptions = {
8
+ /** Maximum width/height in pixels. @default 2000 */
9
+ maxSize?: number;
10
+ };
11
+ type ConvertOptions = {
12
+ /** Only WebP conversion is supported. */
13
+ format: 'webp';
14
+ /**
15
+ * When true, also store a copy in the original format (resized + compressed
16
+ * but not converted). Useful for email clients that don't support WebP.
17
+ * @default false
18
+ */
19
+ storeOriginal?: boolean;
20
+ };
21
+ type OptimizationConfig = {
22
+ /** Image compression options. Omit to disable. */
23
+ compress?: CompressOptions;
24
+ /** Resize options. Omit to disable. */
25
+ resize?: ResizeOptions;
26
+ /** Format conversion options. Omit to disable. */
27
+ convert?: ConvertOptions;
28
+ };
29
+
30
+ interface OptimizeResult {
31
+ file: File;
32
+ originalVariant?: File;
33
+ optimized: boolean;
34
+ }
35
+ /**
36
+ * Optimize an image file on the client before uploading.
37
+ *
38
+ * - **Resize**: Downscale if either dimension exceeds `config.resize.maxSize`
39
+ * - **Compress**: Reduce quality via `config.compress.quality` (1-100)
40
+ * - **Convert**: Convert to WebP when `config.convert.format` is set
41
+ * - **storeOriginal**: When `convert.storeOriginal` is true, also returns an
42
+ * `originalVariant` -- same resize + compress but kept in the original format
43
+ *
44
+ * Non-image files are returned unchanged with `optimized: false`.
45
+ */
46
+ declare function optimizeImage(file: File, config: OptimizationConfig): Promise<OptimizeResult>;
47
+
48
+ type OptimizeState = {
49
+ results: OptimizeResult[] | null;
50
+ isOptimizing: boolean;
51
+ error: string | null;
52
+ };
53
+ /**
54
+ * React hook that optimizes image files on the client.
55
+ *
56
+ * Accepts a single `File` or `File[]` and an `OptimizationConfig`.
57
+ * Runs `optimizeImage` automatically whenever the input reference changes.
58
+ * Non-image files pass through with `optimized: false`.
59
+ *
60
+ * ```tsx
61
+ * const { results, isOptimizing, error } = cmsClient.optimize.useOptimize(file, {
62
+ * compress: { quality: 80 },
63
+ * resize: { maxSize: 1200 },
64
+ * });
65
+ * ```
66
+ */
67
+ declare function useOptimize(input: File | File[], config: OptimizationConfig): OptimizeState;
68
+
69
+ type CMSFetch = (path: string, options?: {
70
+ method?: string;
71
+ body?: unknown;
72
+ query?: unknown;
73
+ /**
74
+ * Forwarded to the underlying `fetch` (better-call → @better-fetch → native
75
+ * `fetch`). Set for fire-and-forget analytics beacons (the A/B event ingest)
76
+ * so the POST is NOT cancelled when the page unloads/navigates mid-request.
77
+ */
78
+ keepalive?: boolean;
79
+ }) => Promise<unknown>;
80
+ interface CMSClientStore {
81
+ notify: (signal: string) => void;
82
+ listen: (signal: string, listener: () => void) => void;
83
+ atoms: Record<string, WritableAtom<unknown>>;
84
+ }
85
+
86
+ /**
87
+ * Client plugin that adds image optimization under its own namespace.
88
+ *
89
+ * Exposes `cmsClient.optimize.useOptimize(file, config)` for client-side
90
+ * image optimization. Optimized files can then be passed to
91
+ * `cmsClient.media.useUploadAssets().upload(files)`.
92
+ *
93
+ * ```ts
94
+ * import { mediaOptimizeClient } from '@createcms/core/plugins/media-optimize';
95
+ *
96
+ * const client = createCMSClient<typeof cms>({
97
+ * baseURL: '/api/cms',
98
+ * plugins: [
99
+ * mediaOptimizeClient({
100
+ * compress: { quality: 90 },
101
+ * resize: { maxSize: 2000 },
102
+ * convert: { format: 'webp', storeOriginal: true },
103
+ * }),
104
+ * ],
105
+ * });
106
+ *
107
+ * // In a component:
108
+ * const { results, isOptimizing } = client.optimize.useOptimize(file, config);
109
+ * ```
110
+ */
111
+ declare function mediaOptimizeClient(config: OptimizationConfig): {
112
+ id: "media-optimize";
113
+ $ERROR_CODES: {
114
+ readonly OPTIMIZATION_FAILED: {
115
+ readonly status: 422;
116
+ readonly message: "Image optimization failed";
117
+ };
118
+ readonly WEBP_NOT_SUPPORTED: {
119
+ readonly status: 422;
120
+ readonly message: "WebP encoding is not supported in this browser and @jsquash/webp is not installed";
121
+ };
122
+ readonly CANVAS_CONTEXT_FAILED: {
123
+ readonly status: 500;
124
+ readonly message: "Failed to acquire canvas 2D context for image processing";
125
+ };
126
+ };
127
+ init(_$fetch: CMSFetch, _$store: CMSClientStore): Promise<{
128
+ context: {
129
+ [x: string]: OptimizationConfig;
130
+ };
131
+ }>;
132
+ getActions: () => {
133
+ optimize: {
134
+ useOptimize: (input: File | File[], overrideConfig?: OptimizationConfig) => OptimizeState;
135
+ };
136
+ };
137
+ atomListeners: {
138
+ matcher: (path: string) => boolean;
139
+ signal: "$mediaSignal";
140
+ }[];
141
+ };
142
+
143
+ export { mediaOptimizeClient, optimizeImage, useOptimize };
144
+ export type { OptimizeResult, OptimizeState };