Skip to content

Commit 86cdf26

Browse files
committed
feat(js-api-client): allow upload for file and mass op from the client
1 parent 1a933b1 commit 86cdf26

File tree

4 files changed

+82
-31
lines changed

4 files changed

+82
-31
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,10 +366,14 @@ Upload files (like images) to your tenant via pre-signed requests. Server-side o
366366
import { createBinaryFileManager } from '@crystallize/js-api-client';
367367

368368
const files = createBinaryFileManager(api);
369-
const key = await files.uploadImage('/absolute/path/to/picture.jpg');
370-
// Use `key` in subsequent PIM mutations
369+
const mediaKey = await files.uploadImage('/absolute/path/to/picture.jpg');
370+
const staticKey = await files.uploadFile('/absolute/path/to/static/file.pdf');
371+
const bulkKey = await files.uploadMassOperationFile('/absolute/path/to/import.zip');
372+
// Use the returned keys in subsequent PIM mutations
371373
```
372374

375+
`uploadImage` validates that the file is an image before creating a `MEDIA` upload. Use `uploadFile` for assets that should live in the tenant's static file storage, and `uploadMassOperationFile` for imports handled by the mass operations pipeline. Call `uploadToTenant` directly if you need lower-level control (e.g., custom buffers or upload types).
376+
373377
[crystallizeobject]: crystallize_marketing|folder|625619f6615e162541535959
374378

375379
## Mass Call Client

UPGRADE.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,14 @@ After (v5): `createBinaryFileManager(api)`
176176
```ts
177177
import { createBinaryFileManager } from '@crystallize/js-api-client';
178178
const files = createBinaryFileManager(api);
179-
const key = await files.uploadImage('/path/to/picture.jpg');
180-
// Use `key` in subsequent PIM mutations
179+
const mediaKey = await files.uploadImage('/path/to/picture.jpg');
180+
const staticKey = await files.uploadFile('/path/to/static/file.pdf');
181+
const bulkKey = await files.uploadMassOperationFile('/path/to/import.json');
182+
// Use the returned keys in subsequent PIM mutations
181183
```
182184

185+
`uploadImage` enforces image uploads and targets the `MEDIA` storage bucket. `uploadFile` routes other assets to the static file storage, while `uploadMassOperationFile` prepares files for ingestion by the mass operations pipeline. Use `uploadToTenant` if you need to provide your own buffer or specify the upload type manually.
186+
183187
### Signature verification
184188

185189
Before (v4): `createAsyncSignatureVerifier` (and an older sync variant)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@crystallize/js-api-client",
33
"license": "MIT",
4-
"version": "5.0.0",
4+
"version": "5.1.0",
55
"type": "module",
66
"author": "Crystallize <[email protected]> (https://crystallize.com)",
77
"contributors": [

src/core/pim/create-binary-file-manager.ts

Lines changed: 69 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,50 @@ import * as fs from 'fs';
22
import * as mime from 'mime-lite';
33
import { ClientInterface } from '../client/create-client.js';
44

5-
type ImageInputWithReferenceId = {
5+
type BinaryInputWithReferenceId = {
66
filename: string;
77
mimeType: string;
88
};
99

10-
type UploadHandler = ImageInputWithReferenceId & {
10+
type BinaryHandler = BinaryInputWithReferenceId & {
1111
buffer: NonSharedBuffer;
12+
type: 'MEDIA' | 'STATIC' | 'MASS_OPERATIONS';
1213
};
1314

14-
const MUTATION_UPLOAD_FILE = `#graphql
15-
mutation UPLOAD_FILE ($filename: String!, $mimeType: String!) {
15+
const generatePresignedUploadRequest = `#graphql
16+
mutation GET_URL($file: String!, $contentType: String!, $type: FileUploadType!) {
1617
generatePresignedUploadRequest(
17-
filename: $filename
18-
contentType: $mimeType
19-
type: MEDIA
18+
filename: $file
19+
contentType: $contentType
20+
type: $type
2021
) {
2122
... on PresignedUploadRequest {
22-
url
23-
fields {
24-
name
25-
value
26-
}
23+
url
24+
fields {
25+
name
26+
value
27+
}
28+
maxSize
29+
lifetime
2730
}
2831
... on BasicError {
29-
errorName
30-
message
32+
error: message
3133
}
3234
}
3335
}`;
3436

3537
export const createBinaryFileManager = (apiClient: ClientInterface) => {
3638
// this function returns the key of the uploaded file
37-
const uploadToTenant = async ({ mimeType, filename, buffer }: UploadHandler): Promise<string> => {
39+
const uploadToTenant = async ({ type = 'MEDIA', mimeType, filename, buffer }: BinaryHandler): Promise<string> => {
3840
const signedRequestResult = await apiClient.nextPimApi<{
3941
generatePresignedUploadRequest: {
4042
url: string;
4143
fields: Array<{ name: string; value: string }>;
4244
};
43-
}>(MUTATION_UPLOAD_FILE, {
44-
filename,
45-
mimeType,
45+
}>(generatePresignedUploadRequest, {
46+
file: filename,
47+
contentType: mimeType,
48+
type: type || 'MEDIA',
4649
});
4750

4851
const payload = signedRequestResult.generatePresignedUploadRequest;
@@ -67,28 +70,68 @@ export const createBinaryFileManager = (apiClient: ClientInterface) => {
6770
return formData.get('key') as string;
6871
};
6972

70-
const uploadImage = async (imagePath: string): Promise<string> => {
71-
const extension = imagePath.split('.').pop() as string;
73+
const uploadImage = async (path: string): Promise<string> => {
74+
const extension = path.split('.').pop() as string;
7275
const mimeType = mime.getType(extension);
73-
const filename = imagePath.split('T/').pop() as string;
76+
const filename = path.split('/').pop() as string;
7477
if (!mimeType) {
75-
throw new Error(`Could not determine mime type for file: ${imagePath}`);
78+
throw new Error(`Could not determine mime type for file: ${path}`);
7679
}
7780
if (!mimeType.includes('image')) {
78-
throw new Error(`File is not an image: ${imagePath}`);
81+
throw new Error(`File is not an image: ${path}`);
7982
}
80-
const buffer = fs.readFileSync(imagePath);
81-
const imageKey = await uploadToTenant({
83+
const buffer = fs.readFileSync(path);
84+
const key = await uploadToTenant({
85+
mimeType,
86+
filename,
87+
buffer,
88+
type: 'MEDIA',
89+
});
90+
const registerImage = `#graphql
91+
mutation REGISTER_IMAGE($key: String!, $tenantId: ID!) {
92+
image {
93+
registerImage(key: $key, tenantId: $tenantId) {
94+
key
95+
}
96+
}
97+
}`;
98+
await apiClient.pimApi(registerImage, {
99+
key,
100+
tenantId: apiClient.config.tenantId,
101+
});
102+
return key;
103+
};
104+
105+
const uploadFile = async (path: string): Promise<string> => {
106+
const extension = path.split('.').pop() as string;
107+
const mimeType = mime.getType(extension);
108+
const filename = path.split('/').pop() as string;
109+
const buffer = fs.readFileSync(path);
110+
return await uploadToTenant({
82111
mimeType,
83112
filename,
84113
buffer,
114+
type: 'STATIC',
85115
});
116+
};
86117

87-
return imageKey;
118+
const uploadMassOperationFile = async (path: string): Promise<string> => {
119+
const extension = path.split('.').pop() as string;
120+
const mimeType = mime.getType(extension);
121+
const filename = path.split('/').pop() as string;
122+
const buffer = fs.readFileSync(path);
123+
return await uploadToTenant({
124+
mimeType,
125+
filename,
126+
buffer,
127+
type: 'MASS_OPERATIONS',
128+
});
88129
};
89130

90131
return {
91132
uploadToTenant,
92133
uploadImage,
134+
uploadFile,
135+
uploadMassOperationFile,
93136
};
94137
};

0 commit comments

Comments
 (0)