@edgestore/server 0.1.5-alpha.0 → 0.1.5-alpha.10
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/adapters/express/index.d.ts +1 -0
- package/adapters/express/index.js +1 -0
- package/dist/adapters/express/index.d.ts +20 -0
- package/dist/adapters/express/index.d.ts.map +1 -0
- package/dist/adapters/express/index.js +117 -0
- package/dist/adapters/express/index.mjs +113 -0
- package/dist/adapters/next/app/index.d.ts +5 -3
- package/dist/adapters/next/app/index.d.ts.map +1 -1
- package/dist/adapters/next/app/index.js +45 -16
- package/dist/adapters/next/app/index.mjs +44 -15
- package/dist/adapters/next/pages/index.d.ts +5 -3
- package/dist/adapters/next/pages/index.d.ts.map +1 -1
- package/dist/adapters/next/pages/index.js +37 -13
- package/dist/adapters/next/pages/index.mjs +36 -12
- package/dist/adapters/shared.d.ts +19 -1
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/core/client/index.d.ts +116 -4
- package/dist/core/client/index.d.ts.map +1 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +109 -34
- package/dist/core/index.mjs +110 -36
- package/dist/core/sdk/index.d.ts.map +1 -1
- package/dist/{index-f33a00fb.js → index-0c401ce1.js} +38 -4
- package/dist/{index-50ab9e08.js → index-23d1ede9.mjs} +42 -7
- package/dist/{index-30a3741e.mjs → index-a2e7ca9e.js} +49 -4
- package/dist/libs/errors/EdgeStoreApiClientError.d.ts +8 -0
- package/dist/libs/errors/EdgeStoreApiClientError.d.ts.map +1 -0
- package/dist/libs/errors/EdgeStoreCredentialsError.d.ts.map +1 -1
- package/dist/libs/errors/EdgeStoreError.d.ts +36 -4
- package/dist/libs/errors/EdgeStoreError.d.ts.map +1 -1
- package/dist/libs/logger.d.ts +13 -0
- package/dist/libs/logger.d.ts.map +1 -0
- package/dist/logger-7ea2248c.mjs +40 -0
- package/dist/logger-b530a3e1.js +42 -0
- package/dist/logger-e0066db9.js +33 -0
- package/dist/providers/aws/index.js +1 -1
- package/dist/providers/aws/index.mjs +1 -1
- package/dist/providers/azure/index.d.ts +20 -0
- package/dist/providers/azure/index.d.ts.map +1 -0
- package/dist/providers/azure/index.js +61 -0
- package/dist/providers/azure/index.mjs +57 -0
- package/dist/providers/edgestore/index.d.ts.map +1 -1
- package/dist/providers/edgestore/index.js +10 -3
- package/dist/providers/edgestore/index.mjs +10 -3
- package/dist/providers/types.d.ts +1 -0
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/{shared-306c1af2.js → shared-06cb0d86.js} +150 -61
- package/dist/{shared-9fad0d51.js → shared-d474acc6.js} +114 -46
- package/dist/{shared-6dea9e91.mjs → shared-d7ea66fb.mjs} +141 -52
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +17 -2
- package/providers/azure/index.d.ts +1 -0
- package/providers/azure/index.js +1 -0
- package/src/adapters/express/index.ts +164 -0
- package/src/adapters/next/app/index.ts +63 -20
- package/src/adapters/next/pages/index.ts +52 -15
- package/src/adapters/shared.ts +142 -40
- package/src/core/client/index.ts +233 -51
- package/src/core/index.ts +6 -0
- package/src/core/sdk/index.ts +7 -1
- package/src/libs/errors/EdgeStoreApiClientError.ts +14 -0
- package/src/libs/errors/EdgeStoreCredentialsError.ts +1 -2
- package/src/libs/errors/EdgeStoreError.ts +74 -7
- package/src/libs/logger.ts +44 -0
- package/src/providers/aws/index.ts +1 -1
- package/src/providers/azure/index.ts +89 -0
- package/src/providers/edgestore/index.ts +9 -2
- package/src/providers/types.ts +1 -0
- package/src/types.ts +8 -0
package/src/core/client/index.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
2
|
+
import { type z, type ZodNever } from 'zod';
|
|
1
3
|
import { type AnyRouter, type Comparison } from '..';
|
|
2
|
-
import {
|
|
4
|
+
import { buildPath, parsePath } from '../../adapters/shared';
|
|
5
|
+
import { type Prettify, type Simplify } from '../../types';
|
|
3
6
|
import {
|
|
4
7
|
type AnyBuilder,
|
|
5
8
|
type InferBucketPathKeys,
|
|
@@ -16,6 +19,104 @@ export type GetFileRes<TBucket extends AnyBuilder> = {
|
|
|
16
19
|
path: InferBucketPathObject<TBucket>;
|
|
17
20
|
};
|
|
18
21
|
|
|
22
|
+
export type UploadOptions = {
|
|
23
|
+
/**
|
|
24
|
+
* e.g. 'my-file-name.jpg'
|
|
25
|
+
*
|
|
26
|
+
* By default, a unique file name will be generated for each upload.
|
|
27
|
+
* If you want to use a custom file name, you can use this option.
|
|
28
|
+
* If you use the same file name for multiple uploads, the previous file will be overwritten.
|
|
29
|
+
* But it might take some time for the CDN cache to be cleared.
|
|
30
|
+
* So maybe you will keep seeing the old file for a while.
|
|
31
|
+
*
|
|
32
|
+
* If you want to replace an existing file immediately leave the `manualFileName` option empty and use the `replaceTargetUrl` option.
|
|
33
|
+
*/
|
|
34
|
+
manualFileName?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Use this to replace an existing file.
|
|
37
|
+
* It will automatically delete the existing file when the upload is complete.
|
|
38
|
+
*/
|
|
39
|
+
replaceTargetUrl?: string;
|
|
40
|
+
/**
|
|
41
|
+
* If true, the file needs to be confirmed by using the `confirmUpload` function.
|
|
42
|
+
* If the file is not confirmed within 24 hours, it will be deleted.
|
|
43
|
+
*
|
|
44
|
+
* This is useful for pages where the file is uploaded as soon as it is selected,
|
|
45
|
+
* but the user can leave the page without submitting the form.
|
|
46
|
+
*
|
|
47
|
+
* This avoids unnecessary zombie files in the bucket.
|
|
48
|
+
*/
|
|
49
|
+
temporary?: boolean;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
type TextContent = string;
|
|
53
|
+
type BlobContent = {
|
|
54
|
+
blob: Blob;
|
|
55
|
+
extension: string;
|
|
56
|
+
};
|
|
57
|
+
type UrlContent = {
|
|
58
|
+
url: string;
|
|
59
|
+
extension: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// type guard for `content`
|
|
63
|
+
function isTextContent(
|
|
64
|
+
content: TextContent | BlobContent | UrlContent,
|
|
65
|
+
): content is TextContent {
|
|
66
|
+
return typeof content === 'string';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isBlobContent(
|
|
70
|
+
content: TextContent | BlobContent | UrlContent,
|
|
71
|
+
): content is BlobContent {
|
|
72
|
+
return typeof content !== 'string' && 'blob' in content;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type UploadFileRequest<TBucket extends AnyBuilder> = {
|
|
76
|
+
/**
|
|
77
|
+
* Can be a string, a blob or an url.
|
|
78
|
+
*
|
|
79
|
+
* If it's a string, it will be converted to a blob with the type `text/plain`.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* // string
|
|
83
|
+
* content: "some text"
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* // blob
|
|
87
|
+
* content: {
|
|
88
|
+
* blob: new Blob([text], { type: "text/csv" }),
|
|
89
|
+
* extension: "csv",
|
|
90
|
+
* }
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* // url
|
|
94
|
+
* content: {
|
|
95
|
+
* url: "https://example.com/my-file.csv",
|
|
96
|
+
* extension: "csv",
|
|
97
|
+
* }
|
|
98
|
+
*/
|
|
99
|
+
content: TextContent | BlobContent | UrlContent;
|
|
100
|
+
options?: UploadOptions;
|
|
101
|
+
} & (TBucket['$config']['ctx'] extends Record<string, never>
|
|
102
|
+
? {}
|
|
103
|
+
: {
|
|
104
|
+
ctx: TBucket['$config']['ctx'];
|
|
105
|
+
}) &
|
|
106
|
+
(TBucket['_def']['input'] extends ZodNever
|
|
107
|
+
? {}
|
|
108
|
+
: {
|
|
109
|
+
input: z.infer<TBucket['_def']['input']>;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export type UploadFileRes<TBucket extends AnyBuilder> = {
|
|
113
|
+
url: string;
|
|
114
|
+
size: number;
|
|
115
|
+
metadata: InferMetadataObject<TBucket>;
|
|
116
|
+
path: InferBucketPathObject<TBucket>;
|
|
117
|
+
pathOrder: (keyof InferBucketPathObject<TBucket>)[];
|
|
118
|
+
};
|
|
119
|
+
|
|
19
120
|
type Filter<TBucket extends AnyBuilder> = {
|
|
20
121
|
AND?: Filter<TBucket>[];
|
|
21
122
|
OR?: Filter<TBucket>[];
|
|
@@ -65,24 +166,50 @@ type EdgeStoreClient<TRouter extends AnyRouter> = {
|
|
|
65
166
|
getFile: (params: {
|
|
66
167
|
url: string;
|
|
67
168
|
}) => Promise<GetFileRes<TRouter['buckets'][K]>>;
|
|
68
|
-
|
|
69
|
-
// requestUpload: (params: {
|
|
70
|
-
// file: File;
|
|
71
|
-
// path: {
|
|
72
|
-
// [TKey in InferBucketPathKeys<TRouter['buckets'][K]>]: string;
|
|
73
|
-
// };
|
|
74
|
-
// metadata: InferMetadataObject<TRouter['buckets'][K]>;
|
|
75
|
-
// replaceTargetUrl?: string;
|
|
76
|
-
// }) => Promise<{
|
|
77
|
-
// uploadUrl: string;
|
|
78
|
-
// accessUrl: string;
|
|
79
|
-
// }>;
|
|
169
|
+
|
|
80
170
|
/**
|
|
81
|
-
*
|
|
171
|
+
* Use this function to upload a file to the bucket directly from your backend.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* // simple example
|
|
176
|
+
* await backendClient.myBucket.upload({
|
|
177
|
+
* content: "some text",
|
|
178
|
+
* });
|
|
179
|
+
* ```
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* // complete example
|
|
184
|
+
* await backendClient.myBucket.upload({
|
|
185
|
+
* content: {
|
|
186
|
+
* blob: new Blob([text], { type: "text/csv" }),
|
|
187
|
+
* extension: "csv",
|
|
188
|
+
* },
|
|
189
|
+
* options: {
|
|
190
|
+
* temporary: true,
|
|
191
|
+
* replaceTargetUrl: replaceUrl,
|
|
192
|
+
* manualFileName: "test.csv",
|
|
193
|
+
* },
|
|
194
|
+
* ctx: {
|
|
195
|
+
* userId: "123",
|
|
196
|
+
* userRole: "admin",
|
|
197
|
+
* },
|
|
198
|
+
* input: {
|
|
199
|
+
* type: "post",
|
|
200
|
+
* },
|
|
201
|
+
* });
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
upload: (
|
|
205
|
+
params: UploadFileRequest<TRouter['buckets'][K]>,
|
|
206
|
+
) => Promise<Prettify<UploadFileRes<TRouter['buckets'][K]>>>;
|
|
207
|
+
/**
|
|
208
|
+
* Confirm a temporary file upload directly from your backend.
|
|
82
209
|
*/
|
|
83
210
|
confirmUpload: (params: { url: string }) => Promise<{ success: boolean }>;
|
|
84
211
|
/**
|
|
85
|
-
* Programmatically delete a file.
|
|
212
|
+
* Programmatically delete a file directly from your backend.
|
|
86
213
|
*/
|
|
87
214
|
deleteFile: (params: { url: string }) => Promise<{
|
|
88
215
|
success: boolean;
|
|
@@ -95,7 +222,7 @@ type EdgeStoreClient<TRouter extends AnyRouter> = {
|
|
|
95
222
|
*/
|
|
96
223
|
listFiles: (
|
|
97
224
|
params?: ListFilesRequest<TRouter['buckets'][K]>,
|
|
98
|
-
) => Promise<ListFilesResponse<TRouter['buckets'][K]
|
|
225
|
+
) => Promise<Prettify<ListFilesResponse<TRouter['buckets'][K]>>>;
|
|
99
226
|
};
|
|
100
227
|
};
|
|
101
228
|
|
|
@@ -124,6 +251,91 @@ export function initEdgeStoreClient<TRouter extends AnyRouter>(config: {
|
|
|
124
251
|
throw new Error(`Bucket ${bucketName} not found`);
|
|
125
252
|
}
|
|
126
253
|
const client: EdgeStoreClient<TRouter>[string] = {
|
|
254
|
+
async upload(params) {
|
|
255
|
+
const content = params.content;
|
|
256
|
+
const ctx = 'ctx' in params ? params.ctx : {};
|
|
257
|
+
const input = 'input' in params ? params.input : {};
|
|
258
|
+
|
|
259
|
+
const { blob, extension } = await (async () => {
|
|
260
|
+
if (isTextContent(content)) {
|
|
261
|
+
return {
|
|
262
|
+
blob: new Blob([content], { type: 'text/plain' }),
|
|
263
|
+
extension: 'txt',
|
|
264
|
+
};
|
|
265
|
+
} else if (isBlobContent(content)) {
|
|
266
|
+
return {
|
|
267
|
+
blob: content.blob,
|
|
268
|
+
extension: content.extension,
|
|
269
|
+
};
|
|
270
|
+
} else {
|
|
271
|
+
return {
|
|
272
|
+
blob: await getBlobFromUrl(content.url),
|
|
273
|
+
extension: content.extension,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
})();
|
|
277
|
+
|
|
278
|
+
const path = buildPath({
|
|
279
|
+
bucket,
|
|
280
|
+
pathAttrs: {
|
|
281
|
+
ctx,
|
|
282
|
+
input,
|
|
283
|
+
},
|
|
284
|
+
fileInfo: {
|
|
285
|
+
type: blob.type,
|
|
286
|
+
size: blob.size,
|
|
287
|
+
extension,
|
|
288
|
+
temporary: false,
|
|
289
|
+
fileName: params.options?.manualFileName,
|
|
290
|
+
replaceTargetUrl: params.options?.replaceTargetUrl,
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
const metadata = await bucket._def.metadata({
|
|
294
|
+
ctx,
|
|
295
|
+
input,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const requestUploadRes = await sdk.requestUpload({
|
|
299
|
+
bucketName,
|
|
300
|
+
bucketType: bucket._def.type,
|
|
301
|
+
fileInfo: {
|
|
302
|
+
fileName: params.options?.manualFileName,
|
|
303
|
+
replaceTargetUrl: params.options?.replaceTargetUrl,
|
|
304
|
+
type: blob.type,
|
|
305
|
+
size: blob.size,
|
|
306
|
+
extension,
|
|
307
|
+
isPublic: bucket._def.accessControl === undefined,
|
|
308
|
+
temporary: params.options?.temporary ?? false,
|
|
309
|
+
path,
|
|
310
|
+
metadata,
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const { signedUrl, multipart } = requestUploadRes;
|
|
315
|
+
|
|
316
|
+
if (multipart) {
|
|
317
|
+
// TODO
|
|
318
|
+
throw new Error('Multipart upload not implemented');
|
|
319
|
+
} else if (signedUrl) {
|
|
320
|
+
await fetch(signedUrl, {
|
|
321
|
+
method: 'PUT',
|
|
322
|
+
body: blob,
|
|
323
|
+
});
|
|
324
|
+
} else {
|
|
325
|
+
throw new Error('Missing signedUrl');
|
|
326
|
+
}
|
|
327
|
+
const { parsedPath, pathOrder } = parsePath(path);
|
|
328
|
+
return {
|
|
329
|
+
url: requestUploadRes.accessUrl,
|
|
330
|
+
size: blob.size,
|
|
331
|
+
metadata,
|
|
332
|
+
path: parsedPath,
|
|
333
|
+
pathOrder,
|
|
334
|
+
} satisfies UploadFileRes<typeof bucket> as UploadFileRes<
|
|
335
|
+
TRouter['buckets'][string]
|
|
336
|
+
>;
|
|
337
|
+
},
|
|
338
|
+
|
|
127
339
|
async getFile(params) {
|
|
128
340
|
const res = await sdk.getFile(params);
|
|
129
341
|
return {
|
|
@@ -136,41 +348,6 @@ export function initEdgeStoreClient<TRouter extends AnyRouter>(config: {
|
|
|
136
348
|
TRouter['buckets'][string]
|
|
137
349
|
>;
|
|
138
350
|
},
|
|
139
|
-
// TODO: Replace with `upload`
|
|
140
|
-
// async requestUpload(params) {
|
|
141
|
-
// const { file, path, metadata, replaceTargetUrl } = params;
|
|
142
|
-
// const fileExtension = file.name.includes('.')
|
|
143
|
-
// ? file.name.split('.').pop()
|
|
144
|
-
// : undefined;
|
|
145
|
-
// if (!fileExtension) {
|
|
146
|
-
// throw new Error('Missing file extension');
|
|
147
|
-
// }
|
|
148
|
-
// const parsedPath = Object.keys(bucket._def.path).map((key) => {
|
|
149
|
-
// const value = path[key as keyof typeof path];
|
|
150
|
-
// if (value === undefined) {
|
|
151
|
-
// throw new Error(`Missing path param ${key}`);
|
|
152
|
-
// }
|
|
153
|
-
// return {
|
|
154
|
-
// key,
|
|
155
|
-
// value,
|
|
156
|
-
// };
|
|
157
|
-
// });
|
|
158
|
-
|
|
159
|
-
// const fileInfo = {
|
|
160
|
-
// size: file.size,
|
|
161
|
-
// extension: fileExtension,
|
|
162
|
-
// isPublic: bucket._def.accessControl === undefined,
|
|
163
|
-
// path: parsedPath,
|
|
164
|
-
// metadata,
|
|
165
|
-
// replaceTargetUrl,
|
|
166
|
-
// };
|
|
167
|
-
|
|
168
|
-
// return await sdk.requestUpload({
|
|
169
|
-
// bucketName,
|
|
170
|
-
// bucketType: bucket._def.type,
|
|
171
|
-
// fileInfo,
|
|
172
|
-
// });
|
|
173
|
-
// },
|
|
174
351
|
|
|
175
352
|
async confirmUpload(params) {
|
|
176
353
|
return await sdk.confirmUpload(params);
|
|
@@ -232,6 +409,11 @@ function getUrl(url: string, baseUrl?: string) {
|
|
|
232
409
|
return url;
|
|
233
410
|
}
|
|
234
411
|
|
|
412
|
+
async function getBlobFromUrl(url: string) {
|
|
413
|
+
const res = await fetch(url);
|
|
414
|
+
return await res.blob();
|
|
415
|
+
}
|
|
416
|
+
|
|
235
417
|
export type InferClientResponse<TRouter extends AnyRouter> = {
|
|
236
418
|
[TBucketName in keyof TRouter['buckets']]: {
|
|
237
419
|
[TClienFn in keyof EdgeStoreClient<TRouter>[TBucketName]]: Simplify<
|
package/src/core/index.ts
CHANGED
|
@@ -7,5 +7,11 @@ export type {
|
|
|
7
7
|
InferBucketPathObject,
|
|
8
8
|
InferMetadataObject,
|
|
9
9
|
} from './internals/bucketBuilder';
|
|
10
|
+
export type {
|
|
11
|
+
EdgeStoreErrorCodeKey,
|
|
12
|
+
EdgeStoreErrorDetails,
|
|
13
|
+
EdgeStoreJsonResponse,
|
|
14
|
+
} from '../libs/errors/EdgeStoreError';
|
|
15
|
+
export { EdgeStoreApiClientError } from '../libs/errors/EdgeStoreApiClientError';
|
|
10
16
|
|
|
11
17
|
export type AnyRouter = EdgeStoreRouter<any>;
|
package/src/core/sdk/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type AnyRouter } from '..';
|
|
2
2
|
import EdgeStoreCredentialsError from '../../libs/errors/EdgeStoreCredentialsError';
|
|
3
|
+
import EdgeStoreError from '../../libs/errors/EdgeStoreError';
|
|
3
4
|
import { type AnyContext, type AnyMetadata } from '../internals/bucketBuilder';
|
|
4
5
|
|
|
5
6
|
const API_ENDPOINT =
|
|
@@ -93,7 +94,12 @@ export const edgeStoreRawSdk = {
|
|
|
93
94
|
path: bucket._def.path.map((p: { [key: string]: () => string }) => {
|
|
94
95
|
const paramEntries = Object.entries(p);
|
|
95
96
|
if (paramEntries[0] === undefined) {
|
|
96
|
-
throw new
|
|
97
|
+
throw new EdgeStoreError({
|
|
98
|
+
message: `Empty path param found in: ${JSON.stringify(
|
|
99
|
+
bucket._def.path,
|
|
100
|
+
)}`,
|
|
101
|
+
code: 'SERVER_ERROR',
|
|
102
|
+
});
|
|
97
103
|
}
|
|
98
104
|
const [key, value] = paramEntries[0];
|
|
99
105
|
return {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
+
|
|
3
|
+
import { type EdgeStoreJsonResponse } from './EdgeStoreError';
|
|
4
|
+
|
|
5
|
+
export class EdgeStoreApiClientError extends Error {
|
|
6
|
+
public readonly data: EdgeStoreJsonResponse;
|
|
7
|
+
|
|
8
|
+
constructor(opts: { response: EdgeStoreJsonResponse }) {
|
|
9
|
+
super(opts.response.message);
|
|
10
|
+
this.name = 'EdgeStoreApiClientError';
|
|
11
|
+
|
|
12
|
+
this.data = opts.response;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const DEFAULT_MESSAGE = `Missing EDGE_STORE_ACCESS_KEY or EDGE_STORE_SECRET_KEY.
|
|
2
|
-
This can happen if you are trying to
|
|
3
|
-
The vanilla client should only be used in the backend.`;
|
|
2
|
+
This can happen if you are trying to import something related to the backend of Edge Store in a client component.`;
|
|
4
3
|
|
|
5
4
|
class EdgeStoreCredentialsError extends Error {
|
|
6
5
|
constructor(message = DEFAULT_MESSAGE) {
|
|
@@ -1,24 +1,91 @@
|
|
|
1
|
+
import { type Simplify } from '../../types';
|
|
2
|
+
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
1
4
|
export const EDGE_STORE_ERROR_CODES = {
|
|
2
5
|
BAD_REQUEST: 400,
|
|
6
|
+
FILE_TOO_LARGE: 400,
|
|
7
|
+
MIME_TYPE_NOT_ALLOWED: 400,
|
|
3
8
|
UNAUTHORIZED: 401,
|
|
9
|
+
UPLOAD_NOT_ALLOWED: 403,
|
|
10
|
+
DELETE_NOT_ALLOWED: 403,
|
|
11
|
+
CREATE_CONTEXT_ERROR: 500,
|
|
12
|
+
SERVER_ERROR: 500,
|
|
4
13
|
} as const;
|
|
5
14
|
|
|
6
15
|
export type EdgeStoreErrorCodeKey = keyof typeof EDGE_STORE_ERROR_CODES;
|
|
7
16
|
|
|
8
|
-
|
|
17
|
+
export type EdgeStoreErrorDetails<TCode extends EdgeStoreErrorCodeKey> =
|
|
18
|
+
TCode extends 'FILE_TOO_LARGE'
|
|
19
|
+
? {
|
|
20
|
+
maxFileSize: number;
|
|
21
|
+
fileSize: number;
|
|
22
|
+
}
|
|
23
|
+
: TCode extends 'MIME_TYPE_NOT_ALLOWED'
|
|
24
|
+
? {
|
|
25
|
+
allowedMimeTypes: string[];
|
|
26
|
+
mimeType: string;
|
|
27
|
+
}
|
|
28
|
+
: never;
|
|
29
|
+
|
|
30
|
+
export type EdgeStoreJsonResponse = Simplify<
|
|
31
|
+
| {
|
|
32
|
+
message: string;
|
|
33
|
+
code: 'FILE_TOO_LARGE';
|
|
34
|
+
details: EdgeStoreErrorDetails<'FILE_TOO_LARGE'>;
|
|
35
|
+
}
|
|
36
|
+
| {
|
|
37
|
+
message: string;
|
|
38
|
+
code: 'MIME_TYPE_NOT_ALLOWED';
|
|
39
|
+
details: EdgeStoreErrorDetails<'MIME_TYPE_NOT_ALLOWED'>;
|
|
40
|
+
}
|
|
41
|
+
| {
|
|
42
|
+
message: string;
|
|
43
|
+
code: Exclude<
|
|
44
|
+
EdgeStoreErrorCodeKey,
|
|
45
|
+
'FILE_TOO_LARGE' | 'MIME_TYPE_NOT_ALLOWED'
|
|
46
|
+
>;
|
|
47
|
+
}
|
|
48
|
+
>;
|
|
49
|
+
|
|
50
|
+
class EdgeStoreError<TCode extends EdgeStoreErrorCodeKey> extends Error {
|
|
9
51
|
public readonly cause?: Error;
|
|
10
|
-
public readonly code:
|
|
52
|
+
public readonly code: TCode;
|
|
53
|
+
public readonly level: 'error' | 'warn';
|
|
54
|
+
public readonly details: EdgeStoreErrorDetails<TCode>;
|
|
11
55
|
|
|
12
|
-
constructor(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
56
|
+
constructor(
|
|
57
|
+
opts: {
|
|
58
|
+
message: string;
|
|
59
|
+
code: TCode;
|
|
60
|
+
cause?: Error;
|
|
61
|
+
} & (EdgeStoreErrorDetails<TCode> extends undefined
|
|
62
|
+
? object
|
|
63
|
+
: {
|
|
64
|
+
details: EdgeStoreErrorDetails<TCode>;
|
|
65
|
+
}),
|
|
66
|
+
) {
|
|
17
67
|
super(opts.message);
|
|
18
68
|
this.name = 'EdgeStoreError';
|
|
19
69
|
|
|
20
70
|
this.code = opts.code;
|
|
21
71
|
this.cause = opts.cause;
|
|
72
|
+
this.level = EDGE_STORE_ERROR_CODES[opts.code] >= 500 ? 'error' : 'warn';
|
|
73
|
+
this.details = 'details' in opts ? opts.details : undefined!;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
formattedMessage(): string {
|
|
77
|
+
return `${this.message}${
|
|
78
|
+
this.details ? `\n Details: ${JSON.stringify(this.details)}` : ''
|
|
79
|
+
}${this.cause ? `\n Caused by: ${this.cause.message}` : ''}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
formattedJson(): EdgeStoreJsonResponse {
|
|
83
|
+
return {
|
|
84
|
+
message:
|
|
85
|
+
this.code === 'SERVER_ERROR' ? 'Internal server error' : this.message,
|
|
86
|
+
code: this.code,
|
|
87
|
+
details: this.details as any,
|
|
88
|
+
} satisfies EdgeStoreJsonResponse;
|
|
22
89
|
}
|
|
23
90
|
}
|
|
24
91
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
3
|
+
const logLevel = ['debug', 'info', 'warn', 'error', 'none'] as const;
|
|
4
|
+
|
|
5
|
+
export type LogLevel = typeof logLevel[number];
|
|
6
|
+
|
|
7
|
+
class Logger {
|
|
8
|
+
private logLevel: LogLevel;
|
|
9
|
+
|
|
10
|
+
constructor(logLevel?: LogLevel) {
|
|
11
|
+
this.logLevel =
|
|
12
|
+
logLevel ?? (process.env.NODE_ENV === 'production' ? 'error' : 'info');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private shouldLog(level: LogLevel): boolean {
|
|
16
|
+
return logLevel.indexOf(level) >= logLevel.indexOf(this.logLevel);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
debug(message?: any, ...optionalParams: any[]): void {
|
|
20
|
+
if (this.shouldLog('debug')) {
|
|
21
|
+
console.debug('[EdgeStoreDebug]', message, ...optionalParams);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
info(message?: any, ...optionalParams: any[]): void {
|
|
26
|
+
if (this.shouldLog('info')) {
|
|
27
|
+
console.info('[EdgeStoreInfo]', message, ...optionalParams);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
warn(message?: any, ...optionalParams: any[]): void {
|
|
32
|
+
if (this.shouldLog('warn')) {
|
|
33
|
+
console.warn('[EdgeStoreWarn]', message, ...optionalParams);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
error(message?: any, ...optionalParams: any[]): void {
|
|
38
|
+
if (this.shouldLog('error')) {
|
|
39
|
+
console.error('[EdgeStoreError]', message, ...optionalParams);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default Logger;
|
|
@@ -107,7 +107,7 @@ export function AWSProvider(options?: AWSProviderOptions): Provider {
|
|
|
107
107
|
const extension = fileInfo.extension
|
|
108
108
|
? `.${fileInfo.extension.replace('.', '')}`
|
|
109
109
|
: '';
|
|
110
|
-
const fileName = `${nameId}${extension}`;
|
|
110
|
+
const fileName = fileInfo.fileName ?? `${nameId}${extension}`;
|
|
111
111
|
const filePath = fileInfo.path.reduce((acc, item) => {
|
|
112
112
|
return `${acc}/${item.value}`;
|
|
113
113
|
}, '');
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { BlobServiceClient } from '@azure/storage-blob';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
import { type Provider } from '../types';
|
|
4
|
+
|
|
5
|
+
export type AzureProviderOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* The storage account name for Azure Blob Storage
|
|
8
|
+
* Can also be set via the `ES_AZURE_ACCOUNT_NAME` environment variable.
|
|
9
|
+
*/
|
|
10
|
+
storageAccountName?: string;
|
|
11
|
+
/**
|
|
12
|
+
* SAS token for Azure Blob Storage
|
|
13
|
+
* Can also be set via the `ES_AZURE_SAS_TOKEN` environment variable.
|
|
14
|
+
*/
|
|
15
|
+
sasToken?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Azure Blob Storage container name
|
|
18
|
+
* Can also be set via the `ES_AZURE_CONTAINER_NAME` environment variable.
|
|
19
|
+
*/
|
|
20
|
+
containerName?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function AzureProvider(options?: AzureProviderOptions): Provider {
|
|
24
|
+
const {
|
|
25
|
+
storageAccountName = process.env.ES_AZURE_ACCOUNT_NAME,
|
|
26
|
+
sasToken = process.env.ES_AZURE_SAS_TOKEN,
|
|
27
|
+
containerName = process.env.ES_AZURE_CONTAINER_NAME,
|
|
28
|
+
} = options ?? {};
|
|
29
|
+
|
|
30
|
+
const baseUrl = `https://${storageAccountName}.blob.core.windows.net`;
|
|
31
|
+
const blobServiceClient = new BlobServiceClient(`${baseUrl}?${sasToken}`);
|
|
32
|
+
const containerClient = blobServiceClient.getContainerClient(
|
|
33
|
+
containerName ?? '',
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
async init() {
|
|
38
|
+
return {};
|
|
39
|
+
},
|
|
40
|
+
getBaseUrl() {
|
|
41
|
+
return baseUrl;
|
|
42
|
+
},
|
|
43
|
+
async getFile({ url }) {
|
|
44
|
+
const blobClient = containerClient.getBlobClient(url);
|
|
45
|
+
|
|
46
|
+
const { contentLength, lastModified } = await blobClient.getProperties();
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
url: url,
|
|
50
|
+
metadata: {},
|
|
51
|
+
path: {},
|
|
52
|
+
size: contentLength ?? 0,
|
|
53
|
+
uploadedAt: lastModified ?? new Date(),
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
async requestUpload({ fileInfo }) {
|
|
57
|
+
const nameId = uuidv4();
|
|
58
|
+
const extension = fileInfo.extension
|
|
59
|
+
? `.${fileInfo.extension.replace('.', '')}`
|
|
60
|
+
: '';
|
|
61
|
+
const fileName = fileInfo.fileName ?? `${nameId}${extension}`;
|
|
62
|
+
|
|
63
|
+
const blobClient = containerClient.getBlobClient(fileName);
|
|
64
|
+
|
|
65
|
+
const url = blobClient.url;
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
uploadUrl: url,
|
|
69
|
+
accessUrl: url,
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
async requestUploadParts() {
|
|
73
|
+
throw new Error('Not implemented');
|
|
74
|
+
},
|
|
75
|
+
async completeMultipartUpload() {
|
|
76
|
+
throw new Error('Not implemented');
|
|
77
|
+
},
|
|
78
|
+
async confirmUpload() {
|
|
79
|
+
throw new Error('Not implemented');
|
|
80
|
+
},
|
|
81
|
+
async deleteFile({ url }) {
|
|
82
|
+
const blobClient = containerClient.getBlobClient(url);
|
|
83
|
+
await blobClient.delete();
|
|
84
|
+
return {
|
|
85
|
+
success: true,
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { initEdgeStoreSdk } from '../../core/sdk';
|
|
2
2
|
import EdgeStoreCredentialsError from '../../libs/errors/EdgeStoreCredentialsError';
|
|
3
|
+
import EdgeStoreError from '../../libs/errors/EdgeStoreError';
|
|
3
4
|
import { type Provider, type RequestUploadRes } from '../types';
|
|
4
5
|
|
|
5
6
|
const DEFAULT_BASE_URL = 'https://files.edgestore.dev';
|
|
@@ -111,7 +112,10 @@ export function EdgeStoreProvider(
|
|
|
111
112
|
thumbnailUrl: res.thumbnailUrl,
|
|
112
113
|
};
|
|
113
114
|
} else {
|
|
114
|
-
throw new
|
|
115
|
+
throw new EdgeStoreError({
|
|
116
|
+
message: 'Could not get upload url',
|
|
117
|
+
code: 'SERVER_ERROR',
|
|
118
|
+
});
|
|
115
119
|
}
|
|
116
120
|
}
|
|
117
121
|
const res = await edgeStoreSdk.requestUpload({
|
|
@@ -126,7 +130,10 @@ export function EdgeStoreProvider(
|
|
|
126
130
|
thumbnailUrl: res.thumbnailUrl,
|
|
127
131
|
};
|
|
128
132
|
}
|
|
129
|
-
throw new
|
|
133
|
+
throw new EdgeStoreError({
|
|
134
|
+
message: 'Could not get upload url',
|
|
135
|
+
code: 'SERVER_ERROR',
|
|
136
|
+
});
|
|
130
137
|
},
|
|
131
138
|
requestUploadParts: async ({ multipart, path }) => {
|
|
132
139
|
const res = await edgeStoreSdk.requestUploadParts({
|
package/src/providers/types.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -61,6 +61,14 @@ export type Simplify<TType> = TType extends any[] | Date
|
|
|
61
61
|
? TType
|
|
62
62
|
: { [K in keyof TType]: TType[K] };
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
export type Prettify<TType> = {
|
|
68
|
+
[K in keyof TType]: TType[K];
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
70
|
+
} & {};
|
|
71
|
+
|
|
64
72
|
/**
|
|
65
73
|
* @public
|
|
66
74
|
*/
|