@pixelfiddler/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PixelFiddler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,212 @@
1
+ # @pixelfiddler/core
2
+
3
+ Core utilities for building [PixelFiddler](https://pixel-fiddler.com) image transformation URLs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @pixelfiddler/core
9
+ # or
10
+ pnpm add @pixelfiddler/core
11
+ # or
12
+ yarn add @pixelfiddler/core
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```typescript
18
+ import { buildTransformationUrl, buildQueryParams } from '@pixelfiddler/core';
19
+
20
+ // Build a complete transformation URL
21
+ const url = buildTransformationUrl(
22
+ { baseUrl: 'https://cdn.pixelfiddler.com/images/photo.jpg' },
23
+ {
24
+ width: 800,
25
+ height: 600,
26
+ format: 'webp',
27
+ quality: 85,
28
+ }
29
+ );
30
+ // => 'https://cdn.pixelfiddler.com/images/photo.jpg?w=800&h=600&f=webp&q=85'
31
+
32
+ // Or just get the query string
33
+ const params = buildQueryParams({
34
+ width: 400,
35
+ blur: 5,
36
+ grayscale: true,
37
+ });
38
+ // => 'w=400&bl=5&gs=true'
39
+ ```
40
+
41
+ ## API
42
+
43
+ ### `buildTransformationUrl(config, options, mode?)`
44
+
45
+ Builds a complete transformation URL.
46
+
47
+ | Parameter | Type | Description |
48
+ |-----------|------|-------------|
49
+ | `config` | `UrlBuilderConfig` | Configuration with `baseUrl` |
50
+ | `options` | `TransformationOptions` | Transformation options |
51
+ | `mode` | `'short' \| 'long'` | Parameter naming mode (default: `'short'`) |
52
+
53
+ ### `buildQueryParams(options, mode?)`
54
+
55
+ Converts transformation options to a URL-encoded query string.
56
+
57
+ | Parameter | Type | Description |
58
+ |-----------|------|-------------|
59
+ | `options` | `TransformationOptions` | Transformation options |
60
+ | `mode` | `'short' \| 'long'` | Parameter naming mode (default: `'short'`) |
61
+
62
+ ## Transformation Options
63
+
64
+ ### Resize
65
+
66
+ ```typescript
67
+ {
68
+ width: 800, // Target width (px)
69
+ height: 600, // Target height (px)
70
+ dpr: 2, // Device pixel ratio (1-4)
71
+ mode: 'FIT', // 'FILL' | 'FIT' | 'PAD' | 'FORCE' | 'COVER'
72
+ background: '#ffffff' // Background color for PAD mode
73
+ }
74
+ ```
75
+
76
+ ### Format & Quality
77
+
78
+ ```typescript
79
+ {
80
+ format: 'webp', // 'jpeg' | 'png' | 'webp' | 'avif' | 'gif' | 'auto'
81
+ quality: 85, // 1-100
82
+ stripMetadata: true // Remove EXIF data
83
+ }
84
+ ```
85
+
86
+ ### Effects
87
+
88
+ ```typescript
89
+ {
90
+ blur: 5, // Gaussian blur (0-100)
91
+ brightness: 10, // Brightness adjustment (0-100)
92
+ contrast: 20, // Contrast adjustment (0-100)
93
+ saturation: 30, // Saturation adjustment (0-100)
94
+ sharpen: 15, // Sharpen intensity (0-100)
95
+ noise: 5, // Add noise (0-100)
96
+ grayscale: true, // Convert to grayscale
97
+ sepia: true, // Apply sepia effect
98
+ rotate: 90 // Rotation (0 | 90 | 180 | 270)
99
+ }
100
+ ```
101
+
102
+ ### Border
103
+
104
+ ```typescript
105
+ {
106
+ border: {
107
+ width: 2, // Border width (px)
108
+ color: '#000000', // Border color
109
+ radius: 8 // Corner radius (px)
110
+ }
111
+ }
112
+ ```
113
+
114
+ ### Crop
115
+
116
+ ```typescript
117
+ {
118
+ crop: {
119
+ type: 'SIMPLE', // 'SIMPLE' | 'OBJECT'
120
+ objectType: 'FACE', // 'FACE' | 'UPPER_BODY' (for OBJECT type)
121
+ width: 400,
122
+ height: 300,
123
+ focus: {
124
+ type: 'AUTO', // 'SPECIFIED' | 'AUTO'
125
+ strategy: 'SMART', // 'CENTER' | 'SMART' | 'DETAIL'
126
+ position: { x: 50, y: 50 } // For SPECIFIED type
127
+ },
128
+ afterResize: true
129
+ }
130
+ }
131
+ ```
132
+
133
+ ### Text Overlay
134
+
135
+ ```typescript
136
+ {
137
+ text: {
138
+ text: 'Hello World',
139
+ color: '#ffffff',
140
+ opacity: 80,
141
+ size: 50,
142
+ font: 'Arial',
143
+ fontSize: 24,
144
+ position: 'CENTER',
145
+ background: '#000000',
146
+ backgroundOpacity: 50
147
+ }
148
+ }
149
+ ```
150
+
151
+ ### Watermark
152
+
153
+ ```typescript
154
+ {
155
+ watermark: {
156
+ name: 'logo', // Predefined watermark name
157
+ opacity: 50,
158
+ size: 25, // Size as percentage
159
+ width: 100, // Or explicit dimensions
160
+ height: 50,
161
+ position: 'BOTTOM_RIGHT'
162
+ }
163
+ }
164
+ ```
165
+
166
+ ### Special Options
167
+
168
+ ```typescript
169
+ {
170
+ original: true, // Return original image (no transformations)
171
+ alias: 'thumb' // Use predefined transformation alias
172
+ }
173
+ ```
174
+
175
+ ## Parameter Aliases
176
+
177
+ By default, short parameter names are used for smaller URLs:
178
+
179
+ | Option | Short | Long |
180
+ |--------|-------|------|
181
+ | width | `w` | `width` |
182
+ | height | `h` | `height` |
183
+ | format | `f` | `format` |
184
+ | quality | `q` | `quality` |
185
+ | blur | `bl` | `blur` |
186
+ | ... | ... | ... |
187
+
188
+ Use `mode: 'long'` for full parameter names:
189
+
190
+ ```typescript
191
+ buildQueryParams({ width: 800 }, 'long');
192
+ // => 'width=800'
193
+ ```
194
+
195
+ ## TypeScript
196
+
197
+ Full TypeScript support with exported types:
198
+
199
+ ```typescript
200
+ import type {
201
+ TransformationOptions,
202
+ ResizeOptions,
203
+ CropOptions,
204
+ TextOptions,
205
+ WatermarkOptions,
206
+ UrlBuilderConfig,
207
+ } from '@pixelfiddler/core';
208
+ ```
209
+
210
+ ## License
211
+
212
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,132 @@
1
+ 'use strict';
2
+
3
+ // src/types.ts
4
+ var CROP_FOCUS = {
5
+ SPECIFIED: "SPECIFIED",
6
+ AUTO: "AUTO"
7
+ };
8
+ var CROP_FOCUS_STRATEGY = {
9
+ CENTER: "CENTER",
10
+ SMART: "SMART",
11
+ DETAIL: "DETAIL"
12
+ };
13
+ var CROP_TYPE = {
14
+ SIMPLE: "SIMPLE",
15
+ OBJECT: "OBJECT"
16
+ };
17
+ var CROP_OBJECT_TYPE = {
18
+ FACE: "FACE",
19
+ UPPER_BODY: "UPPER_BODY"
20
+ };
21
+
22
+ // src/utils.ts
23
+ var normalizeHex = (value) => value.replace("#", "");
24
+ var removeTrailingSlash = (baseUrl) => {
25
+ return baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
26
+ };
27
+ var getNestedValue = (obj, path) => path.reduce(
28
+ (acc, key) => acc == null ? void 0 : acc[key],
29
+ obj
30
+ );
31
+
32
+ // src/config.ts
33
+ var PARAMS = [
34
+ { path: ["width"], long: "width", short: "w" },
35
+ { path: ["height"], long: "height", short: "h" },
36
+ { path: ["dpr"], long: "dpr", short: "dpr" },
37
+ { path: ["mode"], long: "mode", short: "rm" },
38
+ {
39
+ path: ["background"],
40
+ long: "background",
41
+ short: "bg",
42
+ normalize: (v) => typeof v === "string" ? normalizeHex(v) : v
43
+ },
44
+ { path: ["format"], long: "format", short: "f" },
45
+ { path: ["quality"], long: "quality", short: "q" },
46
+ { path: ["stripMetadata"], long: "stripMetadata", short: "st" },
47
+ { path: ["blur"], long: "blur", short: "bl" },
48
+ { path: ["brightness"], long: "brightness", short: "br" },
49
+ { path: ["contrast"], long: "contrast", short: "ct" },
50
+ { path: ["grayscale"], long: "grayscale", short: "gs" },
51
+ { path: ["saturation"], long: "saturation", short: "sa" },
52
+ { path: ["sepia"], long: "sepia", short: "sp" },
53
+ { path: ["sharpen"], long: "sharpen", short: "sh" },
54
+ { path: ["noise"], long: "noise", short: "ns" },
55
+ { path: ["rotate"], long: "rotate", short: "rt" },
56
+ { path: ["border", "width"], long: "border.width", short: "bo.w" },
57
+ {
58
+ path: ["border", "color"],
59
+ long: "border.color",
60
+ short: "bo.c",
61
+ normalize: (v) => typeof v === "string" ? normalizeHex(v) : v
62
+ },
63
+ { path: ["border", "radius"], long: "border.radius", short: "bo.r" },
64
+ { path: ["crop", "type"], long: "crop.type", short: "cr.t" },
65
+ { path: ["crop", "objectType"], long: "crop.objectType", short: "cr.o" },
66
+ { path: ["crop", "focus", "position", "x"], long: "crop.x", short: "cr.x" },
67
+ { path: ["crop", "focus", "position", "y"], long: "crop.y", short: "cr.y" },
68
+ { path: ["crop", "focus", "strategy"], long: "crop", short: "cr" },
69
+ { path: ["crop", "width"], long: "crop.width", short: "cr.w" },
70
+ { path: ["crop", "height"], long: "crop.height", short: "cr.h" },
71
+ { path: ["crop", "afterResize"], long: "crop.afterResize", short: "cr.ar" },
72
+ { path: ["text", "text"], long: "text", short: "tx" },
73
+ {
74
+ path: ["text", "color"],
75
+ long: "text.color",
76
+ short: "tx.c",
77
+ normalize: (v) => typeof v === "string" ? normalizeHex(v) : v
78
+ },
79
+ { path: ["text", "opacity"], long: "text.opacity", short: "tx.o" },
80
+ { path: ["text", "size"], long: "text.size", short: "tx.s" },
81
+ { path: ["text", "font"], long: "text.font", short: "tx.f" },
82
+ { path: ["text", "fontSize"], long: "text.fontSize", short: "tx.fs" },
83
+ { path: ["text", "position"], long: "text.position", short: "tx.p" },
84
+ {
85
+ path: ["text", "background"],
86
+ long: "text.background",
87
+ short: "tx.bg",
88
+ normalize: (v) => typeof v === "string" ? normalizeHex(v) : v
89
+ },
90
+ { path: ["text", "backgroundOpacity"], long: "text.backgroundOpacity", short: "tx.bg.o" },
91
+ { path: ["watermark", "name"], long: "watermark", short: "wm" },
92
+ { path: ["watermark", "opacity"], long: "watermark.opacity", short: "wm.o" },
93
+ { path: ["watermark", "size"], long: "watermark.size", short: "wm.s" },
94
+ { path: ["watermark", "width"], long: "watermark.width", short: "wm.w" },
95
+ { path: ["watermark", "height"], long: "watermark.height", short: "wm.h" },
96
+ { path: ["watermark", "position"], long: "watermark.position", short: "wm.p" }
97
+ ];
98
+
99
+ // src/url-builder.ts
100
+ var buildQueryParams = (options, mode = "short") => {
101
+ const params = [];
102
+ for (const spec of PARAMS) {
103
+ const value = getNestedValue(options, spec.path);
104
+ if (value == null) continue;
105
+ const name = mode === "short" && spec.short ? spec.short : spec.long;
106
+ let normalized = value;
107
+ if ("normalize" in spec) {
108
+ normalized = spec.normalize(value);
109
+ }
110
+ params.push([name, String(normalized)]);
111
+ }
112
+ return new URLSearchParams(
113
+ params.map(([k, v]) => [k, v])
114
+ ).toString();
115
+ };
116
+ var buildTransformationUrl = (config, options, mode = "short") => {
117
+ if (options.original) {
118
+ return `${config.baseUrl}?original`;
119
+ }
120
+ const query = buildQueryParams(options, mode);
121
+ const baseUrl = removeTrailingSlash(config.baseUrl);
122
+ return `${baseUrl}?${query}`;
123
+ };
124
+
125
+ exports.CROP_FOCUS = CROP_FOCUS;
126
+ exports.CROP_FOCUS_STRATEGY = CROP_FOCUS_STRATEGY;
127
+ exports.CROP_OBJECT_TYPE = CROP_OBJECT_TYPE;
128
+ exports.CROP_TYPE = CROP_TYPE;
129
+ exports.buildQueryParams = buildQueryParams;
130
+ exports.buildTransformationUrl = buildTransformationUrl;
131
+ //# sourceMappingURL=index.cjs.map
132
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/utils.ts","../src/config.ts","../src/url-builder.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\r\n * Helper type to extract values from const objects\r\n */\r\ntype ObjectValue<T> = T[keyof T];\r\n\r\n/**\r\n * Resize mode for image transformations\r\n */\r\nexport type ResizeMode = 'FILL' | 'FIT' | 'PAD' | 'FORCE' | 'COVER';\r\n\r\n/**\r\n * Output image format - null or empty means AUTO\r\n */\r\nexport type ImageFormat = 'jpeg' | 'png' | 'webp' | 'avif' | 'gif'\r\n\r\n/**\r\n * Position for text overlays and watermarks\r\n */\r\nexport type Position =\r\n | 'TOP_LEFT'\r\n | 'TOP_CENTER'\r\n | 'TOP_RIGHT'\r\n | 'CENTER_LEFT'\r\n | 'CENTER'\r\n | 'CENTER_RIGHT'\r\n | 'BOTTOM_LEFT'\r\n | 'BOTTOM_CENTER'\r\n | 'BOTTOM_RIGHT';\r\n\r\n/**\r\n * Crop focus type\r\n */\r\nexport const CROP_FOCUS = {\r\n SPECIFIED: 'SPECIFIED',\r\n AUTO: 'AUTO',\r\n} as const;\r\n\r\nexport type CropFocus = ObjectValue<typeof CROP_FOCUS>;\r\n\r\n/**\r\n * Crop focus strategy for AUTO focus\r\n */\r\nexport const CROP_FOCUS_STRATEGY = {\r\n CENTER: 'CENTER',\r\n SMART: 'SMART',\r\n DETAIL: 'DETAIL',\r\n} as const;\r\n\r\nexport type CropFocusStrategy = ObjectValue<typeof CROP_FOCUS_STRATEGY>;\r\n\r\n/**\r\n * Crop type\r\n */\r\nexport const CROP_TYPE = {\r\n SIMPLE: 'SIMPLE',\r\n OBJECT: 'OBJECT',\r\n} as const;\r\n\r\nexport type CropType = ObjectValue<typeof CROP_TYPE>;\r\n\r\n/**\r\n * Object type for smart cropping\r\n */\r\nexport const CROP_OBJECT_TYPE = {\r\n FACE: 'FACE',\r\n UPPER_BODY: 'UPPER_BODY',\r\n} as const;\r\n\r\nexport type CropObjectType = ObjectValue<typeof CROP_OBJECT_TYPE>;\r\n\r\n/**\r\n * Rotation angle in degrees (clockwise)\r\n */\r\nexport type RotationAngle = 0 | 90 | 180 | 270;\r\n\r\n/**\r\n * Device pixel ratio for high-density displays\r\n */\r\nexport type DevicePixelRatio = 1 | 2 | 3 | 4;\r\n\r\n/**\r\n * Hex color string (with or without #)\r\n */\r\nexport type HexColor = string;\r\n\r\n/**\r\n * Resize transformation options\r\n */\r\nexport interface ResizeOptions {\r\n /** Target width in pixels (1-4096) */\r\n width?: number;\r\n /** Target height in pixels (1-4096) */\r\n height?: number;\r\n /** Device pixel ratio for retina displays */\r\n dpr?: DevicePixelRatio;\r\n /** Resize strategy */\r\n mode?: ResizeMode;\r\n /** Background color for PAD mode (hex) */\r\n background?: HexColor;\r\n}\r\n\r\n/**\r\n * Format and quality options\r\n */\r\nexport interface FormatOptions {\r\n /** Output format */\r\n format?: ImageFormat;\r\n /** Quality level (1-100) */\r\n quality?: number;\r\n /** Strip metadata from output */\r\n stripMetadata?: boolean;\r\n}\r\n\r\n/**\r\n * Effect transformation options\r\n */\r\nexport interface EffectOptions {\r\n /** Gaussian blur intensity (0-100) */\r\n blur?: number;\r\n /** Brightness adjustment (0-100) */\r\n brightness?: number;\r\n /** Contrast adjustment (0-100) */\r\n contrast?: number;\r\n /** Convert to grayscale */\r\n grayscale?: boolean;\r\n /** Saturation adjustment (0-100) */\r\n saturation?: number;\r\n /** Apply sepia effect */\r\n sepia?: boolean;\r\n /** Sharpen intensity (0-100) */\r\n sharpen?: number;\r\n /** Add noise intensity (0-100) */\r\n noise?: number;\r\n /** Rotation angle */\r\n rotate?: RotationAngle;\r\n}\r\n\r\n/**\r\n * Border options\r\n */\r\nexport interface BorderOptions {\r\n /** Border width in pixels */\r\n width?: number;\r\n /** Border color (hex) */\r\n color?: HexColor;\r\n /** Corner radius in pixels (1-2000) */\r\n radius?: number;\r\n}\r\n\r\n/**\r\n * Crop focus position for SPECIFIED focus type\r\n */\r\nexport interface CropFocusPosition {\r\n x?: number;\r\n y?: number;\r\n}\r\n\r\n/**\r\n * Crop focus options\r\n */\r\nexport interface CropFocusOptions {\r\n /** Focus type - SPECIFIED (manual position) or AUTO (strategy-based) */\r\n type?: CropFocus;\r\n /** Manual focus position when type is SPECIFIED */\r\n position?: CropFocusPosition;\r\n /** Auto focus strategy when type is AUTO */\r\n strategy?: CropFocusStrategy;\r\n}\r\n\r\n/**\r\n * Crop options\r\n */\r\nexport interface CropOptions {\r\n /** Crop type - SIMPLE or OBJECT */\r\n type?: CropType;\r\n /** Object type for OBJECT crop type */\r\n objectType?: CropObjectType;\r\n /** Focus options */\r\n focus?: CropFocusOptions;\r\n /** Crop width in pixels */\r\n width?: number;\r\n /** Crop height in pixels */\r\n height?: number;\r\n /** Apply crop after resize */\r\n afterResize?: boolean;\r\n}\r\n\r\n/**\r\n * Text overlay options\r\n */\r\nexport interface TextOptions {\r\n /** Text content to render */\r\n text?: string;\r\n /** Text color (hex) */\r\n color?: HexColor;\r\n /** Text opacity (0-100) */\r\n opacity?: number;\r\n /** Background color behind text (hex) */\r\n background?: HexColor;\r\n /** Background opacity (0-100) */\r\n backgroundOpacity?: number;\r\n /** Text size as percentage of image (1-100) */\r\n size?: number;\r\n /** Font family name */\r\n font?: string;\r\n /** Font size in pixels */\r\n fontSize?: number;\r\n /** Text position */\r\n position?: Position;\r\n}\r\n\r\n/**\r\n * Watermark options\r\n */\r\nexport interface WatermarkOptions {\r\n /** Watermark name (predefined in dashboard) */\r\n name?: string;\r\n /** Watermark opacity (0-100) */\r\n opacity?: number;\r\n /** Watermark size as percentage (1-100) */\r\n size?: number;\r\n /** Watermark width in pixels */\r\n width?: number;\r\n /** Watermark height in pixels */\r\n height?: number;\r\n /** Watermark position */\r\n position?: Position;\r\n}\r\n\r\n/**\r\n * All transformation options combined\r\n */\r\nexport interface TransformationOptions\r\n extends ResizeOptions,\r\n FormatOptions,\r\n EffectOptions {\r\n /** Border options */\r\n border?: BorderOptions;\r\n /** Crop options */\r\n crop?: CropOptions;\r\n /** Text overlay options */\r\n text?: TextOptions;\r\n /** Watermark options */\r\n watermark?: WatermarkOptions;\r\n /** Use predefined transformation alias */\r\n alias?: string;\r\n /** Return original image without transformations */\r\n original?: boolean;\r\n}\r\n\r\n/**\r\n * Configuration for PixelFiddler client\r\n */\r\nexport interface PixelFiddlerConfig {\r\n /** Base URL for the CDN (defaults to https://cdn.pixel-fiddler.com) */\r\n baseUrl?: string;\r\n /** Secret key for URL signing */\r\n signatureKey?: string;\r\n}\r\n\r\n/**\r\n * URL builder configuration (internal)\r\n */\r\nexport interface UrlBuilderConfig extends PixelFiddlerConfig {\r\n /** Resolved base URL */\r\n baseUrl: string;\r\n}\r\n\r\n/**\r\n * Mapping of transformation options to query parameter names\r\n */\r\nexport interface QueryParameterMapping {\r\n /** Full parameter name */\r\n name: string;\r\n /** Short alias */\r\n alias?: string;\r\n}\r\n\r\n\r\n","import { Path, PathValue } from './util-types';\r\n\r\nexport const normalizeHex = (value: string) => value.replace('#', '')\r\n\r\n\r\nexport const removeTrailingSlash = (baseUrl: string) => {\r\n return baseUrl.endsWith('/')\r\n ? baseUrl.slice(0, -1)\r\n : baseUrl;\r\n}\r\n\r\nexport const getNestedValue = <\r\n T,\r\n P extends Path<T>\r\n>(\r\n obj: T,\r\n path: P\r\n): PathValue<T, P> | undefined =>\r\n path.reduce<any>(\r\n (acc, key) => (acc == null ? undefined : acc[key]),\r\n obj\r\n );\r\n\r\n","import { ParamSpec, Path } from './util-types';\r\nimport { TransformationOptions } from './types';\r\nimport { normalizeHex } from './utils';\r\n\r\nexport const PARAMS = [\r\n {path: ['width'], long: 'width', short: 'w'},\r\n {path: ['height'], long: 'height', short: 'h'},\r\n {path: ['dpr'], long: 'dpr', short: 'dpr'},\r\n {path: ['mode'], long: 'mode', short: 'rm'},\r\n {\r\n path: ['background'],\r\n long: 'background',\r\n short: 'bg',\r\n normalize: (v) => typeof v === 'string' ? normalizeHex(v) : v\r\n },\r\n\r\n {path: ['format'], long: 'format', short: 'f'},\r\n {path: ['quality'], long: 'quality', short: 'q'},\r\n {path: ['stripMetadata'], long: 'stripMetadata', short: 'st'},\r\n\r\n {path: ['blur'], long: 'blur', short: 'bl'},\r\n {path: ['brightness'], long: 'brightness', short: 'br'},\r\n {path: ['contrast'], long: 'contrast', short: 'ct'},\r\n {path: ['grayscale'], long: 'grayscale', short: 'gs'},\r\n {path: ['saturation'], long: 'saturation', short: 'sa'},\r\n {path: ['sepia'], long: 'sepia', short: 'sp'},\r\n {path: ['sharpen'], long: 'sharpen', short: 'sh'},\r\n {path: ['noise'], long: 'noise', short: 'ns'},\r\n {path: ['rotate'], long: 'rotate', short: 'rt'},\r\n\r\n {path: ['border', 'width'], long: 'border.width', short: 'bo.w'},\r\n {\r\n path: ['border', 'color'],\r\n long: 'border.color',\r\n short: 'bo.c',\r\n normalize: (v) => typeof v === 'string' ? normalizeHex(v) : v\r\n },\r\n {path: ['border', 'radius'], long: 'border.radius', short: 'bo.r'},\r\n\r\n {path: ['crop', 'type'], long: 'crop.type', short: 'cr.t'},\r\n {path: ['crop', 'objectType'], long: 'crop.objectType', short: 'cr.o'},\r\n {path: ['crop', 'focus', 'position', 'x'], long: 'crop.x', short: 'cr.x'},\r\n {path: ['crop', 'focus', 'position', 'y'], long: 'crop.y', short: 'cr.y'},\r\n {path: ['crop', 'focus', 'strategy'], long: 'crop', short: 'cr'},\r\n {path: ['crop', 'width'], long: 'crop.width', short: 'cr.w'},\r\n {path: ['crop', 'height'], long: 'crop.height', short: 'cr.h'},\r\n {path: ['crop', 'afterResize'], long: 'crop.afterResize', short: 'cr.ar'},\r\n\r\n {path: ['text', 'text'], long: 'text', short: 'tx'},\r\n {\r\n path: ['text', 'color'],\r\n long: 'text.color',\r\n short: 'tx.c',\r\n normalize: (v) => typeof v === 'string' ? normalizeHex(v) : v\r\n },\r\n {path: ['text', 'opacity'], long: 'text.opacity', short: 'tx.o'},\r\n {path: ['text', 'size'], long: 'text.size', short: 'tx.s'},\r\n {path: ['text', 'font'], long: 'text.font', short: 'tx.f'},\r\n {path: ['text', 'fontSize'], long: 'text.fontSize', short: 'tx.fs'},\r\n {path: ['text', 'position'], long: 'text.position', short: 'tx.p'},\r\n {\r\n path: ['text', 'background'],\r\n long: 'text.background',\r\n short: 'tx.bg',\r\n normalize: (v) => typeof v === 'string' ? normalizeHex(v) : v\r\n },\r\n {path: ['text', 'backgroundOpacity'], long: 'text.backgroundOpacity', short: 'tx.bg.o'},\r\n\r\n {path: ['watermark', 'name'], long: 'watermark', short: 'wm'},\r\n {path: ['watermark', 'opacity'], long: 'watermark.opacity', short: 'wm.o'},\r\n {path: ['watermark', 'size'], long: 'watermark.size', short: 'wm.s'},\r\n {path: ['watermark', 'width'], long: 'watermark.width', short: 'wm.w'},\r\n {path: ['watermark', 'height'], long: 'watermark.height', short: 'wm.h'},\r\n {path: ['watermark', 'position'], long: 'watermark.position', short: 'wm.p'},\r\n] as const satisfies readonly ParamSpec<\r\n TransformationOptions,\r\n Path<TransformationOptions>\r\n>[];\r\n","import { TransformationOptions, UrlBuilderConfig } from './types';\r\nimport { getNestedValue, removeTrailingSlash } from './utils';\r\nimport { PARAMS } from './config';\r\n\r\n/**\r\n * Determines which parameter names to use in the query string.\r\n * - `'short'` - Use abbreviated aliases (e.g., `w`, `h`, `q`)\r\n * - `'long'` - Use full parameter names (e.g., `width`, `height`, `quality`)\r\n */\r\ntype NameMode = 'short' | 'long';\r\n\r\n/**\r\n * Converts transformation options into a URL-encoded query string.\r\n *\r\n * Iterates through all defined parameters in the PARAMS config, extracts values\r\n * from the options object using their paths, applies any normalization functions\r\n * (e.g., stripping `#` from hex colors), and returns a URL-encoded query string.\r\n *\r\n * @param options - The transformation options object\r\n * @param mode - Whether to use short aliases or long parameter names (default: `'short'`)\r\n * @returns URL-encoded query string (without leading `?`)\r\n *\r\n * @example\r\n * ```ts\r\n * // Using short aliases (default)\r\n * buildQueryParams({ width: 800, height: 600, quality: 80 });\r\n * // => 'w=800&h=600&q=80'\r\n *\r\n * // Using long parameter names\r\n * buildQueryParams({ width: 800, format: 'webp' }, 'long');\r\n * // => 'width=800&format=webp'\r\n *\r\n * // Nested options\r\n * buildQueryParams({ border: { width: 2, color: '#ff0000' } });\r\n * // => 'bo.w=2&bo.c=ff0000'\r\n * ```\r\n */\r\nexport const buildQueryParams = (\r\n options: TransformationOptions,\r\n mode: NameMode = 'short'\r\n): string => {\r\n const params: Array<[string, string]> = [];\r\n\r\n for (const spec of PARAMS) {\r\n const value = getNestedValue(options, spec.path);\r\n if (value == null) continue;\r\n\r\n const name =\r\n mode === 'short' && spec.short ? spec.short : spec.long;\r\n\r\n let normalized = value\r\n if ('normalize' in spec) {\r\n normalized = spec.normalize(value)\r\n }\r\n\r\n params.push([name, String(normalized)]);\r\n }\r\n\r\n\r\n return new URLSearchParams(\r\n params.map(([k, v]) => [k, v])\r\n ).toString();\r\n\r\n};\r\n\r\n/**\r\n * Builds a complete transformation URL from a base URL and transformation options.\r\n *\r\n * Combines the configured base URL with query parameters generated from the\r\n * transformation options. Handles the special `original` flag which returns\r\n * the unmodified source image.\r\n *\r\n * @param config - Configuration containing the base URL for the image\r\n * @param options - The transformation options to apply\r\n * @param mode - Whether to use short aliases or long parameter names (default: `'short'`)\r\n * @returns The complete URL string with query parameters\r\n *\r\n * @example\r\n * ```ts\r\n * const config = { baseUrl: 'https://cdn.example.com/images/photo.jpg' };\r\n *\r\n * // Basic resize\r\n * buildTransformationUrl(config, { width: 400, height: 300 });\r\n * // => 'https://cdn.example.com/images/photo.jpg?w=400&h=300'\r\n *\r\n * // Multiple transformations\r\n * buildTransformationUrl(config, {\r\n * width: 800,\r\n * format: 'webp',\r\n * quality: 85,\r\n * blur: 5\r\n * });\r\n * // => 'https://cdn.example.com/images/photo.jpg?w=800&f=webp&q=85&bl=5'\r\n *\r\n * // Request original image (no transformations)\r\n * buildTransformationUrl(config, { original: true });\r\n * // => 'https://cdn.example.com/images/photo.jpg?original'\r\n *\r\n * // Using long parameter names\r\n * buildTransformationUrl(config, { width: 400 }, 'long');\r\n * // => 'https://cdn.example.com/images/photo.jpg?width=400'\r\n * ```\r\n */\r\nexport const buildTransformationUrl = (\r\n config: UrlBuilderConfig,\r\n options: TransformationOptions,\r\n mode: NameMode = 'short'\r\n) => {\r\n if (options.original) {\r\n return `${config.baseUrl}?original`;\r\n }\r\n\r\n const query = buildQueryParams(options, mode);\r\n\r\n const baseUrl = removeTrailingSlash(config.baseUrl)\r\n return `${baseUrl}?${query}`;\r\n};\r\n"]}
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Helper type to extract values from const objects
3
+ */
4
+ type ObjectValue<T> = T[keyof T];
5
+ /**
6
+ * Resize mode for image transformations
7
+ */
8
+ type ResizeMode = 'FILL' | 'FIT' | 'PAD' | 'FORCE' | 'COVER';
9
+ /**
10
+ * Output image format - null or empty means AUTO
11
+ */
12
+ type ImageFormat = 'jpeg' | 'png' | 'webp' | 'avif' | 'gif';
13
+ /**
14
+ * Position for text overlays and watermarks
15
+ */
16
+ type Position = 'TOP_LEFT' | 'TOP_CENTER' | 'TOP_RIGHT' | 'CENTER_LEFT' | 'CENTER' | 'CENTER_RIGHT' | 'BOTTOM_LEFT' | 'BOTTOM_CENTER' | 'BOTTOM_RIGHT';
17
+ /**
18
+ * Crop focus type
19
+ */
20
+ declare const CROP_FOCUS: {
21
+ readonly SPECIFIED: "SPECIFIED";
22
+ readonly AUTO: "AUTO";
23
+ };
24
+ type CropFocus = ObjectValue<typeof CROP_FOCUS>;
25
+ /**
26
+ * Crop focus strategy for AUTO focus
27
+ */
28
+ declare const CROP_FOCUS_STRATEGY: {
29
+ readonly CENTER: "CENTER";
30
+ readonly SMART: "SMART";
31
+ readonly DETAIL: "DETAIL";
32
+ };
33
+ type CropFocusStrategy = ObjectValue<typeof CROP_FOCUS_STRATEGY>;
34
+ /**
35
+ * Crop type
36
+ */
37
+ declare const CROP_TYPE: {
38
+ readonly SIMPLE: "SIMPLE";
39
+ readonly OBJECT: "OBJECT";
40
+ };
41
+ type CropType = ObjectValue<typeof CROP_TYPE>;
42
+ /**
43
+ * Object type for smart cropping
44
+ */
45
+ declare const CROP_OBJECT_TYPE: {
46
+ readonly FACE: "FACE";
47
+ readonly UPPER_BODY: "UPPER_BODY";
48
+ };
49
+ type CropObjectType = ObjectValue<typeof CROP_OBJECT_TYPE>;
50
+ /**
51
+ * Rotation angle in degrees (clockwise)
52
+ */
53
+ type RotationAngle = 0 | 90 | 180 | 270;
54
+ /**
55
+ * Device pixel ratio for high-density displays
56
+ */
57
+ type DevicePixelRatio = 1 | 2 | 3 | 4;
58
+ /**
59
+ * Hex color string (with or without #)
60
+ */
61
+ type HexColor = string;
62
+ /**
63
+ * Resize transformation options
64
+ */
65
+ interface ResizeOptions {
66
+ /** Target width in pixels (1-4096) */
67
+ width?: number;
68
+ /** Target height in pixels (1-4096) */
69
+ height?: number;
70
+ /** Device pixel ratio for retina displays */
71
+ dpr?: DevicePixelRatio;
72
+ /** Resize strategy */
73
+ mode?: ResizeMode;
74
+ /** Background color for PAD mode (hex) */
75
+ background?: HexColor;
76
+ }
77
+ /**
78
+ * Format and quality options
79
+ */
80
+ interface FormatOptions {
81
+ /** Output format */
82
+ format?: ImageFormat;
83
+ /** Quality level (1-100) */
84
+ quality?: number;
85
+ /** Strip metadata from output */
86
+ stripMetadata?: boolean;
87
+ }
88
+ /**
89
+ * Effect transformation options
90
+ */
91
+ interface EffectOptions {
92
+ /** Gaussian blur intensity (0-100) */
93
+ blur?: number;
94
+ /** Brightness adjustment (0-100) */
95
+ brightness?: number;
96
+ /** Contrast adjustment (0-100) */
97
+ contrast?: number;
98
+ /** Convert to grayscale */
99
+ grayscale?: boolean;
100
+ /** Saturation adjustment (0-100) */
101
+ saturation?: number;
102
+ /** Apply sepia effect */
103
+ sepia?: boolean;
104
+ /** Sharpen intensity (0-100) */
105
+ sharpen?: number;
106
+ /** Add noise intensity (0-100) */
107
+ noise?: number;
108
+ /** Rotation angle */
109
+ rotate?: RotationAngle;
110
+ }
111
+ /**
112
+ * Border options
113
+ */
114
+ interface BorderOptions {
115
+ /** Border width in pixels */
116
+ width?: number;
117
+ /** Border color (hex) */
118
+ color?: HexColor;
119
+ /** Corner radius in pixels (1-2000) */
120
+ radius?: number;
121
+ }
122
+ /**
123
+ * Crop focus position for SPECIFIED focus type
124
+ */
125
+ interface CropFocusPosition {
126
+ x?: number;
127
+ y?: number;
128
+ }
129
+ /**
130
+ * Crop focus options
131
+ */
132
+ interface CropFocusOptions {
133
+ /** Focus type - SPECIFIED (manual position) or AUTO (strategy-based) */
134
+ type?: CropFocus;
135
+ /** Manual focus position when type is SPECIFIED */
136
+ position?: CropFocusPosition;
137
+ /** Auto focus strategy when type is AUTO */
138
+ strategy?: CropFocusStrategy;
139
+ }
140
+ /**
141
+ * Crop options
142
+ */
143
+ interface CropOptions {
144
+ /** Crop type - SIMPLE or OBJECT */
145
+ type?: CropType;
146
+ /** Object type for OBJECT crop type */
147
+ objectType?: CropObjectType;
148
+ /** Focus options */
149
+ focus?: CropFocusOptions;
150
+ /** Crop width in pixels */
151
+ width?: number;
152
+ /** Crop height in pixels */
153
+ height?: number;
154
+ /** Apply crop after resize */
155
+ afterResize?: boolean;
156
+ }
157
+ /**
158
+ * Text overlay options
159
+ */
160
+ interface TextOptions {
161
+ /** Text content to render */
162
+ text?: string;
163
+ /** Text color (hex) */
164
+ color?: HexColor;
165
+ /** Text opacity (0-100) */
166
+ opacity?: number;
167
+ /** Background color behind text (hex) */
168
+ background?: HexColor;
169
+ /** Background opacity (0-100) */
170
+ backgroundOpacity?: number;
171
+ /** Text size as percentage of image (1-100) */
172
+ size?: number;
173
+ /** Font family name */
174
+ font?: string;
175
+ /** Font size in pixels */
176
+ fontSize?: number;
177
+ /** Text position */
178
+ position?: Position;
179
+ }
180
+ /**
181
+ * Watermark options
182
+ */
183
+ interface WatermarkOptions {
184
+ /** Watermark name (predefined in dashboard) */
185
+ name?: string;
186
+ /** Watermark opacity (0-100) */
187
+ opacity?: number;
188
+ /** Watermark size as percentage (1-100) */
189
+ size?: number;
190
+ /** Watermark width in pixels */
191
+ width?: number;
192
+ /** Watermark height in pixels */
193
+ height?: number;
194
+ /** Watermark position */
195
+ position?: Position;
196
+ }
197
+ /**
198
+ * All transformation options combined
199
+ */
200
+ interface TransformationOptions extends ResizeOptions, FormatOptions, EffectOptions {
201
+ /** Border options */
202
+ border?: BorderOptions;
203
+ /** Crop options */
204
+ crop?: CropOptions;
205
+ /** Text overlay options */
206
+ text?: TextOptions;
207
+ /** Watermark options */
208
+ watermark?: WatermarkOptions;
209
+ /** Use predefined transformation alias */
210
+ alias?: string;
211
+ /** Return original image without transformations */
212
+ original?: boolean;
213
+ }
214
+ /**
215
+ * Configuration for PixelFiddler client
216
+ */
217
+ interface PixelFiddlerConfig {
218
+ /** Base URL for the CDN (defaults to https://cdn.pixel-fiddler.com) */
219
+ baseUrl?: string;
220
+ /** Secret key for URL signing */
221
+ signatureKey?: string;
222
+ }
223
+ /**
224
+ * URL builder configuration (internal)
225
+ */
226
+ interface UrlBuilderConfig extends PixelFiddlerConfig {
227
+ /** Resolved base URL */
228
+ baseUrl: string;
229
+ }
230
+
231
+ /**
232
+ * Determines which parameter names to use in the query string.
233
+ * - `'short'` - Use abbreviated aliases (e.g., `w`, `h`, `q`)
234
+ * - `'long'` - Use full parameter names (e.g., `width`, `height`, `quality`)
235
+ */
236
+ type NameMode = 'short' | 'long';
237
+ /**
238
+ * Converts transformation options into a URL-encoded query string.
239
+ *
240
+ * Iterates through all defined parameters in the PARAMS config, extracts values
241
+ * from the options object using their paths, applies any normalization functions
242
+ * (e.g., stripping `#` from hex colors), and returns a URL-encoded query string.
243
+ *
244
+ * @param options - The transformation options object
245
+ * @param mode - Whether to use short aliases or long parameter names (default: `'short'`)
246
+ * @returns URL-encoded query string (without leading `?`)
247
+ *
248
+ * @example
249
+ * ```ts
250
+ * // Using short aliases (default)
251
+ * buildQueryParams({ width: 800, height: 600, quality: 80 });
252
+ * // => 'w=800&h=600&q=80'
253
+ *
254
+ * // Using long parameter names
255
+ * buildQueryParams({ width: 800, format: 'webp' }, 'long');
256
+ * // => 'width=800&format=webp'
257
+ *
258
+ * // Nested options
259
+ * buildQueryParams({ border: { width: 2, color: '#ff0000' } });
260
+ * // => 'bo.w=2&bo.c=ff0000'
261
+ * ```
262
+ */
263
+ declare const buildQueryParams: (options: TransformationOptions, mode?: NameMode) => string;
264
+ /**
265
+ * Builds a complete transformation URL from a base URL and transformation options.
266
+ *
267
+ * Combines the configured base URL with query parameters generated from the
268
+ * transformation options. Handles the special `original` flag which returns
269
+ * the unmodified source image.
270
+ *
271
+ * @param config - Configuration containing the base URL for the image
272
+ * @param options - The transformation options to apply
273
+ * @param mode - Whether to use short aliases or long parameter names (default: `'short'`)
274
+ * @returns The complete URL string with query parameters
275
+ *
276
+ * @example
277
+ * ```ts
278
+ * const config = { baseUrl: 'https://cdn.example.com/images/photo.jpg' };
279
+ *
280
+ * // Basic resize
281
+ * buildTransformationUrl(config, { width: 400, height: 300 });
282
+ * // => 'https://cdn.example.com/images/photo.jpg?w=400&h=300'
283
+ *
284
+ * // Multiple transformations
285
+ * buildTransformationUrl(config, {
286
+ * width: 800,
287
+ * format: 'webp',
288
+ * quality: 85,
289
+ * blur: 5
290
+ * });
291
+ * // => 'https://cdn.example.com/images/photo.jpg?w=800&f=webp&q=85&bl=5'
292
+ *
293
+ * // Request original image (no transformations)
294
+ * buildTransformationUrl(config, { original: true });
295
+ * // => 'https://cdn.example.com/images/photo.jpg?original'
296
+ *
297
+ * // Using long parameter names
298
+ * buildTransformationUrl(config, { width: 400 }, 'long');
299
+ * // => 'https://cdn.example.com/images/photo.jpg?width=400'
300
+ * ```
301
+ */
302
+ declare const buildTransformationUrl: (config: UrlBuilderConfig, options: TransformationOptions, mode?: NameMode) => string;
303
+
304
+ export { type BorderOptions, CROP_FOCUS, CROP_FOCUS_STRATEGY, CROP_OBJECT_TYPE, CROP_TYPE, type CropFocus, type CropFocusOptions, type CropFocusPosition, type CropFocusStrategy, type CropObjectType, type CropOptions, type CropType, type DevicePixelRatio, type EffectOptions, type FormatOptions, type HexColor, type ImageFormat, type PixelFiddlerConfig, type Position, type ResizeMode, type ResizeOptions, type RotationAngle, type TextOptions, type TransformationOptions, type UrlBuilderConfig, type WatermarkOptions, buildQueryParams, buildTransformationUrl };
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Helper type to extract values from const objects
3
+ */
4
+ type ObjectValue<T> = T[keyof T];
5
+ /**
6
+ * Resize mode for image transformations
7
+ */
8
+ type ResizeMode = 'FILL' | 'FIT' | 'PAD' | 'FORCE' | 'COVER';
9
+ /**
10
+ * Output image format - null or empty means AUTO
11
+ */
12
+ type ImageFormat = 'jpeg' | 'png' | 'webp' | 'avif' | 'gif';
13
+ /**
14
+ * Position for text overlays and watermarks
15
+ */
16
+ type Position = 'TOP_LEFT' | 'TOP_CENTER' | 'TOP_RIGHT' | 'CENTER_LEFT' | 'CENTER' | 'CENTER_RIGHT' | 'BOTTOM_LEFT' | 'BOTTOM_CENTER' | 'BOTTOM_RIGHT';
17
+ /**
18
+ * Crop focus type
19
+ */
20
+ declare const CROP_FOCUS: {
21
+ readonly SPECIFIED: "SPECIFIED";
22
+ readonly AUTO: "AUTO";
23
+ };
24
+ type CropFocus = ObjectValue<typeof CROP_FOCUS>;
25
+ /**
26
+ * Crop focus strategy for AUTO focus
27
+ */
28
+ declare const CROP_FOCUS_STRATEGY: {
29
+ readonly CENTER: "CENTER";
30
+ readonly SMART: "SMART";
31
+ readonly DETAIL: "DETAIL";
32
+ };
33
+ type CropFocusStrategy = ObjectValue<typeof CROP_FOCUS_STRATEGY>;
34
+ /**
35
+ * Crop type
36
+ */
37
+ declare const CROP_TYPE: {
38
+ readonly SIMPLE: "SIMPLE";
39
+ readonly OBJECT: "OBJECT";
40
+ };
41
+ type CropType = ObjectValue<typeof CROP_TYPE>;
42
+ /**
43
+ * Object type for smart cropping
44
+ */
45
+ declare const CROP_OBJECT_TYPE: {
46
+ readonly FACE: "FACE";
47
+ readonly UPPER_BODY: "UPPER_BODY";
48
+ };
49
+ type CropObjectType = ObjectValue<typeof CROP_OBJECT_TYPE>;
50
+ /**
51
+ * Rotation angle in degrees (clockwise)
52
+ */
53
+ type RotationAngle = 0 | 90 | 180 | 270;
54
+ /**
55
+ * Device pixel ratio for high-density displays
56
+ */
57
+ type DevicePixelRatio = 1 | 2 | 3 | 4;
58
+ /**
59
+ * Hex color string (with or without #)
60
+ */
61
+ type HexColor = string;
62
+ /**
63
+ * Resize transformation options
64
+ */
65
+ interface ResizeOptions {
66
+ /** Target width in pixels (1-4096) */
67
+ width?: number;
68
+ /** Target height in pixels (1-4096) */
69
+ height?: number;
70
+ /** Device pixel ratio for retina displays */
71
+ dpr?: DevicePixelRatio;
72
+ /** Resize strategy */
73
+ mode?: ResizeMode;
74
+ /** Background color for PAD mode (hex) */
75
+ background?: HexColor;
76
+ }
77
+ /**
78
+ * Format and quality options
79
+ */
80
+ interface FormatOptions {
81
+ /** Output format */
82
+ format?: ImageFormat;
83
+ /** Quality level (1-100) */
84
+ quality?: number;
85
+ /** Strip metadata from output */
86
+ stripMetadata?: boolean;
87
+ }
88
+ /**
89
+ * Effect transformation options
90
+ */
91
+ interface EffectOptions {
92
+ /** Gaussian blur intensity (0-100) */
93
+ blur?: number;
94
+ /** Brightness adjustment (0-100) */
95
+ brightness?: number;
96
+ /** Contrast adjustment (0-100) */
97
+ contrast?: number;
98
+ /** Convert to grayscale */
99
+ grayscale?: boolean;
100
+ /** Saturation adjustment (0-100) */
101
+ saturation?: number;
102
+ /** Apply sepia effect */
103
+ sepia?: boolean;
104
+ /** Sharpen intensity (0-100) */
105
+ sharpen?: number;
106
+ /** Add noise intensity (0-100) */
107
+ noise?: number;
108
+ /** Rotation angle */
109
+ rotate?: RotationAngle;
110
+ }
111
+ /**
112
+ * Border options
113
+ */
114
+ interface BorderOptions {
115
+ /** Border width in pixels */
116
+ width?: number;
117
+ /** Border color (hex) */
118
+ color?: HexColor;
119
+ /** Corner radius in pixels (1-2000) */
120
+ radius?: number;
121
+ }
122
+ /**
123
+ * Crop focus position for SPECIFIED focus type
124
+ */
125
+ interface CropFocusPosition {
126
+ x?: number;
127
+ y?: number;
128
+ }
129
+ /**
130
+ * Crop focus options
131
+ */
132
+ interface CropFocusOptions {
133
+ /** Focus type - SPECIFIED (manual position) or AUTO (strategy-based) */
134
+ type?: CropFocus;
135
+ /** Manual focus position when type is SPECIFIED */
136
+ position?: CropFocusPosition;
137
+ /** Auto focus strategy when type is AUTO */
138
+ strategy?: CropFocusStrategy;
139
+ }
140
+ /**
141
+ * Crop options
142
+ */
143
+ interface CropOptions {
144
+ /** Crop type - SIMPLE or OBJECT */
145
+ type?: CropType;
146
+ /** Object type for OBJECT crop type */
147
+ objectType?: CropObjectType;
148
+ /** Focus options */
149
+ focus?: CropFocusOptions;
150
+ /** Crop width in pixels */
151
+ width?: number;
152
+ /** Crop height in pixels */
153
+ height?: number;
154
+ /** Apply crop after resize */
155
+ afterResize?: boolean;
156
+ }
157
+ /**
158
+ * Text overlay options
159
+ */
160
+ interface TextOptions {
161
+ /** Text content to render */
162
+ text?: string;
163
+ /** Text color (hex) */
164
+ color?: HexColor;
165
+ /** Text opacity (0-100) */
166
+ opacity?: number;
167
+ /** Background color behind text (hex) */
168
+ background?: HexColor;
169
+ /** Background opacity (0-100) */
170
+ backgroundOpacity?: number;
171
+ /** Text size as percentage of image (1-100) */
172
+ size?: number;
173
+ /** Font family name */
174
+ font?: string;
175
+ /** Font size in pixels */
176
+ fontSize?: number;
177
+ /** Text position */
178
+ position?: Position;
179
+ }
180
+ /**
181
+ * Watermark options
182
+ */
183
+ interface WatermarkOptions {
184
+ /** Watermark name (predefined in dashboard) */
185
+ name?: string;
186
+ /** Watermark opacity (0-100) */
187
+ opacity?: number;
188
+ /** Watermark size as percentage (1-100) */
189
+ size?: number;
190
+ /** Watermark width in pixels */
191
+ width?: number;
192
+ /** Watermark height in pixels */
193
+ height?: number;
194
+ /** Watermark position */
195
+ position?: Position;
196
+ }
197
+ /**
198
+ * All transformation options combined
199
+ */
200
+ interface TransformationOptions extends ResizeOptions, FormatOptions, EffectOptions {
201
+ /** Border options */
202
+ border?: BorderOptions;
203
+ /** Crop options */
204
+ crop?: CropOptions;
205
+ /** Text overlay options */
206
+ text?: TextOptions;
207
+ /** Watermark options */
208
+ watermark?: WatermarkOptions;
209
+ /** Use predefined transformation alias */
210
+ alias?: string;
211
+ /** Return original image without transformations */
212
+ original?: boolean;
213
+ }
214
+ /**
215
+ * Configuration for PixelFiddler client
216
+ */
217
+ interface PixelFiddlerConfig {
218
+ /** Base URL for the CDN (defaults to https://cdn.pixel-fiddler.com) */
219
+ baseUrl?: string;
220
+ /** Secret key for URL signing */
221
+ signatureKey?: string;
222
+ }
223
+ /**
224
+ * URL builder configuration (internal)
225
+ */
226
+ interface UrlBuilderConfig extends PixelFiddlerConfig {
227
+ /** Resolved base URL */
228
+ baseUrl: string;
229
+ }
230
+
231
+ /**
232
+ * Determines which parameter names to use in the query string.
233
+ * - `'short'` - Use abbreviated aliases (e.g., `w`, `h`, `q`)
234
+ * - `'long'` - Use full parameter names (e.g., `width`, `height`, `quality`)
235
+ */
236
+ type NameMode = 'short' | 'long';
237
+ /**
238
+ * Converts transformation options into a URL-encoded query string.
239
+ *
240
+ * Iterates through all defined parameters in the PARAMS config, extracts values
241
+ * from the options object using their paths, applies any normalization functions
242
+ * (e.g., stripping `#` from hex colors), and returns a URL-encoded query string.
243
+ *
244
+ * @param options - The transformation options object
245
+ * @param mode - Whether to use short aliases or long parameter names (default: `'short'`)
246
+ * @returns URL-encoded query string (without leading `?`)
247
+ *
248
+ * @example
249
+ * ```ts
250
+ * // Using short aliases (default)
251
+ * buildQueryParams({ width: 800, height: 600, quality: 80 });
252
+ * // => 'w=800&h=600&q=80'
253
+ *
254
+ * // Using long parameter names
255
+ * buildQueryParams({ width: 800, format: 'webp' }, 'long');
256
+ * // => 'width=800&format=webp'
257
+ *
258
+ * // Nested options
259
+ * buildQueryParams({ border: { width: 2, color: '#ff0000' } });
260
+ * // => 'bo.w=2&bo.c=ff0000'
261
+ * ```
262
+ */
263
+ declare const buildQueryParams: (options: TransformationOptions, mode?: NameMode) => string;
264
+ /**
265
+ * Builds a complete transformation URL from a base URL and transformation options.
266
+ *
267
+ * Combines the configured base URL with query parameters generated from the
268
+ * transformation options. Handles the special `original` flag which returns
269
+ * the unmodified source image.
270
+ *
271
+ * @param config - Configuration containing the base URL for the image
272
+ * @param options - The transformation options to apply
273
+ * @param mode - Whether to use short aliases or long parameter names (default: `'short'`)
274
+ * @returns The complete URL string with query parameters
275
+ *
276
+ * @example
277
+ * ```ts
278
+ * const config = { baseUrl: 'https://cdn.example.com/images/photo.jpg' };
279
+ *
280
+ * // Basic resize
281
+ * buildTransformationUrl(config, { width: 400, height: 300 });
282
+ * // => 'https://cdn.example.com/images/photo.jpg?w=400&h=300'
283
+ *
284
+ * // Multiple transformations
285
+ * buildTransformationUrl(config, {
286
+ * width: 800,
287
+ * format: 'webp',
288
+ * quality: 85,
289
+ * blur: 5
290
+ * });
291
+ * // => 'https://cdn.example.com/images/photo.jpg?w=800&f=webp&q=85&bl=5'
292
+ *
293
+ * // Request original image (no transformations)
294
+ * buildTransformationUrl(config, { original: true });
295
+ * // => 'https://cdn.example.com/images/photo.jpg?original'
296
+ *
297
+ * // Using long parameter names
298
+ * buildTransformationUrl(config, { width: 400 }, 'long');
299
+ * // => 'https://cdn.example.com/images/photo.jpg?width=400'
300
+ * ```
301
+ */
302
+ declare const buildTransformationUrl: (config: UrlBuilderConfig, options: TransformationOptions, mode?: NameMode) => string;
303
+
304
+ export { type BorderOptions, CROP_FOCUS, CROP_FOCUS_STRATEGY, CROP_OBJECT_TYPE, CROP_TYPE, type CropFocus, type CropFocusOptions, type CropFocusPosition, type CropFocusStrategy, type CropObjectType, type CropOptions, type CropType, type DevicePixelRatio, type EffectOptions, type FormatOptions, type HexColor, type ImageFormat, type PixelFiddlerConfig, type Position, type ResizeMode, type ResizeOptions, type RotationAngle, type TextOptions, type TransformationOptions, type UrlBuilderConfig, type WatermarkOptions, buildQueryParams, buildTransformationUrl };
package/dist/index.js ADDED
@@ -0,0 +1,125 @@
1
+ // src/types.ts
2
+ var CROP_FOCUS = {
3
+ SPECIFIED: "SPECIFIED",
4
+ AUTO: "AUTO"
5
+ };
6
+ var CROP_FOCUS_STRATEGY = {
7
+ CENTER: "CENTER",
8
+ SMART: "SMART",
9
+ DETAIL: "DETAIL"
10
+ };
11
+ var CROP_TYPE = {
12
+ SIMPLE: "SIMPLE",
13
+ OBJECT: "OBJECT"
14
+ };
15
+ var CROP_OBJECT_TYPE = {
16
+ FACE: "FACE",
17
+ UPPER_BODY: "UPPER_BODY"
18
+ };
19
+
20
+ // src/utils.ts
21
+ var normalizeHex = (value) => value.replace("#", "");
22
+ var removeTrailingSlash = (baseUrl) => {
23
+ return baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
24
+ };
25
+ var getNestedValue = (obj, path) => path.reduce(
26
+ (acc, key) => acc == null ? void 0 : acc[key],
27
+ obj
28
+ );
29
+
30
+ // src/config.ts
31
+ var PARAMS = [
32
+ { path: ["width"], long: "width", short: "w" },
33
+ { path: ["height"], long: "height", short: "h" },
34
+ { path: ["dpr"], long: "dpr", short: "dpr" },
35
+ { path: ["mode"], long: "mode", short: "rm" },
36
+ {
37
+ path: ["background"],
38
+ long: "background",
39
+ short: "bg",
40
+ normalize: (v) => typeof v === "string" ? normalizeHex(v) : v
41
+ },
42
+ { path: ["format"], long: "format", short: "f" },
43
+ { path: ["quality"], long: "quality", short: "q" },
44
+ { path: ["stripMetadata"], long: "stripMetadata", short: "st" },
45
+ { path: ["blur"], long: "blur", short: "bl" },
46
+ { path: ["brightness"], long: "brightness", short: "br" },
47
+ { path: ["contrast"], long: "contrast", short: "ct" },
48
+ { path: ["grayscale"], long: "grayscale", short: "gs" },
49
+ { path: ["saturation"], long: "saturation", short: "sa" },
50
+ { path: ["sepia"], long: "sepia", short: "sp" },
51
+ { path: ["sharpen"], long: "sharpen", short: "sh" },
52
+ { path: ["noise"], long: "noise", short: "ns" },
53
+ { path: ["rotate"], long: "rotate", short: "rt" },
54
+ { path: ["border", "width"], long: "border.width", short: "bo.w" },
55
+ {
56
+ path: ["border", "color"],
57
+ long: "border.color",
58
+ short: "bo.c",
59
+ normalize: (v) => typeof v === "string" ? normalizeHex(v) : v
60
+ },
61
+ { path: ["border", "radius"], long: "border.radius", short: "bo.r" },
62
+ { path: ["crop", "type"], long: "crop.type", short: "cr.t" },
63
+ { path: ["crop", "objectType"], long: "crop.objectType", short: "cr.o" },
64
+ { path: ["crop", "focus", "position", "x"], long: "crop.x", short: "cr.x" },
65
+ { path: ["crop", "focus", "position", "y"], long: "crop.y", short: "cr.y" },
66
+ { path: ["crop", "focus", "strategy"], long: "crop", short: "cr" },
67
+ { path: ["crop", "width"], long: "crop.width", short: "cr.w" },
68
+ { path: ["crop", "height"], long: "crop.height", short: "cr.h" },
69
+ { path: ["crop", "afterResize"], long: "crop.afterResize", short: "cr.ar" },
70
+ { path: ["text", "text"], long: "text", short: "tx" },
71
+ {
72
+ path: ["text", "color"],
73
+ long: "text.color",
74
+ short: "tx.c",
75
+ normalize: (v) => typeof v === "string" ? normalizeHex(v) : v
76
+ },
77
+ { path: ["text", "opacity"], long: "text.opacity", short: "tx.o" },
78
+ { path: ["text", "size"], long: "text.size", short: "tx.s" },
79
+ { path: ["text", "font"], long: "text.font", short: "tx.f" },
80
+ { path: ["text", "fontSize"], long: "text.fontSize", short: "tx.fs" },
81
+ { path: ["text", "position"], long: "text.position", short: "tx.p" },
82
+ {
83
+ path: ["text", "background"],
84
+ long: "text.background",
85
+ short: "tx.bg",
86
+ normalize: (v) => typeof v === "string" ? normalizeHex(v) : v
87
+ },
88
+ { path: ["text", "backgroundOpacity"], long: "text.backgroundOpacity", short: "tx.bg.o" },
89
+ { path: ["watermark", "name"], long: "watermark", short: "wm" },
90
+ { path: ["watermark", "opacity"], long: "watermark.opacity", short: "wm.o" },
91
+ { path: ["watermark", "size"], long: "watermark.size", short: "wm.s" },
92
+ { path: ["watermark", "width"], long: "watermark.width", short: "wm.w" },
93
+ { path: ["watermark", "height"], long: "watermark.height", short: "wm.h" },
94
+ { path: ["watermark", "position"], long: "watermark.position", short: "wm.p" }
95
+ ];
96
+
97
+ // src/url-builder.ts
98
+ var buildQueryParams = (options, mode = "short") => {
99
+ const params = [];
100
+ for (const spec of PARAMS) {
101
+ const value = getNestedValue(options, spec.path);
102
+ if (value == null) continue;
103
+ const name = mode === "short" && spec.short ? spec.short : spec.long;
104
+ let normalized = value;
105
+ if ("normalize" in spec) {
106
+ normalized = spec.normalize(value);
107
+ }
108
+ params.push([name, String(normalized)]);
109
+ }
110
+ return new URLSearchParams(
111
+ params.map(([k, v]) => [k, v])
112
+ ).toString();
113
+ };
114
+ var buildTransformationUrl = (config, options, mode = "short") => {
115
+ if (options.original) {
116
+ return `${config.baseUrl}?original`;
117
+ }
118
+ const query = buildQueryParams(options, mode);
119
+ const baseUrl = removeTrailingSlash(config.baseUrl);
120
+ return `${baseUrl}?${query}`;
121
+ };
122
+
123
+ export { CROP_FOCUS, CROP_FOCUS_STRATEGY, CROP_OBJECT_TYPE, CROP_TYPE, buildQueryParams, buildTransformationUrl };
124
+ //# sourceMappingURL=index.js.map
125
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/utils.ts","../src/config.ts","../src/url-builder.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * Helper type to extract values from const objects\r\n */\r\ntype ObjectValue<T> = T[keyof T];\r\n\r\n/**\r\n * Resize mode for image transformations\r\n */\r\nexport type ResizeMode = 'FILL' | 'FIT' | 'PAD' | 'FORCE' | 'COVER';\r\n\r\n/**\r\n * Output image format - null or empty means AUTO\r\n */\r\nexport type ImageFormat = 'jpeg' | 'png' | 'webp' | 'avif' | 'gif'\r\n\r\n/**\r\n * Position for text overlays and watermarks\r\n */\r\nexport type Position =\r\n | 'TOP_LEFT'\r\n | 'TOP_CENTER'\r\n | 'TOP_RIGHT'\r\n | 'CENTER_LEFT'\r\n | 'CENTER'\r\n | 'CENTER_RIGHT'\r\n | 'BOTTOM_LEFT'\r\n | 'BOTTOM_CENTER'\r\n | 'BOTTOM_RIGHT';\r\n\r\n/**\r\n * Crop focus type\r\n */\r\nexport const CROP_FOCUS = {\r\n SPECIFIED: 'SPECIFIED',\r\n AUTO: 'AUTO',\r\n} as const;\r\n\r\nexport type CropFocus = ObjectValue<typeof CROP_FOCUS>;\r\n\r\n/**\r\n * Crop focus strategy for AUTO focus\r\n */\r\nexport const CROP_FOCUS_STRATEGY = {\r\n CENTER: 'CENTER',\r\n SMART: 'SMART',\r\n DETAIL: 'DETAIL',\r\n} as const;\r\n\r\nexport type CropFocusStrategy = ObjectValue<typeof CROP_FOCUS_STRATEGY>;\r\n\r\n/**\r\n * Crop type\r\n */\r\nexport const CROP_TYPE = {\r\n SIMPLE: 'SIMPLE',\r\n OBJECT: 'OBJECT',\r\n} as const;\r\n\r\nexport type CropType = ObjectValue<typeof CROP_TYPE>;\r\n\r\n/**\r\n * Object type for smart cropping\r\n */\r\nexport const CROP_OBJECT_TYPE = {\r\n FACE: 'FACE',\r\n UPPER_BODY: 'UPPER_BODY',\r\n} as const;\r\n\r\nexport type CropObjectType = ObjectValue<typeof CROP_OBJECT_TYPE>;\r\n\r\n/**\r\n * Rotation angle in degrees (clockwise)\r\n */\r\nexport type RotationAngle = 0 | 90 | 180 | 270;\r\n\r\n/**\r\n * Device pixel ratio for high-density displays\r\n */\r\nexport type DevicePixelRatio = 1 | 2 | 3 | 4;\r\n\r\n/**\r\n * Hex color string (with or without #)\r\n */\r\nexport type HexColor = string;\r\n\r\n/**\r\n * Resize transformation options\r\n */\r\nexport interface ResizeOptions {\r\n /** Target width in pixels (1-4096) */\r\n width?: number;\r\n /** Target height in pixels (1-4096) */\r\n height?: number;\r\n /** Device pixel ratio for retina displays */\r\n dpr?: DevicePixelRatio;\r\n /** Resize strategy */\r\n mode?: ResizeMode;\r\n /** Background color for PAD mode (hex) */\r\n background?: HexColor;\r\n}\r\n\r\n/**\r\n * Format and quality options\r\n */\r\nexport interface FormatOptions {\r\n /** Output format */\r\n format?: ImageFormat;\r\n /** Quality level (1-100) */\r\n quality?: number;\r\n /** Strip metadata from output */\r\n stripMetadata?: boolean;\r\n}\r\n\r\n/**\r\n * Effect transformation options\r\n */\r\nexport interface EffectOptions {\r\n /** Gaussian blur intensity (0-100) */\r\n blur?: number;\r\n /** Brightness adjustment (0-100) */\r\n brightness?: number;\r\n /** Contrast adjustment (0-100) */\r\n contrast?: number;\r\n /** Convert to grayscale */\r\n grayscale?: boolean;\r\n /** Saturation adjustment (0-100) */\r\n saturation?: number;\r\n /** Apply sepia effect */\r\n sepia?: boolean;\r\n /** Sharpen intensity (0-100) */\r\n sharpen?: number;\r\n /** Add noise intensity (0-100) */\r\n noise?: number;\r\n /** Rotation angle */\r\n rotate?: RotationAngle;\r\n}\r\n\r\n/**\r\n * Border options\r\n */\r\nexport interface BorderOptions {\r\n /** Border width in pixels */\r\n width?: number;\r\n /** Border color (hex) */\r\n color?: HexColor;\r\n /** Corner radius in pixels (1-2000) */\r\n radius?: number;\r\n}\r\n\r\n/**\r\n * Crop focus position for SPECIFIED focus type\r\n */\r\nexport interface CropFocusPosition {\r\n x?: number;\r\n y?: number;\r\n}\r\n\r\n/**\r\n * Crop focus options\r\n */\r\nexport interface CropFocusOptions {\r\n /** Focus type - SPECIFIED (manual position) or AUTO (strategy-based) */\r\n type?: CropFocus;\r\n /** Manual focus position when type is SPECIFIED */\r\n position?: CropFocusPosition;\r\n /** Auto focus strategy when type is AUTO */\r\n strategy?: CropFocusStrategy;\r\n}\r\n\r\n/**\r\n * Crop options\r\n */\r\nexport interface CropOptions {\r\n /** Crop type - SIMPLE or OBJECT */\r\n type?: CropType;\r\n /** Object type for OBJECT crop type */\r\n objectType?: CropObjectType;\r\n /** Focus options */\r\n focus?: CropFocusOptions;\r\n /** Crop width in pixels */\r\n width?: number;\r\n /** Crop height in pixels */\r\n height?: number;\r\n /** Apply crop after resize */\r\n afterResize?: boolean;\r\n}\r\n\r\n/**\r\n * Text overlay options\r\n */\r\nexport interface TextOptions {\r\n /** Text content to render */\r\n text?: string;\r\n /** Text color (hex) */\r\n color?: HexColor;\r\n /** Text opacity (0-100) */\r\n opacity?: number;\r\n /** Background color behind text (hex) */\r\n background?: HexColor;\r\n /** Background opacity (0-100) */\r\n backgroundOpacity?: number;\r\n /** Text size as percentage of image (1-100) */\r\n size?: number;\r\n /** Font family name */\r\n font?: string;\r\n /** Font size in pixels */\r\n fontSize?: number;\r\n /** Text position */\r\n position?: Position;\r\n}\r\n\r\n/**\r\n * Watermark options\r\n */\r\nexport interface WatermarkOptions {\r\n /** Watermark name (predefined in dashboard) */\r\n name?: string;\r\n /** Watermark opacity (0-100) */\r\n opacity?: number;\r\n /** Watermark size as percentage (1-100) */\r\n size?: number;\r\n /** Watermark width in pixels */\r\n width?: number;\r\n /** Watermark height in pixels */\r\n height?: number;\r\n /** Watermark position */\r\n position?: Position;\r\n}\r\n\r\n/**\r\n * All transformation options combined\r\n */\r\nexport interface TransformationOptions\r\n extends ResizeOptions,\r\n FormatOptions,\r\n EffectOptions {\r\n /** Border options */\r\n border?: BorderOptions;\r\n /** Crop options */\r\n crop?: CropOptions;\r\n /** Text overlay options */\r\n text?: TextOptions;\r\n /** Watermark options */\r\n watermark?: WatermarkOptions;\r\n /** Use predefined transformation alias */\r\n alias?: string;\r\n /** Return original image without transformations */\r\n original?: boolean;\r\n}\r\n\r\n/**\r\n * Configuration for PixelFiddler client\r\n */\r\nexport interface PixelFiddlerConfig {\r\n /** Base URL for the CDN (defaults to https://cdn.pixel-fiddler.com) */\r\n baseUrl?: string;\r\n /** Secret key for URL signing */\r\n signatureKey?: string;\r\n}\r\n\r\n/**\r\n * URL builder configuration (internal)\r\n */\r\nexport interface UrlBuilderConfig extends PixelFiddlerConfig {\r\n /** Resolved base URL */\r\n baseUrl: string;\r\n}\r\n\r\n/**\r\n * Mapping of transformation options to query parameter names\r\n */\r\nexport interface QueryParameterMapping {\r\n /** Full parameter name */\r\n name: string;\r\n /** Short alias */\r\n alias?: string;\r\n}\r\n\r\n\r\n","import { Path, PathValue } from './util-types';\r\n\r\nexport const normalizeHex = (value: string) => value.replace('#', '')\r\n\r\n\r\nexport const removeTrailingSlash = (baseUrl: string) => {\r\n return baseUrl.endsWith('/')\r\n ? baseUrl.slice(0, -1)\r\n : baseUrl;\r\n}\r\n\r\nexport const getNestedValue = <\r\n T,\r\n P extends Path<T>\r\n>(\r\n obj: T,\r\n path: P\r\n): PathValue<T, P> | undefined =>\r\n path.reduce<any>(\r\n (acc, key) => (acc == null ? undefined : acc[key]),\r\n obj\r\n );\r\n\r\n","import { ParamSpec, Path } from './util-types';\r\nimport { TransformationOptions } from './types';\r\nimport { normalizeHex } from './utils';\r\n\r\nexport const PARAMS = [\r\n {path: ['width'], long: 'width', short: 'w'},\r\n {path: ['height'], long: 'height', short: 'h'},\r\n {path: ['dpr'], long: 'dpr', short: 'dpr'},\r\n {path: ['mode'], long: 'mode', short: 'rm'},\r\n {\r\n path: ['background'],\r\n long: 'background',\r\n short: 'bg',\r\n normalize: (v) => typeof v === 'string' ? normalizeHex(v) : v\r\n },\r\n\r\n {path: ['format'], long: 'format', short: 'f'},\r\n {path: ['quality'], long: 'quality', short: 'q'},\r\n {path: ['stripMetadata'], long: 'stripMetadata', short: 'st'},\r\n\r\n {path: ['blur'], long: 'blur', short: 'bl'},\r\n {path: ['brightness'], long: 'brightness', short: 'br'},\r\n {path: ['contrast'], long: 'contrast', short: 'ct'},\r\n {path: ['grayscale'], long: 'grayscale', short: 'gs'},\r\n {path: ['saturation'], long: 'saturation', short: 'sa'},\r\n {path: ['sepia'], long: 'sepia', short: 'sp'},\r\n {path: ['sharpen'], long: 'sharpen', short: 'sh'},\r\n {path: ['noise'], long: 'noise', short: 'ns'},\r\n {path: ['rotate'], long: 'rotate', short: 'rt'},\r\n\r\n {path: ['border', 'width'], long: 'border.width', short: 'bo.w'},\r\n {\r\n path: ['border', 'color'],\r\n long: 'border.color',\r\n short: 'bo.c',\r\n normalize: (v) => typeof v === 'string' ? normalizeHex(v) : v\r\n },\r\n {path: ['border', 'radius'], long: 'border.radius', short: 'bo.r'},\r\n\r\n {path: ['crop', 'type'], long: 'crop.type', short: 'cr.t'},\r\n {path: ['crop', 'objectType'], long: 'crop.objectType', short: 'cr.o'},\r\n {path: ['crop', 'focus', 'position', 'x'], long: 'crop.x', short: 'cr.x'},\r\n {path: ['crop', 'focus', 'position', 'y'], long: 'crop.y', short: 'cr.y'},\r\n {path: ['crop', 'focus', 'strategy'], long: 'crop', short: 'cr'},\r\n {path: ['crop', 'width'], long: 'crop.width', short: 'cr.w'},\r\n {path: ['crop', 'height'], long: 'crop.height', short: 'cr.h'},\r\n {path: ['crop', 'afterResize'], long: 'crop.afterResize', short: 'cr.ar'},\r\n\r\n {path: ['text', 'text'], long: 'text', short: 'tx'},\r\n {\r\n path: ['text', 'color'],\r\n long: 'text.color',\r\n short: 'tx.c',\r\n normalize: (v) => typeof v === 'string' ? normalizeHex(v) : v\r\n },\r\n {path: ['text', 'opacity'], long: 'text.opacity', short: 'tx.o'},\r\n {path: ['text', 'size'], long: 'text.size', short: 'tx.s'},\r\n {path: ['text', 'font'], long: 'text.font', short: 'tx.f'},\r\n {path: ['text', 'fontSize'], long: 'text.fontSize', short: 'tx.fs'},\r\n {path: ['text', 'position'], long: 'text.position', short: 'tx.p'},\r\n {\r\n path: ['text', 'background'],\r\n long: 'text.background',\r\n short: 'tx.bg',\r\n normalize: (v) => typeof v === 'string' ? normalizeHex(v) : v\r\n },\r\n {path: ['text', 'backgroundOpacity'], long: 'text.backgroundOpacity', short: 'tx.bg.o'},\r\n\r\n {path: ['watermark', 'name'], long: 'watermark', short: 'wm'},\r\n {path: ['watermark', 'opacity'], long: 'watermark.opacity', short: 'wm.o'},\r\n {path: ['watermark', 'size'], long: 'watermark.size', short: 'wm.s'},\r\n {path: ['watermark', 'width'], long: 'watermark.width', short: 'wm.w'},\r\n {path: ['watermark', 'height'], long: 'watermark.height', short: 'wm.h'},\r\n {path: ['watermark', 'position'], long: 'watermark.position', short: 'wm.p'},\r\n] as const satisfies readonly ParamSpec<\r\n TransformationOptions,\r\n Path<TransformationOptions>\r\n>[];\r\n","import { TransformationOptions, UrlBuilderConfig } from './types';\r\nimport { getNestedValue, removeTrailingSlash } from './utils';\r\nimport { PARAMS } from './config';\r\n\r\n/**\r\n * Determines which parameter names to use in the query string.\r\n * - `'short'` - Use abbreviated aliases (e.g., `w`, `h`, `q`)\r\n * - `'long'` - Use full parameter names (e.g., `width`, `height`, `quality`)\r\n */\r\ntype NameMode = 'short' | 'long';\r\n\r\n/**\r\n * Converts transformation options into a URL-encoded query string.\r\n *\r\n * Iterates through all defined parameters in the PARAMS config, extracts values\r\n * from the options object using their paths, applies any normalization functions\r\n * (e.g., stripping `#` from hex colors), and returns a URL-encoded query string.\r\n *\r\n * @param options - The transformation options object\r\n * @param mode - Whether to use short aliases or long parameter names (default: `'short'`)\r\n * @returns URL-encoded query string (without leading `?`)\r\n *\r\n * @example\r\n * ```ts\r\n * // Using short aliases (default)\r\n * buildQueryParams({ width: 800, height: 600, quality: 80 });\r\n * // => 'w=800&h=600&q=80'\r\n *\r\n * // Using long parameter names\r\n * buildQueryParams({ width: 800, format: 'webp' }, 'long');\r\n * // => 'width=800&format=webp'\r\n *\r\n * // Nested options\r\n * buildQueryParams({ border: { width: 2, color: '#ff0000' } });\r\n * // => 'bo.w=2&bo.c=ff0000'\r\n * ```\r\n */\r\nexport const buildQueryParams = (\r\n options: TransformationOptions,\r\n mode: NameMode = 'short'\r\n): string => {\r\n const params: Array<[string, string]> = [];\r\n\r\n for (const spec of PARAMS) {\r\n const value = getNestedValue(options, spec.path);\r\n if (value == null) continue;\r\n\r\n const name =\r\n mode === 'short' && spec.short ? spec.short : spec.long;\r\n\r\n let normalized = value\r\n if ('normalize' in spec) {\r\n normalized = spec.normalize(value)\r\n }\r\n\r\n params.push([name, String(normalized)]);\r\n }\r\n\r\n\r\n return new URLSearchParams(\r\n params.map(([k, v]) => [k, v])\r\n ).toString();\r\n\r\n};\r\n\r\n/**\r\n * Builds a complete transformation URL from a base URL and transformation options.\r\n *\r\n * Combines the configured base URL with query parameters generated from the\r\n * transformation options. Handles the special `original` flag which returns\r\n * the unmodified source image.\r\n *\r\n * @param config - Configuration containing the base URL for the image\r\n * @param options - The transformation options to apply\r\n * @param mode - Whether to use short aliases or long parameter names (default: `'short'`)\r\n * @returns The complete URL string with query parameters\r\n *\r\n * @example\r\n * ```ts\r\n * const config = { baseUrl: 'https://cdn.example.com/images/photo.jpg' };\r\n *\r\n * // Basic resize\r\n * buildTransformationUrl(config, { width: 400, height: 300 });\r\n * // => 'https://cdn.example.com/images/photo.jpg?w=400&h=300'\r\n *\r\n * // Multiple transformations\r\n * buildTransformationUrl(config, {\r\n * width: 800,\r\n * format: 'webp',\r\n * quality: 85,\r\n * blur: 5\r\n * });\r\n * // => 'https://cdn.example.com/images/photo.jpg?w=800&f=webp&q=85&bl=5'\r\n *\r\n * // Request original image (no transformations)\r\n * buildTransformationUrl(config, { original: true });\r\n * // => 'https://cdn.example.com/images/photo.jpg?original'\r\n *\r\n * // Using long parameter names\r\n * buildTransformationUrl(config, { width: 400 }, 'long');\r\n * // => 'https://cdn.example.com/images/photo.jpg?width=400'\r\n * ```\r\n */\r\nexport const buildTransformationUrl = (\r\n config: UrlBuilderConfig,\r\n options: TransformationOptions,\r\n mode: NameMode = 'short'\r\n) => {\r\n if (options.original) {\r\n return `${config.baseUrl}?original`;\r\n }\r\n\r\n const query = buildQueryParams(options, mode);\r\n\r\n const baseUrl = removeTrailingSlash(config.baseUrl)\r\n return `${baseUrl}?${query}`;\r\n};\r\n"]}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@pixelfiddler/core",
3
+ "version": "0.1.0",
4
+ "description": "Core utilities for PixelFiddler image transformation SDK",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "devDependencies": {
25
+ "@vitest/coverage-v8": "^4.0.18",
26
+ "tsup": "^8.0.1",
27
+ "typescript": "^5.3.3",
28
+ "vitest": "^4.0.18"
29
+ },
30
+ "keywords": [
31
+ "pixelfiddler",
32
+ "image",
33
+ "transformation",
34
+ "cdn",
35
+ "optimization",
36
+ "resize",
37
+ "crop",
38
+ "webp",
39
+ "avif"
40
+ ],
41
+ "license": "MIT",
42
+ "author": "PixelFiddler",
43
+ "homepage": "https://pixel-fiddler.com",
44
+ "bugs": {
45
+ "url": "https://github.com/pixelfiddler/js-sdk/issues"
46
+ },
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/pixelfiddler/js-sdk.git",
50
+ "directory": "packages/core"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "engines": {
56
+ "node": ">=18"
57
+ },
58
+ "sideEffects": false,
59
+ "scripts": {
60
+ "build": "tsup",
61
+ "dev": "tsup --watch",
62
+ "test": "vitest run",
63
+ "test:watch": "vitest",
64
+ "test:coverage": "vitest run --coverage",
65
+ "typecheck": "tsc --noEmit",
66
+ "lint": "eslint src --ext .ts",
67
+ "clean": "rm -rf dist coverage"
68
+ }
69
+ }