@edgestore/server 0.0.0-alpha.12

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.
Files changed (83) hide show
  1. package/README.md +86 -0
  2. package/adapters/next/app/index.d.ts +1 -0
  3. package/adapters/next/app/index.js +1 -0
  4. package/adapters/next/index.d.ts +1 -0
  5. package/adapters/next/index.js +1 -0
  6. package/adapters/next/pages/index.d.ts +1 -0
  7. package/adapters/next/pages/index.js +1 -0
  8. package/core/index.d.ts +1 -0
  9. package/core/index.js +1 -0
  10. package/dist/adapters/imageTypes.d.ts +2 -0
  11. package/dist/adapters/imageTypes.d.ts.map +1 -0
  12. package/dist/adapters/next/app/index.d.ts +14 -0
  13. package/dist/adapters/next/app/index.d.ts.map +1 -0
  14. package/dist/adapters/next/app/index.js +95 -0
  15. package/dist/adapters/next/app/index.mjs +91 -0
  16. package/dist/adapters/next/pages/index.d.ts +15 -0
  17. package/dist/adapters/next/pages/index.d.ts.map +1 -0
  18. package/dist/adapters/next/pages/index.js +69 -0
  19. package/dist/adapters/next/pages/index.mjs +65 -0
  20. package/dist/adapters/shared.d.ts +79 -0
  21. package/dist/adapters/shared.d.ts.map +1 -0
  22. package/dist/core/client/index.d.ts +81 -0
  23. package/dist/core/client/index.d.ts.map +1 -0
  24. package/dist/core/index.d.ts +6 -0
  25. package/dist/core/index.d.ts.map +1 -0
  26. package/dist/core/index.js +96 -0
  27. package/dist/core/index.mjs +91 -0
  28. package/dist/core/internals/bucketBuilder.d.ts +199 -0
  29. package/dist/core/internals/bucketBuilder.d.ts.map +1 -0
  30. package/dist/core/internals/createPathParamProxy.d.ts +21 -0
  31. package/dist/core/internals/createPathParamProxy.d.ts.map +1 -0
  32. package/dist/core/sdk/index.d.ts +200 -0
  33. package/dist/core/sdk/index.d.ts.map +1 -0
  34. package/dist/index-3999aae6.js +187 -0
  35. package/dist/index-3cc4d530.js +183 -0
  36. package/dist/index-ca41982b.mjs +183 -0
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +213 -0
  40. package/dist/index.mjs +209 -0
  41. package/dist/libs/errors/EdgeStoreCredentialsError.d.ts +5 -0
  42. package/dist/libs/errors/EdgeStoreCredentialsError.d.ts.map +1 -0
  43. package/dist/libs/errors/EdgeStoreError.d.ts +16 -0
  44. package/dist/libs/errors/EdgeStoreError.d.ts.map +1 -0
  45. package/dist/providers/aws/index.d.ts +9 -0
  46. package/dist/providers/aws/index.d.ts.map +1 -0
  47. package/dist/providers/aws/index.js +81 -0
  48. package/dist/providers/aws/index.mjs +77 -0
  49. package/dist/providers/edgestore/index.d.ts +8 -0
  50. package/dist/providers/edgestore/index.d.ts.map +1 -0
  51. package/dist/providers/edgestore/index.js +121 -0
  52. package/dist/providers/edgestore/index.mjs +117 -0
  53. package/dist/providers/index.d.ts +3 -0
  54. package/dist/providers/index.d.ts.map +1 -0
  55. package/dist/providers/types.d.ts +91 -0
  56. package/dist/providers/types.d.ts.map +1 -0
  57. package/dist/shared-43667670.mjs +232 -0
  58. package/dist/shared-6bef8919.js +227 -0
  59. package/dist/shared-f7607e44.js +239 -0
  60. package/dist/types.d.ts +88 -0
  61. package/dist/types.d.ts.map +1 -0
  62. package/package.json +96 -0
  63. package/providers/aws/index.d.ts +1 -0
  64. package/providers/aws/index.js +1 -0
  65. package/providers/edgestore/index.d.ts +1 -0
  66. package/providers/edgestore/index.js +1 -0
  67. package/src/adapters/imageTypes.ts +10 -0
  68. package/src/adapters/next/app/index.ts +111 -0
  69. package/src/adapters/next/pages/index.ts +84 -0
  70. package/src/adapters/shared.ts +306 -0
  71. package/src/core/client/index.ts +202 -0
  72. package/src/core/index.ts +10 -0
  73. package/src/core/internals/bucketBuilder.ts +462 -0
  74. package/src/core/internals/createPathParamProxy.ts +40 -0
  75. package/src/core/sdk/index.ts +381 -0
  76. package/src/index.ts +1 -0
  77. package/src/libs/errors/EdgeStoreCredentialsError.ts +12 -0
  78. package/src/libs/errors/EdgeStoreError.ts +25 -0
  79. package/src/providers/aws/index.ts +109 -0
  80. package/src/providers/edgestore/index.ts +140 -0
  81. package/src/providers/index.ts +2 -0
  82. package/src/providers/types.ts +107 -0
  83. package/src/types.ts +139 -0
@@ -0,0 +1,111 @@
1
+ import { NextRequest } from 'next/server';
2
+ import { EdgeStoreRouter } from '../../../core/internals/bucketBuilder';
3
+ import EdgeStoreError, {
4
+ EDGE_STORE_ERROR_CODES,
5
+ } from '../../../libs/errors/EdgeStoreError';
6
+ import { EdgeStoreProvider } from '../../../providers/edgestore';
7
+ import { Provider } from '../../../providers/types';
8
+ import { MaybePromise } from '../../../types';
9
+ import {
10
+ deleteFile,
11
+ DeleteFileBody,
12
+ init,
13
+ requestUpload,
14
+ RequestUploadBody,
15
+ requestUploadParts,
16
+ RequestUploadPartsParams,
17
+ } from '../../shared';
18
+
19
+ export type CreateContextOptions = {
20
+ req: NextRequest;
21
+ };
22
+
23
+ export type Config<TCtx> = {
24
+ provider?: Provider;
25
+ router: EdgeStoreRouter<TCtx>;
26
+ createContext: (opts: CreateContextOptions) => MaybePromise<TCtx>;
27
+ };
28
+
29
+ export function createEdgeStoreNextHandler<TCtx>(config: Config<TCtx>) {
30
+ const { provider = EdgeStoreProvider() } = config;
31
+ return async (req: NextRequest) => {
32
+ try {
33
+ if (req.nextUrl.pathname === '/api/edgestore/init') {
34
+ const ctx = await config.createContext({ req });
35
+ const { newCookies, token, baseUrl } = await init({
36
+ ctx,
37
+ provider,
38
+ router: config.router,
39
+ });
40
+ return new Response(
41
+ JSON.stringify({
42
+ token,
43
+ baseUrl,
44
+ }),
45
+ {
46
+ status: 200,
47
+ headers: {
48
+ 'Content-Type': 'application/json',
49
+ 'Set-Cookie': newCookies.join('; '),
50
+ },
51
+ },
52
+ );
53
+ } else if (req.nextUrl.pathname === '/api/edgestore/request-upload') {
54
+ const res = await requestUpload({
55
+ provider,
56
+ router: config.router,
57
+ body: (await req.json()) as RequestUploadBody,
58
+ ctxToken: req.cookies.get('edgestore-ctx')?.value,
59
+ });
60
+ return new Response(JSON.stringify(res), {
61
+ status: 200,
62
+ headers: {
63
+ 'Content-Type': 'application/json',
64
+ },
65
+ });
66
+ } else if (
67
+ req.nextUrl.pathname === '/api/edgestore/request-upload-parts'
68
+ ) {
69
+ const res = await requestUploadParts({
70
+ provider,
71
+ router: config.router,
72
+ body: (await req.json()) as RequestUploadPartsParams,
73
+ ctxToken: req.cookies.get('edgestore-ctx')?.value,
74
+ });
75
+ return new Response(JSON.stringify(res), {
76
+ status: 200,
77
+ headers: {
78
+ 'Content-Type': 'application/json',
79
+ },
80
+ });
81
+ } else if (req.nextUrl.pathname === '/api/edgestore/delete-file') {
82
+ await deleteFile({
83
+ provider,
84
+ router: config.router,
85
+ body: (await req.json()) as DeleteFileBody,
86
+ ctxToken: req.cookies.get('edgestore-ctx')?.value,
87
+ });
88
+ return new Response(null, {
89
+ status: 200,
90
+ });
91
+ } else {
92
+ return new Response(null, {
93
+ status: 404,
94
+ });
95
+ }
96
+ } catch (err) {
97
+ if (err instanceof EdgeStoreError) {
98
+ return new Response(err.message, {
99
+ status: EDGE_STORE_ERROR_CODES[err.code],
100
+ });
101
+ } else if (err instanceof Error) {
102
+ return new Response(err.message, {
103
+ status: 500,
104
+ });
105
+ }
106
+ return new Response('Internal server error', {
107
+ status: 500,
108
+ });
109
+ }
110
+ };
111
+ }
@@ -0,0 +1,84 @@
1
+ import { NextApiRequest, NextApiResponse } from 'next/types';
2
+ import { EdgeStoreRouter } from '../../../core/internals/bucketBuilder';
3
+ import EdgeStoreError, {
4
+ EDGE_STORE_ERROR_CODES,
5
+ } from '../../../libs/errors/EdgeStoreError';
6
+ import { EdgeStoreProvider } from '../../../providers/edgestore';
7
+ import { Provider } from '../../../providers/types';
8
+ import { MaybePromise } from '../../../types';
9
+ import {
10
+ deleteFile,
11
+ DeleteFileBody,
12
+ init,
13
+ requestUpload,
14
+ RequestUploadBody,
15
+ requestUploadParts,
16
+ RequestUploadPartsParams,
17
+ } from '../../shared';
18
+
19
+ export type CreateContextOptions = {
20
+ req: NextApiRequest;
21
+ res: NextApiResponse;
22
+ };
23
+
24
+ export type Config<TCtx> = {
25
+ provider?: Provider;
26
+ router: EdgeStoreRouter<TCtx>;
27
+ createContext: (opts: CreateContextOptions) => MaybePromise<TCtx>;
28
+ };
29
+
30
+ export function createEdgeStoreNextHandler<TCtx>(config: Config<TCtx>) {
31
+ const { provider = EdgeStoreProvider() } = config;
32
+ return async (req: NextApiRequest, res: NextApiResponse) => {
33
+ try {
34
+ if (req.url === '/api/edgestore/init') {
35
+ const ctx = await config.createContext({ req, res });
36
+ const { newCookies, token, baseUrl } = await init({
37
+ ctx,
38
+ provider,
39
+ router: config.router,
40
+ });
41
+ res.setHeader('Set-Cookie', newCookies);
42
+ res.json({
43
+ token,
44
+ baseUrl,
45
+ });
46
+ } else if (req.url === '/api/edgestore/request-upload') {
47
+ res.json(
48
+ await requestUpload({
49
+ provider,
50
+ router: config.router,
51
+ body: req.body as RequestUploadBody,
52
+ ctxToken: req.cookies['edgestore-ctx'],
53
+ }),
54
+ );
55
+ } else if (req.url === '/api/edgestore/request-upload-parts') {
56
+ res.json(
57
+ await requestUploadParts({
58
+ provider,
59
+ router: config.router,
60
+ body: req.body as RequestUploadPartsParams,
61
+ ctxToken: req.cookies['edgestore-ctx'],
62
+ }),
63
+ );
64
+ } else if (req.url === '/api/edgestore/delete-file') {
65
+ await deleteFile({
66
+ provider,
67
+ router: config.router,
68
+ body: req.body as DeleteFileBody,
69
+ ctxToken: req.cookies['edgestore-ctx'],
70
+ });
71
+ res.status(200).end();
72
+ } else {
73
+ res.status(404).end();
74
+ }
75
+ } catch (err) {
76
+ if (err instanceof EdgeStoreError) {
77
+ res.status(EDGE_STORE_ERROR_CODES[err.code]).send(err.message);
78
+ } else if (err instanceof Error) {
79
+ res.status(500).send(err.message);
80
+ }
81
+ res.status(500).send('Internal server error');
82
+ }
83
+ };
84
+ }
@@ -0,0 +1,306 @@
1
+ import { hkdf } from '@panva/hkdf';
2
+ import { serialize } from 'cookie';
3
+ import { EncryptJWT, jwtDecrypt } from 'jose';
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import {
6
+ AnyBuilder,
7
+ AnyPath,
8
+ EdgeStoreRouter,
9
+ } from '../core/internals/bucketBuilder';
10
+ import EdgeStoreError from '../libs/errors/EdgeStoreError';
11
+ import { Provider } from '../providers/types';
12
+ import { IMAGE_MIME_TYPES } from './imageTypes';
13
+
14
+ // TODO: change it to 1 hour when we have a way to refresh the token
15
+ const DEFAULT_MAX_AGE = 30 * 24 * 60 * 60; // 30 days
16
+
17
+ export async function init<TCtx>(params: {
18
+ provider: Provider;
19
+ router: EdgeStoreRouter<TCtx>;
20
+ ctx: TCtx;
21
+ }) {
22
+ const { ctx, provider, router } = params;
23
+ const ctxToken = await encryptJWT(ctx);
24
+ const { token } = await provider.init({
25
+ ctx,
26
+ router: router,
27
+ });
28
+ const newCookies = [
29
+ serialize('edgestore-ctx', ctxToken, {
30
+ path: '/',
31
+ maxAge: DEFAULT_MAX_AGE,
32
+ }),
33
+ ];
34
+ if (token) {
35
+ newCookies.push(
36
+ serialize('edgestore-token', token, {
37
+ path: '/',
38
+ maxAge: DEFAULT_MAX_AGE,
39
+ }),
40
+ );
41
+ }
42
+ const baseUrl = provider.getBaseUrl();
43
+
44
+ return {
45
+ newCookies,
46
+ token,
47
+ baseUrl,
48
+ };
49
+ }
50
+
51
+ export type RequestUploadBody = {
52
+ bucketName: string;
53
+ input: any;
54
+ fileInfo: {
55
+ size: number;
56
+ type: string;
57
+ extension: string;
58
+ replaceTargetUrl?: string;
59
+ };
60
+ };
61
+
62
+ export async function requestUpload<TCtx>(params: {
63
+ provider: Provider;
64
+ router: EdgeStoreRouter<TCtx>;
65
+ ctxToken: string | undefined;
66
+ body: RequestUploadBody;
67
+ }) {
68
+ const {
69
+ provider,
70
+ router,
71
+ ctxToken,
72
+ body: { bucketName, input, fileInfo },
73
+ } = params;
74
+
75
+ if (!ctxToken) {
76
+ throw new EdgeStoreError({
77
+ message: 'Missing edgestore-ctx cookie',
78
+ code: 'UNAUTHORIZED',
79
+ });
80
+ }
81
+ const ctx = await getContext(ctxToken);
82
+ const bucket = router.buckets[bucketName];
83
+ if (!bucket) {
84
+ throw new Error(`Bucket ${bucketName} not found`);
85
+ }
86
+ if (bucket._def.beforeUpload) {
87
+ const canUpload = await bucket._def.beforeUpload?.({
88
+ ctx,
89
+ input,
90
+ fileInfo: {
91
+ size: fileInfo.size,
92
+ type: fileInfo.type,
93
+ extension: fileInfo.extension,
94
+ replaceTargetUrl: fileInfo.replaceTargetUrl,
95
+ },
96
+ });
97
+ if (!canUpload) {
98
+ throw new Error('Upload not allowed');
99
+ }
100
+ }
101
+
102
+ if (bucket._def.type === 'IMAGE') {
103
+ if (!IMAGE_MIME_TYPES.includes(fileInfo.type)) {
104
+ throw new EdgeStoreError({
105
+ code: 'BAD_REQUEST',
106
+ message: 'Only images are allowed in this bucket',
107
+ });
108
+ }
109
+ }
110
+
111
+ const path = buildPath({
112
+ fileInfo,
113
+ bucket,
114
+ pathAttrs: { ctx, input },
115
+ });
116
+ const metadata = await bucket._def.metadata?.({ ctx, input });
117
+ const isPublic = bucket._def.accessControl === undefined;
118
+ const requestUploadRes = await provider.requestUpload({
119
+ bucketName,
120
+ bucketType: bucket._def.type,
121
+ fileInfo: {
122
+ ...fileInfo,
123
+ path,
124
+ isPublic,
125
+ metadata,
126
+ },
127
+ });
128
+ return {
129
+ ...requestUploadRes,
130
+ size: fileInfo.size,
131
+ uploadedAt: new Date().toISOString(),
132
+ path,
133
+ metadata,
134
+ };
135
+ }
136
+
137
+ export type RequestUploadPartsParams = {
138
+ multipart: {
139
+ uploadId: string;
140
+ parts: number[];
141
+ };
142
+ path: string;
143
+ };
144
+
145
+ export async function requestUploadParts<TCtx>(params: {
146
+ provider: Provider;
147
+ router: EdgeStoreRouter<TCtx>;
148
+ ctxToken: string | undefined;
149
+ body: RequestUploadPartsParams;
150
+ }) {
151
+ const {
152
+ provider,
153
+ router,
154
+ ctxToken,
155
+ body: { multipart, path },
156
+ } = params;
157
+ if (!ctxToken) {
158
+ throw new EdgeStoreError({
159
+ message: 'Missing edgestore-ctx cookie',
160
+ code: 'UNAUTHORIZED',
161
+ });
162
+ }
163
+ await getContext(ctxToken); // just to check if the token is valid
164
+ const bucket = router.buckets[multipart.uploadId];
165
+ if (!bucket) {
166
+ throw new Error(`Bucket ${multipart.uploadId} not found`);
167
+ }
168
+ return await provider.requestUploadParts({
169
+ multipart,
170
+ path,
171
+ });
172
+ }
173
+
174
+ export type DeleteFileBody = {
175
+ bucketName: string;
176
+ url: string;
177
+ };
178
+
179
+ export async function deleteFile<TCtx>(params: {
180
+ provider: Provider;
181
+ router: EdgeStoreRouter<TCtx>;
182
+ ctxToken: string | undefined;
183
+ body: DeleteFileBody;
184
+ }) {
185
+ const {
186
+ provider,
187
+ router,
188
+ ctxToken,
189
+ body: { bucketName, url },
190
+ } = params;
191
+
192
+ if (!ctxToken) {
193
+ throw new EdgeStoreError({
194
+ message: 'Missing edgestore-ctx cookie',
195
+ code: 'UNAUTHORIZED',
196
+ });
197
+ }
198
+ const ctx = await getContext(ctxToken);
199
+ const bucket = router.buckets[bucketName];
200
+ if (!bucket) {
201
+ throw new Error(`Bucket ${bucketName} not found`);
202
+ }
203
+
204
+ if (!bucket._def.beforeDelete) {
205
+ throw new Error(
206
+ 'You need to define beforeDelete if you want to delete files directly from the frontend.',
207
+ );
208
+ }
209
+
210
+ const file = await provider.getFile({
211
+ url,
212
+ });
213
+ const canDelete = await bucket._def.beforeDelete({
214
+ ctx,
215
+ file,
216
+ });
217
+ if (!canDelete) {
218
+ throw new Error('Delete not allowed');
219
+ }
220
+ await provider.deleteFile({
221
+ bucket,
222
+ url,
223
+ });
224
+ }
225
+
226
+ async function encryptJWT(ctx: any) {
227
+ const secret =
228
+ process.env.EDGE_STORE_JWT_SECRET ?? process.env.EDGE_STORE_SECRET_KEY;
229
+ if (!secret) {
230
+ throw new Error(
231
+ 'EDGE_STORE_JWT_SECRET or EDGE_STORE_SECRET_KEY is not defined',
232
+ );
233
+ }
234
+ const encryptionSecret = await getDerivedEncryptionKey(secret);
235
+ return await new EncryptJWT(ctx)
236
+ .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })
237
+ .setIssuedAt()
238
+ .setExpirationTime(Date.now() / 1000 + DEFAULT_MAX_AGE)
239
+ .setJti(uuidv4())
240
+ .encrypt(encryptionSecret);
241
+ }
242
+
243
+ async function decryptJWT(token: string) {
244
+ const secret =
245
+ process.env.EDGE_STORE_JWT_SECRET ?? process.env.EDGE_STORE_SECRET_KEY;
246
+ if (!secret) {
247
+ throw new Error(
248
+ 'EDGE_STORE_JWT_SECRET or EDGE_STORE_SECRET_KEY is not set',
249
+ );
250
+ }
251
+ const encryptionSecret = await getDerivedEncryptionKey(secret);
252
+ const { payload } = await jwtDecrypt(token, encryptionSecret, {
253
+ clockTolerance: 15,
254
+ });
255
+ return payload;
256
+ }
257
+
258
+ async function getDerivedEncryptionKey(secret: string) {
259
+ return await hkdf(
260
+ 'sha256',
261
+ secret,
262
+ '',
263
+ 'Edge Store Generated Encryption Key',
264
+ 32,
265
+ );
266
+ }
267
+
268
+ function buildPath(params: {
269
+ fileInfo: RequestUploadBody['fileInfo'];
270
+ bucket: AnyBuilder;
271
+ pathAttrs: {
272
+ ctx: any;
273
+ input: any;
274
+ };
275
+ }) {
276
+ const { bucket } = params;
277
+ const pathParams = bucket._def.path as AnyPath;
278
+ const path = pathParams.map((param) => {
279
+ const paramEntries = Object.entries(param);
280
+ if (paramEntries[0] === undefined) {
281
+ throw new Error('Missing path param');
282
+ }
283
+ const [key, value] = paramEntries[0];
284
+ // this is a string like: "ctx.xxx" or "input.yyy.zzz"
285
+ const currParamVal = value()
286
+ .split('.')
287
+ .reduce((acc2: any, key: string) => {
288
+ if (acc2[key] === undefined) {
289
+ throw new Error(`Missing key ${key} in ${JSON.stringify(acc2)}`);
290
+ }
291
+ return acc2[key];
292
+ }, params.pathAttrs as any) as string;
293
+ return {
294
+ key,
295
+ value: currParamVal,
296
+ };
297
+ });
298
+ return path;
299
+ }
300
+
301
+ async function getContext(token?: string) {
302
+ if (!token) {
303
+ throw new Error('No token');
304
+ }
305
+ return await decryptJWT(token);
306
+ }
@@ -0,0 +1,202 @@
1
+ import { AnyRouter, Comparison } from '..';
2
+ import { Simplify } from '../../types';
3
+ import {
4
+ AnyBuilder,
5
+ InferBucketPathKeys,
6
+ InferMetadataObject,
7
+ } from '../internals/bucketBuilder';
8
+ import { initEdgeStoreSdk } from '../sdk';
9
+
10
+ export type GetFileRes<TBucket extends AnyBuilder> = {
11
+ url: string;
12
+ size: number;
13
+ uploadedAt: Date;
14
+ metadata: InferMetadataObject<TBucket>;
15
+ path: {
16
+ [TKey in InferBucketPathKeys<TBucket>]: string;
17
+ };
18
+ };
19
+
20
+ type Filter<TBucket extends AnyBuilder> = {
21
+ AND?: Filter<TBucket>[];
22
+ OR?: Filter<TBucket>[];
23
+ uploadedAt?: Comparison<Date>;
24
+ path?: {
25
+ [K in InferBucketPathKeys<TBucket>]?: Comparison;
26
+ };
27
+ metadata?: {
28
+ [K in keyof InferMetadataObject<TBucket>]?: Comparison;
29
+ };
30
+ };
31
+
32
+ export type ListFilesRequest<TBucket extends AnyBuilder> = {
33
+ filter?: Filter<TBucket>;
34
+ pagination?: {
35
+ currentPage: number;
36
+ pageSize: number;
37
+ };
38
+ };
39
+
40
+ export type ListFilesResponse<TBucket extends AnyBuilder> = {
41
+ data: {
42
+ url: string;
43
+ thumbnailUrl: TBucket['_def']['type'] extends 'IMAGE'
44
+ ? string | null
45
+ : never;
46
+ size: number;
47
+ uploadedAt: Date;
48
+ metadata: InferMetadataObject<TBucket>;
49
+ path: {
50
+ [TKey in InferBucketPathKeys<TBucket>]: string;
51
+ };
52
+ }[];
53
+ pagination: {
54
+ currentPage: number;
55
+ totalPages: number;
56
+ totalCount: number;
57
+ };
58
+ };
59
+
60
+ type EdgeStoreClient<TRouter extends AnyRouter> = {
61
+ [K in keyof TRouter['buckets']]: {
62
+ getFile: (params: {
63
+ url: string;
64
+ }) => Promise<GetFileRes<TRouter['buckets'][K]>>;
65
+ // TODO: replace with `upload`
66
+ // requestUpload: (params: {
67
+ // file: File;
68
+ // path: {
69
+ // [TKey in InferBucketPathKeys<TRouter['buckets'][K]>]: string;
70
+ // };
71
+ // metadata: InferMetadataObject<TRouter['buckets'][K]>;
72
+ // replaceTargetUrl?: string;
73
+ // }) => Promise<{
74
+ // uploadUrl: string;
75
+ // accessUrl: string;
76
+ // }>;
77
+ /**
78
+ * Programmatically delete a file.
79
+ */
80
+ deleteFile: (params: { url: string }) => Promise<{
81
+ success: boolean;
82
+ }>;
83
+ /**
84
+ * List files in a bucket.
85
+ *
86
+ * You can also filter the results by passing a filter object.
87
+ * The results are paginated.
88
+ */
89
+ listFiles: (
90
+ params?: ListFilesRequest<TRouter['buckets'][K]>,
91
+ ) => Promise<ListFilesResponse<TRouter['buckets'][K]>>;
92
+ };
93
+ };
94
+
95
+ export function initEdgeStoreClient<TRouter extends AnyRouter>(config: {
96
+ router: TRouter;
97
+ accessKey?: string;
98
+ secretKey?: string;
99
+ }) {
100
+ const sdk = initEdgeStoreSdk({
101
+ accessKey: config.accessKey,
102
+ secretKey: config.secretKey,
103
+ });
104
+ return new Proxy<EdgeStoreClient<TRouter>>({} as any, {
105
+ get(_target, key) {
106
+ const bucketName = key as string;
107
+ const bucket = config.router.buckets[bucketName];
108
+ if (!bucket) {
109
+ throw new Error(`Bucket ${bucketName} not found`);
110
+ }
111
+ const client: EdgeStoreClient<TRouter>[string] = {
112
+ async getFile(params) {
113
+ const res = await sdk.getFile(params);
114
+ return {
115
+ url: res.url,
116
+ size: res.size,
117
+ uploadedAt: new Date(res.uploadedAt),
118
+ metadata: res.metadata,
119
+ path: res.path as any,
120
+ } satisfies GetFileRes<typeof bucket> as GetFileRes<
121
+ TRouter['buckets'][string]
122
+ >;
123
+ },
124
+ // TODO: Replace with `upload`
125
+ // async requestUpload(params) {
126
+ // const { file, path, metadata, replaceTargetUrl } = params;
127
+ // const fileExtension = file.name.includes('.')
128
+ // ? file.name.split('.').pop()
129
+ // : undefined;
130
+ // if (!fileExtension) {
131
+ // throw new Error('Missing file extension');
132
+ // }
133
+ // const parsedPath = Object.keys(bucket._def.path).map((key) => {
134
+ // const value = path[key as keyof typeof path];
135
+ // if (value === undefined) {
136
+ // throw new Error(`Missing path param ${key}`);
137
+ // }
138
+ // return {
139
+ // key,
140
+ // value,
141
+ // };
142
+ // });
143
+
144
+ // const fileInfo = {
145
+ // size: file.size,
146
+ // extension: fileExtension,
147
+ // isPublic: bucket._def.accessControl === undefined,
148
+ // path: parsedPath,
149
+ // metadata,
150
+ // replaceTargetUrl,
151
+ // };
152
+
153
+ // return await sdk.requestUpload({
154
+ // bucketName,
155
+ // bucketType: bucket._def.type,
156
+ // fileInfo,
157
+ // });
158
+ // },
159
+
160
+ async deleteFile(params) {
161
+ return await sdk.deleteFile({
162
+ ...params,
163
+ });
164
+ },
165
+
166
+ async listFiles(params) {
167
+ const res = await sdk.listFiles({
168
+ bucketName,
169
+ ...params,
170
+ });
171
+
172
+ const files = res.data.map((file) => {
173
+ return {
174
+ url: file.url,
175
+ thumbnailUrl: file.thumbnailUrl,
176
+ size: file.size,
177
+ uploadedAt: new Date(file.uploadedAt),
178
+ metadata: file.metadata,
179
+ path: file.path as any,
180
+ };
181
+ }) satisfies ListFilesResponse<
182
+ typeof bucket
183
+ >['data'] as ListFilesResponse<TRouter['buckets'][string]>['data'];
184
+
185
+ return {
186
+ data: files,
187
+ pagination: res.pagination,
188
+ };
189
+ },
190
+ };
191
+ return client;
192
+ },
193
+ });
194
+ }
195
+
196
+ export type InferClientResponse<TRouter extends AnyRouter> = {
197
+ [TBucketName in keyof TRouter['buckets']]: {
198
+ [TClienFn in keyof EdgeStoreClient<TRouter>[TBucketName]]: Simplify<
199
+ Awaited<ReturnType<EdgeStoreClient<TRouter>[TBucketName][TClienFn]>>
200
+ >;
201
+ };
202
+ };
@@ -0,0 +1,10 @@
1
+ import { EdgeStoreRouter } from './internals/bucketBuilder';
2
+
3
+ export * from './client';
4
+ export * from './sdk';
5
+ export type {
6
+ InferBucketPathKeys,
7
+ InferMetadataObject,
8
+ } from './internals/bucketBuilder';
9
+
10
+ export type AnyRouter = EdgeStoreRouter<any>;