@immagin/client 0.2.0 → 0.2.2

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 CHANGED
@@ -17,19 +17,20 @@ import { Immagin } from '@immagin/client'
17
17
 
18
18
  const client = new Immagin({ apiKey: 'imk_...' })
19
19
 
20
- // Get a processed image URL
20
+ // Get a processed image URL (via API)
21
21
  const url = await client.images.url('photos/hero.jpg', {
22
22
  size: [800, 600],
23
23
  })
24
24
 
25
25
  // Upload an image
26
- const file = new Blob([buffer], { type: 'image/jpeg' })
27
- await client.images.upload(file, { key: 'photos/hero.jpg' })
26
+ import { readFileSync } from 'node:fs'
27
+ const buffer = readFileSync('photo.jpg')
28
+ await client.images.upload(buffer, 'photos/hero.jpg')
28
29
  ```
29
30
 
30
31
  ## Images
31
32
 
32
- ### Get a processed URL
33
+ ### Get a processed URL (via API)
33
34
 
34
35
  Returns a signed URL that serves the image through Immagin's processing pipeline.
35
36
 
@@ -41,8 +42,7 @@ With transformations:
41
42
 
42
43
  ```ts
43
44
  const url = await client.images.url('photo.jpg', {
44
- size: [400], // width only
45
- size: [400, 300], // width and height
45
+ size: [400, 300],
46
46
  text: {
47
47
  text: 'Hello',
48
48
  position: 'bottom-right', // top-left, top-right, bottom-left, bottom-right, center
@@ -53,36 +53,54 @@ const url = await client.images.url('photo.jpg', {
53
53
  })
54
54
  ```
55
55
 
56
- ### Get a pre-signed upload URL
56
+ ### Sign URLs locally (without API call)
57
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).
58
+ Generate signed image URLs on the server without making an API request. Requires `tenantId` and `tenantSecret` in the constructor.
59
59
 
60
60
  ```ts
61
- const { uploadUrl, key } = await client.images.signUrl('photos/hero.jpg', 'image/jpeg')
61
+ const client = new Immagin({
62
+ apiKey: 'imk_...',
63
+ tenantId: 'your-tenant-id',
64
+ tenantSecret: 'your-tenant-secret',
65
+ })
62
66
 
63
- // Upload directly to S3 yourself
64
- await fetch(uploadUrl, { method: 'PUT', body: file })
67
+ // Synchronous - no network request
68
+ const url = client.images.signedUrl('photo.jpg')
69
+
70
+ // With transformations
71
+ const thumb = client.images.signedUrl('photo.jpg', {
72
+ size: [800, 600],
73
+ text: { text: '© My Company', position: 'bottom-right', opacity: 0.5 },
74
+ })
65
75
  ```
66
76
 
67
- ### Upload
77
+ > **Note:** This uses `node:crypto` and is intended for server-side use only. Never expose your tenant secret in client-side code.
68
78
 
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.
79
+ ### Get a pre-signed upload URL
80
+
81
+ Returns a pre-signed S3 URL for direct upload (expires in 5 minutes). Useful for browser uploads where you don't want to expose your API key.
70
82
 
71
83
  ```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',
84
+ // Server: get the presigned URL
85
+ const { uploadUrl, key } = await client.images.signUrl('photos/hero.jpg')
86
+ // Return uploadUrl to the browser
87
+
88
+ // Browser: upload directly to S3
89
+ await fetch(uploadUrl, {
90
+ method: 'PUT',
91
+ body: file,
77
92
  })
93
+ ```
78
94
 
79
- // Node.js
95
+ ### Upload (Node.js)
96
+
97
+ Convenience method that gets a signed URL and uploads in one call. The file goes directly to S3 and never passes through the API.
98
+
99
+ ```ts
80
100
  import { readFileSync } from 'node:fs'
101
+
81
102
  const buffer = readFileSync('photo.jpg')
82
- await client.images.upload(buffer, {
83
- key: 'uploads/photo.jpg',
84
- contentType: 'image/jpeg',
85
- })
103
+ await client.images.upload(buffer, 'uploads/photo.jpg')
86
104
  ```
87
105
 
88
106
  ### List
@@ -107,7 +125,7 @@ await client.images.delete('uploads/photo.jpg')
107
125
 
108
126
  ## API Keys
109
127
 
110
- Manage API keys programmatically. Useful for building admin tools or rotating keys.
128
+ Manage API keys programmatically.
111
129
 
112
130
  ### Create
113
131
 
@@ -151,8 +169,10 @@ try {
151
169
 
152
170
  ```ts
153
171
  const client = new Immagin({
154
- apiKey: 'imk_...', // Required
155
- baseUrl: 'https://...', // Optional, defaults to https://gateway.immag.in
172
+ apiKey: 'imk_...', // Required
173
+ baseUrl: 'https://...', // Optional, defaults to https://gateway.immag.in
174
+ tenantId: 'your-tenant-id', // Optional, required for signedUrl()
175
+ tenantSecret: 'your-secret', // Optional, required for signedUrl()
156
176
  })
157
177
  ```
158
178
 
package/dist/index.d.mts CHANGED
@@ -13,10 +13,6 @@ interface ImageEdits {
13
13
  text?: TextOverlay;
14
14
  }
15
15
  interface ImageUrlOptions extends ImageEdits {}
16
- interface UploadOptions {
17
- key: string;
18
- contentType?: string;
19
- }
20
16
  interface UploadResult {
21
17
  uploadUrl: string;
22
18
  key: string;
@@ -60,8 +56,8 @@ declare class ImagesResource {
60
56
  constructor(client: Immagin);
61
57
  signedUrl(key: string, edits?: ImageEdits): string;
62
58
  url(key: string, options?: ImageUrlOptions): Promise<string>;
63
- signUrl(key: string, contentType?: string): Promise<UploadResult>;
64
- upload(file: Blob | Buffer | ReadableStream, options: UploadOptions): Promise<UploadResult>;
59
+ signUrl(key: string): Promise<UploadResult>;
60
+ upload(file: Blob | Buffer | ReadableStream, key: string): Promise<UploadResult>;
65
61
  list(options?: ListImagesOptions): Promise<ListImagesResult>;
66
62
  delete(key: string): Promise<void>;
67
63
  }
@@ -102,4 +98,4 @@ declare class ImmaginError extends Error {
102
98
  constructor(message: string, status: number, body?: unknown | undefined);
103
99
  }
104
100
  //#endregion
105
- 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 };
101
+ export { type ApiKey, type CreateKeyResult, type ImageEdits, type ImageEntry, type ImageUrlOptions, Immagin, type ImmaginConfig, ImmaginError, type ListImagesOptions, type ListImagesResult, type TextOverlay, type TextPosition, type UploadResult };
package/dist/index.mjs CHANGED
@@ -1,6 +1,43 @@
1
1
  import { createHmac } from "node:crypto";
2
2
 
3
3
  //#region src/resources/images.ts
4
+ const MIME_TYPES = {
5
+ jpg: "image/jpeg",
6
+ jpeg: "image/jpeg",
7
+ png: "image/png",
8
+ gif: "image/gif",
9
+ webp: "image/webp",
10
+ avif: "image/avif",
11
+ svg: "image/svg+xml",
12
+ tiff: "image/tiff",
13
+ tif: "image/tiff",
14
+ bmp: "image/bmp",
15
+ ico: "image/x-icon",
16
+ heic: "image/heic",
17
+ heif: "image/heif"
18
+ };
19
+ function mimeFromKey(key) {
20
+ const ext = key.split(".").pop()?.toLowerCase();
21
+ return ext ? MIME_TYPES[ext] : void 0;
22
+ }
23
+ function mimeFromBuffer(buf) {
24
+ if (buf.length < 12) return void 0;
25
+ if (buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "image/jpeg";
26
+ if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
27
+ if (buf[0] === 71 && buf[1] === 73 && buf[2] === 70) return "image/gif";
28
+ if (buf[0] === 82 && buf[1] === 73 && buf[2] === 70 && buf[3] === 70 && buf[8] === 87 && buf[9] === 69 && buf[10] === 66 && buf[11] === 80) return "image/webp";
29
+ if (buf[0] === 66 && buf[1] === 77) return "image/bmp";
30
+ if (buf[4] === 102 && buf[5] === 116 && buf[6] === 121 && buf[7] === 112) {
31
+ const brand = buf.slice(8, 12).toString("ascii");
32
+ if (brand === "avif" || brand === "avis") return "image/avif";
33
+ if (brand === "heic" || brand === "heix") return "image/heic";
34
+ if (brand === "heif" || brand === "mif1") return "image/heif";
35
+ }
36
+ }
37
+ function mimeFromFile(file) {
38
+ if (typeof Blob !== "undefined" && file instanceof Blob && file.type) return file.type;
39
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(file)) return mimeFromBuffer(file);
40
+ }
4
41
  var ImagesResource = class {
5
42
  constructor(client) {
6
43
  this.client = client;
@@ -28,15 +65,19 @@ var ImagesResource = class {
28
65
  }
29
66
  return (await this.client.request("GET", "/v1/images/url", { params })).url;
30
67
  }
31
- async signUrl(key, contentType) {
32
- return this.client.request("POST", "/v1/images/sign-url", { body: {
68
+ async signUrl(key) {
69
+ const contentType = mimeFromKey(key);
70
+ return this.client.request("POST", "/v1/images/sign-url", { body: contentType ? {
33
71
  key,
34
- contentType: contentType || "application/octet-stream"
35
- } });
72
+ contentType
73
+ } : { key } });
36
74
  }
37
- async upload(file, options) {
38
- const { key, contentType } = options;
39
- const signResult = await this.signUrl(key, contentType);
75
+ async upload(file, key) {
76
+ const contentType = mimeFromFile(file) ?? mimeFromKey(key);
77
+ const signResult = await this.client.request("POST", "/v1/images/sign-url", { body: contentType ? {
78
+ key,
79
+ contentType
80
+ } : { key } });
40
81
  await fetch(signResult.uploadUrl, {
41
82
  method: "PUT",
42
83
  body: file,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immagin/client",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Node.js and browser client for the Immagin image processing API",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,15 +34,14 @@
34
34
  "publishConfig": {
35
35
  "access": "public"
36
36
  },
37
- "scripts": {
38
- "build": "tsdown",
39
- "dev": "tsdown --watch",
40
- "test": "vitest run",
41
- "prepublishOnly": "tsdown"
42
- },
43
37
  "devDependencies": {
44
38
  "tsdown": "^0.20.3",
45
39
  "typescript": "^5.7.2",
46
40
  "vitest": "^3.0.5"
41
+ },
42
+ "scripts": {
43
+ "build": "tsdown",
44
+ "dev": "tsdown --watch",
45
+ "test": "vitest run"
47
46
  }
48
- }
47
+ }