@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 +161 -0
- package/dist/index.d.mts +98 -0
- package/dist/index.mjs +113 -0
- package/package.json +47 -0
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
|
package/dist/index.d.mts
ADDED
|
@@ -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
|
+
}
|