@immagin/client 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/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # @immagin/client
2
+
3
+ Node.js and browser client for the [Immagin](https://immag.in) image processing API.
4
+
5
+ Zero dependencies. Works everywhere `fetch` is available.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @immagin/client
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```ts
16
+ import { Immagin } from '@immagin/client'
17
+
18
+ const client = new Immagin({ apiKey: 'imk_...' })
19
+
20
+ // Get a processed image URL
21
+ const url = await client.images.url('photos/hero.jpg', {
22
+ size: [800, 600],
23
+ })
24
+
25
+ // Upload an image
26
+ const file = new Blob([buffer], { type: 'image/jpeg' })
27
+ await client.images.upload(file, { key: 'photos/hero.jpg' })
28
+ ```
29
+
30
+ ## Images
31
+
32
+ ### Get a processed URL
33
+
34
+ Returns a signed URL that serves the image through Immagin's processing pipeline.
35
+
36
+ ```ts
37
+ const url = await client.images.url('photo.jpg')
38
+ ```
39
+
40
+ With transformations:
41
+
42
+ ```ts
43
+ const url = await client.images.url('photo.jpg', {
44
+ size: [400], // width only
45
+ size: [400, 300], // width and height
46
+ text: {
47
+ text: 'Hello',
48
+ position: 'bottom-right', // top-left, top-right, bottom-left, bottom-right, center
49
+ fontSize: 24,
50
+ color: '#ffffff',
51
+ opacity: 0.8,
52
+ },
53
+ })
54
+ ```
55
+
56
+ ### Get a pre-signed upload URL
57
+
58
+ Returns a pre-signed S3 URL for direct upload. Useful when you need to handle the upload yourself (e.g. from a mobile app or with a custom upload flow).
59
+
60
+ ```ts
61
+ const { uploadUrl, key } = await client.images.signUrl('photos/hero.jpg', 'image/jpeg')
62
+
63
+ // Upload directly to S3 yourself
64
+ await fetch(uploadUrl, { method: 'PUT', body: file })
65
+ ```
66
+
67
+ ### Upload
68
+
69
+ Convenience method that gets a signed URL and uploads in one call. The file goes directly to S3 and never passes through the API.
70
+
71
+ ```ts
72
+ // Browser
73
+ const input = document.querySelector('input[type="file"]')
74
+ await client.images.upload(input.files[0], {
75
+ key: 'uploads/photo.jpg',
76
+ contentType: 'image/jpeg',
77
+ })
78
+
79
+ // Node.js
80
+ import { readFileSync } from 'node:fs'
81
+ const buffer = readFileSync('photo.jpg')
82
+ await client.images.upload(buffer, {
83
+ key: 'uploads/photo.jpg',
84
+ contentType: 'image/jpeg',
85
+ })
86
+ ```
87
+
88
+ ### List
89
+
90
+ ```ts
91
+ const result = await client.images.list()
92
+ // { images: [{ key, size, lastModified }], nextCursor? }
93
+
94
+ // With pagination
95
+ const page = await client.images.list({
96
+ prefix: 'uploads/',
97
+ limit: 50,
98
+ cursor: result.nextCursor,
99
+ })
100
+ ```
101
+
102
+ ### Delete
103
+
104
+ ```ts
105
+ await client.images.delete('uploads/photo.jpg')
106
+ ```
107
+
108
+ ## API Keys
109
+
110
+ Manage API keys programmatically. Useful for building admin tools or rotating keys.
111
+
112
+ ### Create
113
+
114
+ ```ts
115
+ const result = await client.keys.create({ name: 'Production' })
116
+ // { key: 'imk_...', prefix: 'imk_abcd', name: 'Production' }
117
+ // Save the key - it won't be shown again
118
+ ```
119
+
120
+ ### List
121
+
122
+ ```ts
123
+ const keys = await client.keys.list()
124
+ // [{ id, name, keyPrefix, createdAt, lastUsedAt }]
125
+ ```
126
+
127
+ ### Revoke
128
+
129
+ ```ts
130
+ await client.keys.revoke(keyId)
131
+ ```
132
+
133
+ ## Error handling
134
+
135
+ All API errors throw `ImmaginError` with the HTTP status and response body.
136
+
137
+ ```ts
138
+ import { Immagin, ImmaginError } from '@immagin/client'
139
+
140
+ try {
141
+ await client.images.delete('missing.jpg')
142
+ } catch (err) {
143
+ if (err instanceof ImmaginError) {
144
+ console.log(err.status) // 404
145
+ console.log(err.message) // "Not found"
146
+ }
147
+ }
148
+ ```
149
+
150
+ ## Configuration
151
+
152
+ ```ts
153
+ const client = new Immagin({
154
+ apiKey: 'imk_...', // Required
155
+ baseUrl: 'https://...', // Optional, defaults to https://gateway.immag.in
156
+ })
157
+ ```
158
+
159
+ ## License
160
+
161
+ MIT
@@ -0,0 +1,98 @@
1
+ //#region src/types.d.ts
2
+ type TextPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center';
3
+ interface TextOverlay {
4
+ text: string;
5
+ fontSize?: number;
6
+ color?: string;
7
+ opacity?: number;
8
+ position?: TextPosition;
9
+ padding?: number;
10
+ }
11
+ interface ImageEdits {
12
+ size?: [number] | [number, number];
13
+ text?: TextOverlay;
14
+ }
15
+ interface ImageUrlOptions extends ImageEdits {}
16
+ interface UploadOptions {
17
+ key: string;
18
+ contentType?: string;
19
+ }
20
+ interface UploadResult {
21
+ uploadUrl: string;
22
+ key: string;
23
+ }
24
+ interface ImageEntry {
25
+ key: string;
26
+ size: number;
27
+ lastModified: string;
28
+ }
29
+ interface ListImagesResult {
30
+ images: ImageEntry[];
31
+ nextCursor?: string;
32
+ }
33
+ interface ListImagesOptions {
34
+ prefix?: string;
35
+ cursor?: string;
36
+ limit?: number;
37
+ }
38
+ interface ApiKey {
39
+ id: number;
40
+ name: string;
41
+ keyPrefix: string;
42
+ createdAt: string;
43
+ lastUsedAt: string | null;
44
+ }
45
+ interface CreateKeyResult {
46
+ key: string;
47
+ prefix: string;
48
+ name: string;
49
+ }
50
+ interface ImmaginConfig {
51
+ apiKey: string;
52
+ baseUrl?: string;
53
+ }
54
+ //#endregion
55
+ //#region src/resources/images.d.ts
56
+ declare class ImagesResource {
57
+ private client;
58
+ constructor(client: Immagin);
59
+ url(key: string, options?: ImageUrlOptions): Promise<string>;
60
+ signUrl(key: string, contentType?: string): Promise<UploadResult>;
61
+ upload(file: Blob | Buffer | ReadableStream, options: UploadOptions): Promise<UploadResult>;
62
+ list(options?: ListImagesOptions): Promise<ListImagesResult>;
63
+ delete(key: string): Promise<void>;
64
+ }
65
+ //#endregion
66
+ //#region src/resources/keys.d.ts
67
+ declare class KeysResource {
68
+ private client;
69
+ constructor(client: Immagin);
70
+ create(options: {
71
+ name: string;
72
+ }): Promise<CreateKeyResult>;
73
+ list(): Promise<ApiKey[]>;
74
+ revoke(keyId: number): Promise<void>;
75
+ }
76
+ //#endregion
77
+ //#region src/client.d.ts
78
+ declare class Immagin {
79
+ private apiKey;
80
+ private baseUrl;
81
+ images: ImagesResource;
82
+ keys: KeysResource;
83
+ constructor(config: ImmaginConfig);
84
+ /** @internal */
85
+ request<T>(method: string, path: string, options?: {
86
+ body?: unknown;
87
+ params?: Record<string, string>;
88
+ }): Promise<T>;
89
+ }
90
+ //#endregion
91
+ //#region src/errors.d.ts
92
+ declare class ImmaginError extends Error {
93
+ status: number;
94
+ body?: unknown | undefined;
95
+ constructor(message: string, status: number, body?: unknown | undefined);
96
+ }
97
+ //#endregion
98
+ export { type ApiKey, type CreateKeyResult, type ImageEdits, type ImageEntry, type ImageUrlOptions, Immagin, type ImmaginConfig, ImmaginError, type ListImagesOptions, type ListImagesResult, type TextOverlay, type TextPosition, type UploadOptions, type UploadResult };
package/dist/index.mjs ADDED
@@ -0,0 +1,113 @@
1
+ //#region src/resources/images.ts
2
+ var ImagesResource = class {
3
+ constructor(client) {
4
+ this.client = client;
5
+ }
6
+ async url(key, options) {
7
+ const params = { key };
8
+ if (options?.size) {
9
+ params.width = String(options.size[0]);
10
+ if (options.size.length === 2) params.height = String(options.size[1]);
11
+ }
12
+ if (options?.text) {
13
+ params["text.text"] = options.text.text;
14
+ if (options.text.position) params["text.position"] = options.text.position;
15
+ if (options.text.fontSize) params["text.fontSize"] = String(options.text.fontSize);
16
+ if (options.text.opacity) params["text.opacity"] = String(options.text.opacity);
17
+ if (options.text.color) params["text.color"] = options.text.color;
18
+ }
19
+ return (await this.client.request("GET", "/v1/images/url", { params })).url;
20
+ }
21
+ async signUrl(key, contentType) {
22
+ return this.client.request("POST", "/v1/images/sign-url", { body: {
23
+ key,
24
+ contentType: contentType || "application/octet-stream"
25
+ } });
26
+ }
27
+ async upload(file, options) {
28
+ const { key, contentType } = options;
29
+ const signResult = await this.signUrl(key, contentType);
30
+ await fetch(signResult.uploadUrl, {
31
+ method: "PUT",
32
+ body: file,
33
+ headers: contentType ? { "content-type": contentType } : void 0
34
+ });
35
+ return signResult;
36
+ }
37
+ async list(options) {
38
+ const params = {};
39
+ if (options?.prefix) params.prefix = options.prefix;
40
+ if (options?.cursor) params.cursor = options.cursor;
41
+ if (options?.limit) params.limit = String(options.limit);
42
+ return this.client.request("GET", "/v1/images", { params });
43
+ }
44
+ async delete(key) {
45
+ await this.client.request("DELETE", `/v1/images/${key}`);
46
+ }
47
+ };
48
+
49
+ //#endregion
50
+ //#region src/resources/keys.ts
51
+ var KeysResource = class {
52
+ constructor(client) {
53
+ this.client = client;
54
+ }
55
+ async create(options) {
56
+ return this.client.request("POST", "/v1/keys", { body: options });
57
+ }
58
+ async list() {
59
+ return (await this.client.request("GET", "/v1/keys")).keys;
60
+ }
61
+ async revoke(keyId) {
62
+ await this.client.request("DELETE", `/v1/keys/${keyId}`);
63
+ }
64
+ };
65
+
66
+ //#endregion
67
+ //#region src/errors.ts
68
+ var ImmaginError = class extends Error {
69
+ constructor(message, status, body) {
70
+ super(message);
71
+ this.status = status;
72
+ this.body = body;
73
+ this.name = "ImmaginError";
74
+ }
75
+ };
76
+
77
+ //#endregion
78
+ //#region src/client.ts
79
+ const DEFAULT_BASE_URL = "https://gateway.immag.in";
80
+ var Immagin = class {
81
+ apiKey;
82
+ baseUrl;
83
+ images;
84
+ keys;
85
+ constructor(config) {
86
+ this.apiKey = config.apiKey;
87
+ this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
88
+ this.images = new ImagesResource(this);
89
+ this.keys = new KeysResource(this);
90
+ }
91
+ /** @internal */
92
+ async request(method, path, options) {
93
+ const url = new URL(path, this.baseUrl);
94
+ if (options?.params) {
95
+ for (const [key, value] of Object.entries(options.params)) if (value !== void 0) url.searchParams.set(key, value);
96
+ }
97
+ const headers = { authorization: `Bearer ${this.apiKey}` };
98
+ if (options?.body) headers["content-type"] = "application/json";
99
+ const response = await fetch(url.toString(), {
100
+ method,
101
+ headers,
102
+ body: options?.body ? JSON.stringify(options.body) : void 0
103
+ });
104
+ if (!response.ok) {
105
+ const body = await response.json().catch(() => null);
106
+ throw new ImmaginError(body?.error || `HTTP ${response.status}`, response.status, body);
107
+ }
108
+ return response.json();
109
+ }
110
+ };
111
+
112
+ //#endregion
113
+ export { Immagin, ImmaginError };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@immagin/client",
3
+ "version": "0.1.0",
4
+ "description": "Node.js and browser client for the Immagin image processing API",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "homepage": "https://immag.in",
8
+ "keywords": [
9
+ "immagin",
10
+ "image",
11
+ "image-processing",
12
+ "cdn",
13
+ "s3",
14
+ "upload",
15
+ "resize",
16
+ "api-client"
17
+ ],
18
+ "sideEffects": false,
19
+ "main": "./dist/index.mjs",
20
+ "types": "./dist/index.d.mts",
21
+ "exports": {
22
+ ".": {
23
+ "import": "./dist/index.mjs",
24
+ "types": "./dist/index.d.mts"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "README.md"
30
+ ],
31
+ "engines": {
32
+ "node": ">=18"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "devDependencies": {
38
+ "tsdown": "^0.20.3",
39
+ "typescript": "^5.7.2",
40
+ "vitest": "^3.0.5"
41
+ },
42
+ "scripts": {
43
+ "build": "tsdown",
44
+ "dev": "tsdown --watch",
45
+ "test": "vitest run"
46
+ }
47
+ }